목차는 다음과 같다.
- 커서 기반 페이지네이션 수정
- 트랜잭션
- 인덱스 연구하기
내가 진행중, 진행 완료한 미션 모아서 보는 쿼리(페이징 포함)에서
정렬 기준을 1순위는 포인트로 2순위는 최신순으로 하여 Cursor기반 페이지네이션을 구현해볼 것이다.
기존 구현 방식은 최신순, id 조합으로 구현을 했었다.
SELECT
m.id, m.points, m.content, s.name AS mission_name, s.store_id, um.user_mission_status
FROM (
SELECT user_mission_id, mission_id, user_mission_status
FROM user_missions
WHERE um.user_id = 1
AND um.user_mission_status IN ('PROGRESS', 'SUCCESS')
AND (created_at < '2025-09-18 12:00:00.000000' OR
(um.user_mission_id < 123 AND created_at = '2025-09-18 12:00:00.000000'))
ORDER BY created_at, user_mission_id DESC
LIMIT 10;
)
JOIN missions AS um ON m.mission_id = um.mission_id
JOIN stores s ON m.store_id = s.store_id;
포인트,날짜,id 조합으로 구현하면 된다. WHERE절을 다음과 같이 수정해야 한다.
m.points < 100
OR (m.points < 100 AND m.created_at '2025-09-18 12:00:00.000000')
OR (m.points < 100 AND m.created_at '2025-09-18 12:00:00.000000' AND m.mission_id < 123)
전체 코드는 다음과 같이 된다.
SELECT
m.mission_id,
m.points,
m.content,
s.name AS store_name,
s.store_id,
um.user_mission_status
FROM (
SELECT um.user_mission_id, um.mission_id, um.user_mission_status
FROM user_missions um
WHERE um.user_id = 1
AND um.user_mission_status IN ('PROGRESS', 'SUCCESS')
AND (
m.points < 100
OR (m.points = 100 AND um.created_at < '2025-09-18 12:00:00.000000')
OR (m.points = 100 AND um.created_at = '2025-09-18 12:00:00.000000' AND um.user_mission_id < 123)
)
ORDER BY m.points DESC, um.created_at DESC, um.user_mission_id DESC
LIMIT 10
) um
JOIN missions m ON um.mission_id = m.mission_id
JOIN stores s ON m.store_id = s.store_id;
트랜잭션은 데이터베이스의 상태를 변화시키는 한 개의 논리적 작업 단위이다. SQL문을 사용해서 데이터베이스에 접근하는 것이다. 여러 SQL문이더라도 한번 실행하는 단위라고 볼 수 있다. 데이터의 일관성을 보장하기 위해서 사용된다.
트랜잭션 관리를 비즈니스 로직과 분리하는 것이 권장되는데, 스프링에서는 @Transactional 어노테이션을 통해, AOP를 사용하여 트랜잭션 관리를 쉽게 분리할 수 있도록 한다.
스프링에서 메서드 실행 시, 트랜잭션에 대해 하나의 트랜잭션이 다른 트랜잭션 내에서 호출될 때 해당 트랜잭션을 어떻게 처리할 지, 기존 트랜잭션이 있는지, 없는 채로 실행해도 되는지, 새로 만들어야만 하는지를 결정하는 규칙을 전파라고 한다.
Spring에서는 @Transactional의 propagation 옵션으로 설정 가능하다.
인덱스는 데이터 검색 성능 향상을 위해서 사용하는 자료구조로, 주로 B-Tree 또는 Hash이다.
종류는 다음과 같은 게 있다.
함수 기반 인덱스와 복합 인덱스를, 위의 커서 기반 페이징 쿼리 기반으로 생성해보고, 성능 비교를 해 볼 것이다.
커서가 문자열(title)일 때 대소문자 구분을 없애고 싶다면 다음과 같이 생성할 수 있다.
CREATE INDEX idx_mission_lower_title
ON mission ((LOWER(title)));
장점
단점
복합 인덱스는 여러 컬럼을 묶어서, 특정 조합을 조회할 때 유리하다. 사용자의 상태별로 미션을 조회할 때,
사용자id, 사용자의 미션 진행 상태, 미션 id로 조회를 하게 된다. 조건이 많을 경우에 인덱스를 사용하면 성능이 약간 향상될 수 있다. 즉 자주 조회하는 컬럼들을 묶어서, 인덱스를 생성하는 것이다.
CREATE INDEX idx_user_status_mission
ON user_mission(user_id, status, mission_id);
장점
단점