공공부하자개발 · 영어 학습 노트
SQL
대용량·배치실행 계획·조인·인덱스·파티션·락·대량 처리0/8 완료
  • 01실행 계획 읽기
  • 02조인 방식: Nested Loop, Hash, Sort Merge 와 드라이빙 테이블
  • 03인덱스 튜닝
  • 04통계 정보와 힌트
  • 05파티션: 범위·목록·해시 분할, 파티션 프루닝, 파티션 단위 관리
  • 06대량 INSERT·UPDATE 와 배치 커밋
  • 07락과 데드락
  • 08대용량 삭제·아카이빙
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 대용량·배치 › 07 / 8

락과 데드락

잠금 대기, 데드락 원인과 예방, 잠금 조회
섹션 6진행 0 / 8
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6정리‹ 이전다음 ›

4. 응용 변형 예제

예제 4: 외래 키 컬럼의 인덱스 확인

부모 행을 지우거나 키를 바꿀 때 DB 는 자식 표에 그 부모를 참조하는 행이 있는지 봅니다. 자식 표의 외래 키 컬럼에 인덱스가 없으면 자식 표를 넓게 잠글 수 있습니다.

  • Oracle: 외래 키에 인덱스가 없으면 부모 행 삭제·키 변경 때 자식 표 전체에 잠금이 걸립니다.
  • MySQL InnoDB: 외래 키 컬럼에 인덱스를 자동으로 만들어 줍니다.
  • MSSQL: 외래 키 컬럼 인덱스는 자동으로 만들어지지 않습니다.

parent, child 표로 자식 표의 인덱스를 조회합니다. H2 는 외래 키 컬럼 인덱스를 자동으로 만들어 Oracle·MSSQL 과 다릅니다.

sql
SELECT
       column_name
     , ordinal_position
  FROM INFORMATION_SCHEMA.INDEX_COLUMNS
 WHERE table_name = 'CHILD'
 ORDER BY index_name, ordinal_position;
text
COLUMN_NAME | ORDINAL_POSITION
------------+-----------------
PARENT_ID   | 1
ID          | 1
(2행)

PARENT_ID 에 인덱스가 있고 PK 인 ID 에도 있습니다. 외래 키 컬럼 중 앞자리에 인덱스가 없는 것만 찾는 쿼리는 아래처럼 씁니다.

sql
SELECT
       k.table_name
     , k.column_name
  FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
 WHERE k.constraint_name = 'FK_CHILD_PARENT'
   AND NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.INDEX_COLUMNS i WHERE i.table_name = k.table_name AND i.column_name = k.column_name AND i.ordinal_position = 1);
text
TABLE_NAME | COLUMN_NAME
-----------+------------
(0행)

H2 는 자동 생성 때문에 항상 0행입니다. Oracle 이나 MSSQL 에서는 각 DB 의 인덱스 뷰(Oracle 은 USER_IND_COLUMNS, MSSQL 은 sys.index_columns)로 같은 조인을 만들면 인덱스 없는 외래 키가 나옵니다. 나온 컬럼에는 인덱스를 추가합니다.

부모 행 삭제가 자식 때문에 막히는 것도 확인합니다.

sql
-- @error
DELETE FROM parent WHERE id = 1;
text
예상 오류: Referential integrity constraint violation: "FK_CHILD_PARENT: PUBLIC.CHILD FOREIGN KEY(PARENT_ID) REFERENCES PUBLIC.PARENT(ID) (1)"

이 검사가 자식 표를 찾을 때 인덱스가 있어야 빠르고 좁게 잠급니다. 인덱스가 없는 Oracle 에서는 부모 행 삭제가 자식 표 전체 잠금으로 번질 수 있습니다.

예제 5: DB별 잠금 조회 (H2 미지원, 문법 검토만)

누가 누구를 막고 있는지는 각 DB 의 조회 뷰로 봅니다. H2 는 이 뷰들이 없어서 실행하지 않고, 아래 결과는 예제 데이터로 그린 도식입니다.

