InnoDB에서 SELECT ... FOR UPDATE SKIP LOCKED는 다른 트랜잭션이 잠근 행을 기다리지 않고 결과에서 제외합니다. 따라서 이 질의는 전체 테이블을 한 시점에 빠짐없이 보여 주는 읽기가 아닙니다.

공식 문서는 이 불일치 뷰를 일반 트랜잭션 처리에는 적합하지 않다고 설명하고, 여러 세션이 같은 큐 형태의 표를 읽을 때 경합을 줄이는 용도는 제시합니다.

SKIP LOCKED는 잠긴 행을 기다리는 대신 건너뜁니다

InnoDB 엔진의 일반 잠금 읽기에서는 요청한 행을 다른 트랜잭션이 잠그고 있으면 읽는 세션은 잠금이 풀릴 때까지 기다립니다. SELECT ... FOR UPDATE SKIP LOCKED를 쓰면 동작이 달라집니다. 질의는 해당 행을 기다리지 않고 현재 결과 집합에서 제외합니다. SKIP LOCKED는 FOR UPDATE 또는 FOR SHARE 잠금 읽기와 함께 사용하며, 이 글은 MySQL 8.4 Reference Manual의 규칙을 기준으로 설명합니다. 문서는 이 옵션이 행 수준 잠금에만 적용된다고 밝힙니다.

잠금 해제까지 기다리는 일반 잠금 읽기와 구분해야 합니다. 기다리지 않는 두 옵션도 결과가 다릅니다. NOWAIT는 필요한 행 잠금을 즉시 얻지 못하면 오류를 반환합니다. SKIP LOCKED는 오류 대신 잠긴 행을 결과에서 빼고 나머지 중 읽을 수 있는 행을 돌려줍니다. 옵션 이름이 비슷해도 한쪽은 실패로 알려 주고, 다른 쪽은 일부 행을 생략한 결과를 반환한다는 차이가 있습니다.

두 worker의 시간표에서 선택된 행만 구분합니다

다음은 id 순으로 작업을 선택하는 교육용 큐 예시입니다. 실제 운영 테이블이나 AnkiPass 유료 카드 문항이 아닙니다. Worker A가 첫 트랜잭션에서 id=1을 잠근 뒤 아직 커밋하지 않았다고 가정합니다. Worker B가 같은 조건으로 잠금 읽기를 하면 id=1은 기다리는 대신 결과에서 빠지고, 다음으로 선택 가능한 id=2를 받을 수 있습니다.

MySQL 8.4 InnoDB의 개념 구문이며 서버에서 실행하지 않았습니다
START TRANSACTION;
SELECT id FROM jobs
WHERE state = 'ready'
ORDER BY id
LIMIT 1
FOR UPDATE SKIP LOCKED;
-- 선택한 id의 상태를 같은 트랜잭션에서 바꾸고 COMMIT
COMMIT;
id=1을 Worker A가 잠근 상태에서 Worker B가 보는 결과
세션처음에 보이는 작업SKIP LOCKED 결과이 결과가 뜻하는 것
Worker Aid=1id=1을 잠금이 행의 잠금은 A가 커밋하거나 롤백할 때까지 유지됩니다.
Worker Bid=1, id=2, id=3id=2 선택 가능현재 잠긴 id=1을 이번 잠금 읽기 결과에서 제외했습니다.
다른 읽기큐 전체질의 조건에 따른 일반 결과한 worker가 건너뛴 사실은 작업이 삭제되거나 완료됐다는 뜻이 아닙니다.

여기서 B가 id=2를 받았다는 사실은 A의 id=1이 큐에서 없어졌음을 뜻하지 않습니다. A의 트랜잭션이 롤백되면 id=1 작업은 여전히 대기 상태로 남을 수 있습니다. B는 다른 트랜잭션의 충돌하는 행 잠금을 기다리지 않고, 선택한 행에 자신의 FOR UPDATE 잠금을 획득합니다. 그러므로 결과가 0행이어도 '큐에 작업이 없다'고 확정할 수 없습니다. 모든 후보 행이 다른 세션에서 잠겼을 수도 있습니다.

