UMC_9기 실전 SQL

seonseon·2025년 9월 15일
0

연합동아리_umc

목록 보기
3/6

📍cursor 기반 페이징

문제 상황
member_mission 테이블과 mission 테이블이 있음.

우리는 이 테이블을 join하여 reward 필드를 내림차순, 그다음 created_at 필드를 내림차순으로 정렬해서 페이징(cursor 기반) 하고 싶음.

그냥 OFFSET 쓰면 느려지니까 → 커서(cursor) 값을 만들어서 효율적으로 다음 페이지를 가져오려는 것.

SELECT
  mm.id AS member_mission_id,
  m.reward,
  s.name AS store_name,
  m.mission_spec,
  mm.status, 
  CONCAT(LPAD(m.reward, 10, '0'), LPAD(UNIX_TIMESTAMP(mm.created_at), 20, '0')) as cursor_value
  
FROM member_mission AS mm
  JOIN mission AS m ON mm.mission_id = m.id
  JOIN store AS s ON m.store_id = s.id
WHERE 
  mm.member_id = {로그인 사용자 ID 넣기}
  AND mm.status IN ('진행중', '성공')
  AND (
    CONCAT(LPAD(m.reward, 10, '0'), LPAD(UNIX_TIMESTAMP(mm.created_at), 20, '0'))
    < '{이전 페이지 마지막 cursor_value}'
    )
ORDER BY m.reward DESC, mm.created_at DESC
LIMIT 3;

중요 코드

CONCAT(LPAD(m.reward, 10, '0'), LPAD(UNIX_TIMESTAMP(mm.created_at), 20, '0'))
  • created_at은 DATETIME 타입이라 UNIX_TIMESTAMP()나 DATE_FORMAT() 같은 함수로 숫자/문자열로 변환해야 함.

  • reward 필드는 10글자로, created_at 필드는 20글자로 만든 후, 해당 cursor_value를 사용하여 페이지네이션 구현.

다양한 트랜젝션 상태와 트랜젝션 전파

트랜젝션 상태
MySQL에서 트랜잭션은 데이터베이스를 상태를 바꾸는 일종의 작업 단위이다.
INSERT, DELETE, UPDATE 등의 SQL 명령문을 통해 데이터를 상태를 바꿀 때마다 내부적으로 자동적으로 Commit을 실행하여 변경된 내역을 데이터베이스의 반영하는 것이다.
(git commit과 비슷한 기능)

트랜젝션의 상태는 다음과 같이 도식화할 수 있다.
1. 활성(Active)
트랜잭션이 정상적으로 실행중인 상태를 의미한다.트랜잭션이 시작되면, 해당 트랜잭션의 상태는 활동(Active)상태가 된다. 해당 상태는 설계자가 설계한 대로 연산들이 정상적으로 실행중인 상태를 의미한다.

- 작업 성공 시
2-1. 부분 완료(Partially Committed)
트랜잭션의 마지막까지 실행되었지만, Commit 연산이 실행되기 직전의 상태

2-2. 완료(Committed)
트랜잭션이 성공이 종료되어 Commit 연산을 실행한 후의 상태

-작업 실패 시
2-1. 실패(Failed) : 트랜잭션 실행에 오류가 발생하여 중단된 상태.

2-2. 철회(Aborted) : 트랜잭션이 비정상적으로 종료되어 Rollback 연산을 수행한 상태.트랜잭션을 수행하는 중간에 모종의 원인으로 인하여 오류가 발생하여 실행이 중단된 상태를 실패(Failed)상태라고 한다.

📍예외 : DDL문(CREATE, DROP, ALTER, RENAME, TRUNCATE)은 transaction의 rollback 대상이 아니다.

트랜젝션 전파

트랜잭션 전파는 메서드 호출 시 트랜잭션을 어떻게 이어갈지 결정하는 정책이다.

전파 속성설명사용 예시
REQUIRED (기본값)이미 트랜잭션이 있으면 참여, 없으면 새로 생성대부분의 일반 서비스 로직
REQUIRES_NEW무조건 새로운 트랜잭션 생성, 기존 트랜잭션은 일시 정지로그 기록, 알림 전송 (독립적으로 성공해야 하는 작업)
SUPPORTS트랜잭션이 있으면 참여, 없으면 비트랜잭션 실행트랜잭션 필요 없는 조회 로직
MANDATORY반드시 기존 트랜잭션 안에서 실행, 없으면 예외 발생반드시 트랜잭션 안에서만 실행돼야 하는 경우
NOT_SUPPORTED트랜잭션이 있으면 중단시키고 비트랜잭션 실행대용량 조회 (트랜잭션 오버헤드 줄이고 싶을 때)
NEVER트랜잭션이 있으면 예외 발생트랜잭션이 절대 필요 없는 경우
NESTED부모 트랜잭션 안에서 중첩 트랜잭션 시작 (savepoint)부분 롤백이 필요한 작업

📍 스프링에서 @Transactional + propagation 어노테이션 사용하는 법

@Service
public class MissionService {

    // 1. REQUIRED (기본값)
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveMissionRequired() {
        // 트랜잭션 있으면 참여, 없으면 새로 생성
    }

    // 2. REQUIRES_NEW
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveMissionRequiresNew() {
        // 항상 새로운 트랜잭션 시작 (기존 트랜잭션은 잠시 중단)
    }

    // 3. SUPPORTS
    @Transactional(propagation = Propagation.SUPPORTS)
    public void getMissionSupports() {
        // 트랜잭션 있으면 참여, 없으면 트랜잭션 없이 실행
    }

    // 4. MANDATORY
    @Transactional(propagation = Propagation.MANDATORY)
    public void saveMissionMandatory() {
        // 반드시 기존 트랜잭션 필요 (없으면 예외 발생)
    }

    // 5. NOT_SUPPORTED
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void getMissionNotSupported() {
        // 트랜잭션 있으면 중단시키고 트랜잭션 없이 실행
    }

    // 6. NEVER
    @Transactional(propagation = Propagation.NEVER)
    public void saveMissionNever() {
        // 트랜잭션 있으면 예외 발생
    }

    // 7. NESTED
    @Transactional(propagation = Propagation.NESTED)
    public void saveMissionNested() {
        // 부모 트랜잭션 안에서 savepoint 기반 중첩 트랜잭션 실행
    }
}
  • @Transactional 기본은 REQUIRED
  • 상황에 맞게 propagation 속성을 지정해서 제어한다.

출처: https://inpa.tistory.com/entry/MYSQL-📚-트랜잭션Transaction-이란-💯-정리 [Inpa Dev 👨‍💻:티스토리]

0개의 댓글