Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- FLUTTER
- LinkedList
- my_sql
- 주말도 한다
- 추상클래스
- dart
- 2일차
- rdbms nosql 차이
- 주말에도 1일 1쿼리
- flutter 믹스인
- 배열과 리스트
- late 키워드
- mysql mongo 성능 비교
- ?. ?? ! late
- null 병합 연산자
- 1일 1쿼리
- 컴포지션과 집합
- jmeter
- array
- null check 연산자
- 앱개발 가보자고
- 주말도 식지않아
- null 억제 연산자
- null safety
- 비동기 처리
- SQL
- 다중상속
- MySQL
- 콜백 함수
- 오늘은 1일 2쿼리
Archives
- Today
- Total
subindev 님의 블로그
2025.02.07(금)- 채팅방 조회 쿼리 최적화 본문
채팅방 조회 쿼리 최적화 방법
상황 : 현재 RDBS(MySQL)을 이용하여 데이터베이스를 설계함. 채팅방 아이디를 이용하여 아래의 DTO를 조회해아함.
쿼리를 하나로 작성하는 것과 여러 개의 쿼리로 나누어 실행하는 것 중 어떤 것이 더 성능적으로 유리할까?
하나로 작성할 때, JOIN 후 limit을 거는것과 limit을 건 서브쿼리와 JOIN 하는 것에 차이가 있을까?
1. DTO 구조
private int chatRoomId;
private ProductCardDTO productCard;
private List<UserCardDTO> participants;
private List<ChatMessageDTO> messages; // TODO : Message - Pagination
2. 성능 최적화를 위한 접근 방식
1️⃣ 조인 후 LIMIT 걸어 30개 추출 (쿼리 비용: 1214.48)
- 조인 시 메시지가 많아질수록 성능 저하
- 메시지가 많을 경우, 모든 메시지를 조인 후 정렬하는 건 비효율적
- 메시지 10만 건이 있다면, 불필요한 데이터도 함께 읽어야 하므로 성능 저하 발생 가능
- 따라서 WITH 절이나 서브쿼리로 먼저 30개를 제한하는 방법이 효과적
2️⃣ WITH 절을 활용하여 상위 30개만 추출 후 조인 (쿼리 비용: 2.91)
- 서브쿼리 & WITH 절을 사용하면 최적화 가능
- 데이터베이스 옵티마이저가 먼저 30개만 가져오고 나머지 조인을 수행 → 불필요한 데이터 읽기 방지
- 실행 비용도 1,214 → 2.91로 대폭 감소.
3️⃣ 여러 개의 쿼리로 나눠 실행 (총 쿼리 비용: 1027.25)
- 여러 개의 쿼리로 나누면 DB 호출이 증가할 수 있음
- 여러 개의 쿼리로 나누면 DB I/O가 증가해서 네트워크 비용이 커질 수 있음.
- 하지만 경우에 따라 캐싱이나 비동기 처리를 활용하면 더 나을 수도 있음.
- 예를 들어, 참여자 목록은 캐싱하고, 채팅 메시지만 페이징 처리하는 방법도 고려 가능
3. 쿼리 성능 비교
1️⃣ 조인 후 LIMIT 걸어 30개 추출 (쿼리 비용: 1214.48)
SELECT DISTINCT
c.id AS chatRoomId,
p.product_images AS productThumbnail,
p.product_name AS name,
p.price AS price,
p.is_negotiable AS isNegotiable,
p.id AS productId,
m.user_id AS userId,
u.name AS userName,
u.profile_img AS profileImage,
m.content AS messageContent,
m.created_at AS messageCreatedAt
FROM tb_chat_room AS c
JOIN tb_product AS p ON p.id = c.product_id
JOIN tb_chat_message AS m ON c.id = m.chat_room_id
JOIN tb_user AS u ON u.id = m.user_id
WHERE c.id = 1
ORDER BY m.created_at DESC
LIMIT 30;
장점:
- 쿼리가 단순하여 작성이 용이함.
단점:
- 채팅 메시지가 많을 경우, 전체 데이터에 대해 정렬 및 조인 연산이 수행되므로 성능 저하 가능.
- 불필요한 데이터를 읽고 정렬하는 비용이 발생.
2️⃣ WITH 절을 활용하여 상위 30개만 추출 후 조인 (쿼리 비용: 2.91)
(1) WITH 절 사용
WITH recent_messages AS (
SELECT
chat_room_id, content, created_at, user_id
FROM tb_chat_message
WHERE chat_room_id = 1
ORDER BY created_at DESC
LIMIT 30
)
SELECT
c.id AS chatRoomId,
p.product_images AS productThumbnail,
p.product_name AS name,
p.price AS price,
p.is_negotiable AS isNegotiable,
p.id AS productId,
rm.user_id AS userId,
u.name AS userName,
u.profile_img AS profileImage,
rm.content AS messageContent,
rm.created_at AS messageCreatedAt
FROM tb_chat_room c
JOIN tb_product p ON p.id = c.product_id
JOIN recent_messages rm ON c.id = rm.chat_room_id
JOIN tb_user u ON u.id = rm.user_id;
(2) 서브쿼리 사용
SELECT
c.id AS chatRoomId,
p.product_images AS productThumbnail,
p.product_name AS name,
p.price AS price,
p.is_negotiable AS isNegotiable,
p.id AS productId,
rm.user_id AS userId,
u.name AS userName,
u.profile_img AS profileImage,
rm.content AS messageContent,
rm.created_at AS messageCreatedAt
FROM tb_chat_room c
JOIN tb_product p ON p.id = c.product_id
JOIN (
SELECT
chat_room_id, content, created_at, user_id
FROM tb_chat_message
WHERE chat_room_id = 1
ORDER BY created_at DESC
LIMIT 30
) rm ON c.id = rm.chat_room_id
JOIN tb_user u ON u.id = rm.user_id;
장점:
- tb_chat_message 테이블에서 필요한 최신 30개 메시지만 먼저 조회 → 불필요한 데이터 조인 방지
- 데이터 양이 많아질수록 효율적
단점:
- 쿼리가 다소 복잡해질 수 있음.
- 서브쿼리의 최적화 여부는 DBMS에 따라 다를 수 있음.
3️⃣ 여러 개의 쿼리로 나눠 실행 (총 쿼리 비용: 1027.25)
-- (1) 채팅방 참여자 조회
SELECT
u.id AS userId,
u.name AS userName,
u.profile_img AS profileImage
FROM tb_user u
WHERE u.id IN(
SELECT DISTINCT user_id
FROM tb_chat_mapping
WHERE chat_room_id = 1
ORDER BY create_at DESC
);
-- (2) 상품 정보 조회
SELECT
p.id AS productId,
p.product_images AS productThumbnail,
p.product_name AS name,
p.price AS price,
p.is_negotiable AS isNegotiable
FROM tb_chat_room c
JOIN tb_product p ON p.id = c.product_id
WHERE c.id = 1;
-- (3) 최신 30개 메시지 조회
SELECT
chat_room_id, content, created_at, user_id
FROM tb_chat_message
WHERE chat_room_id = 1
ORDER BY created_at DESC
LIMIT 30;
- 데이터를 개별적으로 가져와서 필요한 경우만 최적화 가능.
- 백엔드에서 추가 가공이 가능하여 유연성 증가.
단점:
- API 요청이 많아지고 데이터베이스 연결이 여러 번 발생하여 성능 저하 가능.
- JOIN을 사용하지 않으므로 일관성 관리가 어려울 수 있음.
❓ 2️⃣의 with 절 내 쿼리와 3️⃣의 사용자 쿼리가 같은데 비용이 다르다 ?
- 쿼리 옵티마이저가 3️⃣쿼리는 전체 테이블을 스캔한 후 정렬하여 LIMIT를 걸고
- 2️⃣ 쿼리는 같은 쿼리지만, WITH절로 묶음으로써 옵티마이저가 더 효율적인 실행 계획을 선택하게 됩니다.
# 3️⃣ 쿼리 코스트 - 1014.25
SELECT
chat_room_id, content, created_at, user_id
FROM tb_chat_message
WHERE chat_room_id = 1
ORDER BY created_at DESC
LIMIT 30;
# 이유 : 쿼리 옵티마이저는 이를 전체 테이블을 먼저 스캔하고, 정렬한 다음 LIMIT을 적용하려고 할 수 있습니다.
# 즉, "일단 다 가져와서 정렬하고 30개만 보여줄게" 라는 접근 방식
# 2️⃣ 쿼리 코스트 - 2.91
WITH recent_messages AS (
SELECT
chat_room_id, content, created_at, user_id
FROM tb_chat_message
WHERE chat_room_id = 1
ORDER BY created_at DESC
LIMIT 30
)
SELECT
c.id AS chatRoomId,
p.product_images AS productThumbnail,
p.product_name AS name,
p.price AS price,
p.is_negotiable AS isNegotiable,
p.id AS productId,
rm.user_id AS userId,
u.name AS userName,
u.profile_img AS profileImage,
rm.content AS messageContent,
rm.created_at AS messageCreatedAt
FROM tb_chat_room c
JOIN tb_product p ON p.id = c.product_id
JOIN recent_messages rm ON c.id = rm.chat_room_id
JOIN tb_user u ON u.id = rm.user_id;
# 이유 : 옵티마이저가 이를 "아, 어차피 30개만 필요하니까 먼저 30개만 가져와서 임시 테이블에 넣고 시작하자" 라고 해석
# 실제로는 같은 쿼리지만, WITH절로 묶음으로써 옵티마이저가 더 효율적인 실행 계획을 선택하게 됩니다.
4. 결론
1️⃣ 단일 쿼리 + LIMIT | 1214.48 | 간단한 쿼리 | 메시지가 많아질수록 성능 저하 |
2️⃣ WITH 절 + 조인 | 2.91 | 성능 최적화 | 쿼리 구조 복잡 |
3️⃣ 여러 개의 쿼리 | 1027.25 | 유연성 | DB 연결 비용 증가 |
=> 메시지가 많다는 전제 하에 2️⃣쿼리가 가장 효과적
'데이터 베이스 > SQL - 1일 1쿼리' 카테고리의 다른 글
2025.02.07(금) 1일 1쿼리 (0) | 2025.02.07 |
---|---|
2025.01.21(화) 1일 1쿼리 (0) | 2025.01.21 |
2025.01.20(월) 1일 1쿼리 (0) | 2025.01.20 |
2025.01.19(일) 1일 1쿼리 (0) | 2025.01.19 |
2025.01.18(토) 1일 1쿼리 (0) | 2025.01.18 |