빠른 큐 조회와 일관된 업무 조회를 혼동하지 않습니다

MySQL Reference Manual은 잠긴 행을 건너뛴 질의가 일관되지 않은 데이터 뷰를 반환한다고 분명히 적습니다. 여러 worker가 큐에서 각자 처리 대상을 가져가도록 경합을 줄이는 데는 쓸 수 있지만, 전체 결과를 빠짐없이 검토하거나 계정 잔액·재고 같은 업무 조건을 한 번에 판정하는 일반 읽기에는 적합하지 않습니다. '지금 처리할 수 있는 작업 선택'과 '현재 전체 상태를 정확히 요약'은 서로 다른 질문입니다.

작업 큐에서 사용하더라도 잠금 읽기와 작업 상태 변경을 같은 짧은 트랜잭션으로 묶는 것이 중요합니다. 선택만 하고 잠금을 풀면 다른 worker가 같은 행을 다시 선택할 수 있습니다. 실제 시스템은 실패 후 재시도, 오래 멈춘 작업의 회수, 중복 실행 방지 같은 정책도 별도로 정해야 합니다. SKIP LOCKED는 이 정책 전체를 자동으로 제공하는 기능이 아니라, 현재 잠긴 행을 기다리지 않는 읽기 옵션입니다.

질의 결과를 볼 때 네 가지를 순서대로 확인합니다

  1. 테이블 엔진과 질의가 InnoDB의 잠금 읽기인지 확인합니다.
  2. FOR UPDATE 또는 FOR SHARE와 SKIP LOCKED가 함께 쓰였는지 봅니다.
  3. 다른 트랜잭션의 잠금 때문에 제외될 수 있는 행을 따로 표시합니다.
  4. 0행 결과를 큐 전체가 빈 상태라는 증거로 삼지 말고, 필요한 경우 잠금 해제 후 다시 조회합니다.

흔한 오답은 'SKIP LOCKED니까 큐에서 잠긴 작업은 이미 처리 중이거나 없어졌다'고 말하는 것입니다. 기능은 행을 제외할 뿐 그 행의 업무 상태를 바꾸지 않습니다. 또 결과가 불일치 뷰라는 경고를 놓치고 일반 조회에 사용하면, 읽지 않은 행을 전체 결과에서 빠진 행으로 잘못 판단할 수 있습니다. 먼저 읽기의 목적이 빠른 작업 선점인지, 전체 데이터의 정확한 판단인지 구분하세요.

정답을 가리고 결과의 의미를 설명해 보세요

테이블에는 대기 작업 10, 11이 있고 Worker A가 10을 잠갔습니다. Worker B의 조회에 SKIP LOCKED가 있어 11만 반환됐습니다. B는 10이 삭제됐다고 결론 내릴 수 없습니다. 10은 A가 잠근 동안 B의 결과에서만 제외됐습니다. 만약 11도 다른 세션에서 잠겨 있다면 B의 결과가 0행일 수 있지만, 그 사실만으로 큐 전체가 비었다고 판단해서는 안 됩니다. 이 답은 문서에 적힌 '불일치 뷰' 경고와 큐 형태 표의 사용 범위를 그대로 적용한 것입니다.

정답을 가리고 확인해 보세요

이 글을 위해 만든 학습 예시이며 공식 기출문제나 판매 카드의 인용이 아닙니다.

Worker A가 작업 10을 잠갔고 Worker B의 SKIP LOCKED 질의가 0행을 반환했습니다. 큐가 비었다고 단정할 수 있나요?

정답과 해설 확인아닙니다. 조건에 맞는 작업이 다른 트랜잭션에 잠겨 B의 이번 결과에서 제외됐을 수 있습니다.

MySQL 8.4는 SKIP LOCKED가 잠긴 행을 결과에서 빼고, 결과가 일관된 전체 데이터 뷰가 아니라고 설명합니다. 큐 처리에 쓸 수 있다는 말은 전체 큐가 비었음을 보장한다는 뜻이 아닙니다.