sql
-- Oracle · Tibero
SELECT
       sid
     , serial#
     , username
     , blocking_session
     , event
  FROM v$session
 WHERE blocking_session IS NOT NULL;

Oracle 은 V$SESSION 의 BLOCKING_SESSION(10g 부터)이 막고 있는 세션의 SID 를 줍니다. 잠금 자체는 V$LOCK 에서 봅니다.

도식(H2 미지원, 실행 결과 아님). 세션 B(SID 42)가 세션 A(SID 17)에게 막힌 상태입니다.

text
SID | SERIAL# | USERNAME | BLOCKING_SESSION | EVENT
----+---------+----------+------------------+------------------------------
42  | 5310    | APP      | 17               | enq: TX - row lock contention
sql
-- MySQL 8.0
SELECT
       waiting_pid
     , waiting_query
     , blocking_pid
  FROM sys.innodb_lock_waits;

MySQL 8.0 부터 performance_schema.data_locks 와 data_lock_waits 로 잠금과 대기를 볼 수 있고, sys.innodb_lock_waits 는 이를 읽기 좋게 묶은 뷰입니다. 가장 최근 데드락은 SHOW ENGINE INNODB STATUS 의 LATEST DETECTED DEADLOCK 절에 남습니다.

도식(H2 미지원, 실행 결과 아님).

text
waiting_pid | waiting_query                    | blocking_pid
------------+----------------------------------+-------------
28          | UPDATE account SET balance = ... | 25
sql
-- MSSQL
SELECT
       session_id
     , blocking_session_id
     , wait_type
     , wait_time
  FROM sys.dm_exec_requests
 WHERE blocking_session_id <> 0;

MSSQL 은 sys.dm_exec_requests 의 blocking_session_id 가 0 이 아닌 요청이 대기 중인 세션입니다. 어떤 자원이 잠겼는지는 sys.dm_tran_locks 에서 봅니다.

도식(H2 미지원, 실행 결과 아님).

text
session_id | blocking_session_id | wait_type | wait_time
-----------+---------------------+-----------+----------
55         | 52                  | LCK_M_U   | 31200

조회로 막고 있는 세션을 찾은 뒤 그 세션이 무엇을 하는지 확인하고, 정말 필요할 때만 세션을 종료합니다. 종료된 세션의 트랜잭션은 롤백되므로 확인 없이 끊지 않습니다.

예제 6: 작업 큐의 DB별 문법 (문법 검토만)

예제 2 의 H2 문법과 달리 "N건만 잡기" 를 쓰는 모양이 DB마다 다릅니다. 아래는 문법 검토만 한 것입니다.

sql
-- MySQL 8.0
SELECT id FROM job_queue WHERE status = 'WAIT' ORDER BY id LIMIT 3 FOR UPDATE SKIP LOCKED;
sql
-- MSSQL
SELECT TOP 3 id FROM job_queue WITH (UPDLOCK, READPAST) WHERE status = 'WAIT' ORDER BY id;
sql
-- Oracle · Tibero
SELECT id FROM job_queue WHERE status = 'WAIT' ORDER BY id FOR UPDATE SKIP LOCKED;

MSSQL 은 UPDLOCK 이 행을 잠그고 READPAST 가 잠긴 행을 건너뜁니다. Oracle 은 FETCH FIRST 와 FOR UPDATE 를 함께 쓰면 오류(ORA-02014)이고, ROWNUM <= 3 은 SKIP LOCKED 보다 먼저 적용되어 3건을 못 채울 수 있습니다. 그래서 FOR UPDATE SKIP LOCKED 커서를 열고 3건만 가져옵니다(PL/SQL, 문법 검토만).

응용 변형 예제
  • 예제 4: 외래 키 컬럼의 인덱스 확인
  • 예제 5: DB별 잠금 조회 (H2 미지원, 문법 검토만)
  • 예제 6: 작업 큐의 DB별 문법 (문법 검토만)
이전 섹션3 코드 예제4 / 6다음 섹션5 자주 하는 실수 (Tip)