격리수준별 동작과 InnoDB 잠금으로 동시성 문제와 데드락을 제어한다.
트랜잭션 격리수준과 잠금 심화
입문편에서 ACID 개념과 COMMIT/ROLLBACK의 기본 사용법을 익혔다면, 이번 레슨은 한 단계 더 깊이 들어갑니다. 실제 서비스 환경에서는 수십~수백 개의 커넥션이 동시에 데이터를 읽고 쓰기 때문에, 격리수준이 내부적으로 어떻게 동작하는지, InnoDB가 어떤 잠금을 언제 거는지를 정확히 이해해야 성능 저하와 데드락 문제를 피할 수 있습니다.
이 레슨에서는 READ COMMITTED와 REPEATABLE READ의 내부 메커니즘 차이부터, 갭 락·넥스트키 락이 팬텀 리드를 막는 원리, 그리고 데드락 발생 시나리오와 진단·회피 설계까지 실무에 바로 적용 가능한 수준으로 다룹니다.
학습 목표
- READ COMMITTED와 REPEATABLE READ의 스냅샷 생성 시점 차이를 설명할 수 있다.
- 팬텀 리드가 발생하는 조건과 갭 락(Gap Lock) / **넥스트키 락(Next-Key Lock)**이 이를 차단하는 원리를 이해한다.
- 레코드 락, 인텐션 락, 삽입 인텐션 락의 역할을 구분하고 잠금 충돌 관계를 파악한다.
SHOW ENGINE INNODB STATUS로 데드락을 분석하고, 발생 패턴별 회피 전략을 적용할 수 있다.- 비관적 잠금(
SELECT ... FOR UPDATE)과 낙관적 잠금(버전 컬럼 비교)을 상황에 맞게 선택할 수 있다.
READ COMMITTED vs REPEATABLE READ 내부 동작 차이
InnoDB의 일관된 읽기(Consistent Read)는 **MVCC(Multi-Version Concurrency Control)**를 기반으로 합니다. 각 레코드는 변경 시마다 언두 로그(Undo Log)에 이전 버전을 보존하고, 트랜잭션은 자신에게 "보여야 할" 버전을 스냅샷 기준 시점으로 결정합니다.
핵심 차이는 스냅샷을 언제 찍느냐입니다.
| 격리수준 | 스냅샷 생성 시점 | 발생 가능한 이상 현상 |
|---|---|---|
| READ COMMITTED | 각 SELECT 문 실행 시점마다 새로 생성 | Non-Repeatable Read 발생 가능 |
| REPEATABLE READ | 트랜잭션 시작 후 첫 번째 읽기 시점에 한 번만 생성 | 동일 쿼리 반복 결과 일관성 보장 |
-- 세션 A
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1;
-- 결과: 10000
-- 세션 B (별도 연결)
UPDATE accounts SET balance = 5000 WHERE id = 1;
COMMIT;
-- 세션 A 에서 다시 조회
SELECT balance FROM accounts WHERE id = 1;
-- READ COMMITTED → 5000 (세션 B의 변경이 반영됨)
-- REPEATABLE READ → 10000 (트랜잭션 최초 읽기 시점 스냅샷 유지)
💡 TIP MySQL 기본 격리수준은
REPEATABLE READ입니다. 스냅샷이 언두 로그 체인을 통해 유지되므로, 트랜잭션을 오래 열어두면 언두 로그가 쌓여 퍼지(Purge) 지연과 성능 저하를 유발합니다. 긴 트랜잭션은 반드시 피하세요.
현재 격리수준과 언두 히스토리 길이는 다음으로 확인할 수 있습니다.
-- 현재 세션 격리수준 확인
SELECT @@transaction_isolation;
-- InnoDB 언두 히스토리 길이 모니터링
SHOW ENGINE INNODB STATUS\G
-- History list length 값이 수천 이상이면 퍼지 지연 징후
팬텀 리드와 갭 락·넥스트키 락
팬텀 리드란?
같은 트랜잭션 안에서 동일 조건으로 SELECT를 두 번 실행했을 때, 첫 번째에는 없던 행이 두 번째에 나타나는 현상입니다. REPEATABLE READ의 MVCC 스냅샷만으로는 막을 수 없고, 잠금 읽기(Locking Read) 또는 쓰기 작업 경로에서 실제 행 존재 여부를 다시 확인하기 때문에 발생합니다.
-- 세션 A
START TRANSACTION;
SELECT * FROM orders WHERE amount > 1000 FOR UPDATE;
-- 결과: 3건
-- 세션 B
INSERT INTO orders (amount) VALUES (1500);
COMMIT;
-- 세션 A (잠금 없이 단순 SELECT라면 스냅샷 읽기라 3건 유지)
-- 하지만 FOR UPDATE / INSERT~SELECT 경로에서는 팬텀 발생 가능
SELECT * FROM orders WHERE amount > 1000 FOR UPDATE;
-- InnoDB 갭 락이 없다면 4건이 될 수 있음
갭 락(Gap Lock)
갭 락은 인덱스 레코드 사이의 간격에 거는 잠금입니다. 실제 행이 없는 범위를 잠가서 새로운 행 삽입을 차단합니다.
인덱스 값: 10 --- (갭) --- 20 --- (갭) --- 30
↑ ↑
갭 락 영역 갭 락 영역
-- amount 컬럼에 인덱스 존재 가정
-- 세션 A: amount = 15인 레코드가 없는 상황에서
SELECT * FROM orders WHERE amount = 15 FOR UPDATE;
-- InnoDB는 (10, 20) 사이 갭에 Gap Lock을 걸어
-- 세션 B가 amount = 15를 INSERT하는 것을 차단
⚠️ 주의 갭 락은 READ COMMITTED에서는 거의 사용되지 않습니다. REPEATABLE READ 이상에서만 기본 활성화됩니다. READ COMMITTED로 낮추면 갭 락이 줄어 삽입 동시성이 높아지지만, 팬텀 리드가 허용됩니다.
넥스트키 락(Next-Key Lock)
넥스트키 락은 레코드 락 + 해당 레코드 앞의 갭 락을 합친 복합 잠금입니다. InnoDB가 범위 스캔 시 기본적으로 사용하는 잠금 단위입니다.
-- orders 테이블에 amount 값이 10, 20, 30이 있다고 가정
-- 세션 A
SELECT * FROM orders WHERE amount BETWEEN 15 AND 25 FOR UPDATE;
-- 실제로 걸리는 잠금:
-- Next-Key Lock: (10, 20] → 20 레코드 + (10,20) 갭
-- Next-Key Lock: (20, 30] → 30 레코드 + (20,30) 갭
-- 따라서 amount = 22 INSERT 시도도 차단됨
| 잠금 유형 | 범위 | 팬텀 리드 방지 |
|---|---|---|
| Record Lock | 특정 인덱스 레코드 1개 | 불가 |
| Gap Lock | 레코드 사이 간격 | 가능 (삽입 차단) |
| Next-Key Lock | 간격 + 오른쪽 레코드 | 가능 (InnoDB 기본) |
레코드 락·인텐션 락·삽입 인텐션 락 구분
InnoDB의 잠금은 여러 계층과 목적으로 나뉩니다.
레코드 락(Record Lock)
인덱스 레코드 하나에 걸리는 가장 기본적인 잠금입니다. SELECT ... FOR UPDATE는 X(배타) 잠금, SELECT ... FOR SHARE는 S(공유) 잠금을 획득합니다.
-- 단일 행 X 잠금 (다른 세션의 읽기·쓰기 모두 차단)
SELECT * FROM products WHERE id = 42 FOR UPDATE;
-- 단일 행 S 잠금 (다른 세션의 읽기는 허용, 쓰기는 차단)
SELECT * FROM products WHERE id = 42 FOR SHARE;
⚠️ 주의
WHERE조건에 인덱스가 없으면 레코드 락이 아니라 테이블 전체 행에 잠금이 걸릴 수 있습니다. 반드시 잠금 쿼리에는 인덱스가 사용되는지EXPLAIN으로 확인하세요.
인텐션 락(Intention Lock)
테이블 수준 잠금으로, 행 수준 잠금이 걸릴 것임을 테이블 레벨에서 선언합니다. 실제 행을 막는 것이 아니라 LOCK TABLES 같은 테이블 전체 잠금과의 충돌을 빠르게 감지하기 위한 메타 잠금입니다.
- IS(Intention Shared): 행에 S 잠금을 걸기 전 자동 획득
- IX(Intention Exclusive): 행에 X 잠금을 걸기 전 자동 획득
LOCK TABLES t WRITE; -- 테이블 X 잠금 시도
→ 다른 세션이 IX를 보유 중이면 즉시 충돌 감지 (행 하나하나 확인 불필요)
인텐션 락 호환 행렬:
| IS | IX | S | X | |
|---|---|---|---|---|
| IS | 호환 | 호환 | 호환 | 충돌 |
| IX | 호환 | 호환 | 충돌 | 충돌 |
| S | 호환 | 충돌 | 호환 | 충돌 |
| X | 충돌 | 충돌 | 충돌 | 충돌 |
삽입 인텐션 락(Insert Intention Lock)
갭 락의 특수한 형태로, INSERT 직전에 획득하는 잠금입니다. 같은 갭에 대한 삽입 인텐션 락끼리는 서로 호환되기 때문에, 서로 다른 위치에 삽입하는 세션들이 불필요하게 차단되지 않습니다.
-- 세션 A: amount = 12 삽입 예정 → Insert Intention Lock on (10, 20) gap
-- 세션 B: amount = 17 삽입 예정 → Insert Intention Lock on (10, 20) gap
-- → 두 세션 모두 진행 가능 (같은 갭 내라도 삽입 위치가 다르면 호환)
-- 단, 세션 C가 Gap Lock으로 (10, 20) 갭을 이미 잠그고 있다면
-- 세션 A, B의 Insert Intention Lock은 세션 C의 Gap Lock과 충돌하여 대기
현재 보유 중인 잠금을 실시간으로 확인하려면 다음을 사용합니다.
-- MySQL 8.0+ (information_schema.innodb_lock_waits / innodb_locks는
-- 8.0에서 제거되었으므로 performance_schema.data_lock_waits를 사용)
SELECT
r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
b.trx_query blocking_query
FROM performance_schema.data_lock_waits w
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id;
-- MySQL 5.7 이하에서는 information_schema.innodb_lock_waits를 사용
-- (위 뷰는 5.7까지만 유효하며 8.0에서는 'Unknown table' 오류 발생)
-- 또는 버전 무관하게 sys.innodb_lock_waits 뷰로 간단히 조회 가능
데드락 발생 시나리오와 SHOW ENGINE INNODB STATUS 분석
전형적인 데드락 패턴
데드락은 두 트랜잭션이 서로 상대방이 보유한 잠금을 기다리며 영구 대기에 빠지는 상황입니다.
-- 세션 A -- 세션 B
START TRANSACTION; START TRANSACTION;
UPDATE accounts SET balance = balance - 100
WHERE id = 1; -- id=1 X잠금 획득
UPDATE accounts SET balance = balance - 200
WHERE id = 2; -- id=2 X잠금 획득
UPDATE accounts SET balance = balance + 100
WHERE id = 2; -- id=2 대기 (세션B 보유)
UPDATE accounts SET balance = balance + 200
WHERE id = 1; -- id=1 대기 (세션A 보유)
-- → 데드락! InnoDB가 하나를 롤백
SHOW ENGINE INNODB STATUS 분석
데드락이 발생하면 InnoDB는 자동으로 한 트랜잭션을 롤백하고 LATEST DETECTED DEADLOCK 섹션에 상세 정보를 기록합니다.
SHOW ENGINE INNODB STATUS\G
출력에서 확인할 핵심 항목:
------------------------
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 421938, ACTIVE 5 sec starting index read
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 10, OS thread handle 140..., query id 345 ...
UPDATE accounts SET balance = balance + 100 WHERE id = 2
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 35 page no 4 n bits 72
index PRIMARY of table `mydb`.`accounts`
trx id 421938 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: ... id=1
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 35 page no 4 n bits 72
index PRIMARY of table `mydb`.`accounts`
trx id 421938 lock_mode X locks rec but not gap waiting
Record lock ... id=2
*** (2) TRANSACTION: ...
*** (2) HOLDS THE LOCK(S): ... id=2
*** (2) WAITING FOR THIS LOCK TO BE GRANTED: ... id=1
*** WE ROLL BACK TRANSACTION (2)
분석 포인트:
- HOLDS THE LOCK(S): 각 트랜잭션이 보유한 잠금 대상 레코드 확인
- WAITING FOR THIS LOCK: 기다리는 잠금 대상 확인 → 순환 의존 구조 파악
- WE ROLL BACK TRANSACTION (N): 롤백된 트랜잭션 번호 (일반적으로 더 적은 작업을 한 쪽이 희생)
💡 TIP
innodb_print_all_deadlocks = ON으로 설정하면 모든 데드락을 MySQL 에러 로그에도 기록합니다. 운영 환경에서 데드락 추적에 매우 유용합니다.
SET GLOBAL innodb_print_all_deadlocks = ON;
데드락 회피 설계 원칙
-- ❌ 나쁜 예: 트랜잭션마다 다른 순서로 행을 잠금
-- 트랜잭션 A: id=1 → id=2 순서
-- 트랜잭션 B: id=2 → id=1 순서
-- ✅ 좋은 예: 항상 동일한 순서(예: id 오름차순)로 행을 잠금
START TRANSACTION;
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
-- id=1 먼저, id=2 나중 → 순환 대기 방지
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
비관적·낙관적 잠금 패턴과 SELECT ... FOR UPDATE/SHARE
비관적 잠금(Pessimistic Locking)
"충돌이 발생할 것"이라고 가정하고, 데이터를 읽는 순간부터 잠금을 획득하는 전략입니다. 경합이 높은 환경에서 데이터 정합성을 강하게 보장합니다.
-- ✅ 비관적 잠금: 재고 차감 예시
START TRANSACTION;
-- 읽기와 동시에 X 잠금 획득 (다른 세션의 읽기·수정 차단)
SELECT stock FROM products WHERE id = 10 FOR UPDATE;
-- 비즈니스 로직: stock이 충분한지 확인
-- (애플리케이션 코드에서 검사 후)
UPDATE products SET stock = stock - 1 WHERE id = 10;
INSERT INTO order_items (product_id, quantity) VALUES (10, 1);
COMMIT;
FOR SHARE는 다른 세션도 읽을 수 있게 허용하되 쓰기만 차단합니다.
-- 읽기는 허용, 쓰기만 차단 (S 잠금)
SELECT * FROM products WHERE id = 10 FOR SHARE;
NOWAIT와 SKIP LOCKED로 잠금 대기 전략을 세밀하게 제어할 수 있습니다.
-- 잠금 획득 불가 시 즉시 에러 반환 (대기 없음)
SELECT * FROM products WHERE id = 10 FOR UPDATE NOWAIT;
-- 잠금된 행은 건너뛰고 잠금 가능한 행만 반환 (큐 처리에 유용)
-- 전제: status(또는 정렬/필터 컬럼)에 인덱스가 있어야 함
CREATE INDEX idx_tasks_status ON tasks(status);
SELECT * FROM tasks WHERE status = 'PENDING' LIMIT 10 FOR UPDATE SKIP LOCKED;
💡 TIP
SKIP LOCKED는 작업 큐(Job Queue) 패턴 구현에 매우 효과적입니다. 여러 워커가 동시에 미처리 작업을 가져갈 때, 이미 처리 중인 행은 자동으로 건너뜁니다. 단,status(또는 정렬·필터 기준 컬럼)에 반드시 인덱스가 있어야 합니다. 인덱스가 없으면 풀스캔으로 후보 행 전체에 잠금이 걸려, 이미 잠긴 행만 건너뛰려던SKIP LOCKED의 동시성 이점이 사라집니다.
낙관적 잠금(Optimistic Locking)
"충돌이 드물다"고 가정하고, 잠금 없이 읽은 뒤 갱신 시점에 버전이 바뀌었는지 확인하는 전략입니다. 읽기 성능이 높고 잠금 경합이 적습니다.
-- 테이블에 version 컬럼 추가
ALTER TABLE products ADD COLUMN version INT NOT NULL DEFAULT 0;
-- 낙관적 잠금: 읽기 (잠금 없음)
SELECT id, stock, version FROM products WHERE id = 10;
-- 결과: id=10, stock=100, version=5
-- 갱신 시 version 조건으로 충돌 감지
UPDATE products
SET stock = stock - 1,
version = version + 1
WHERE id = 10 AND version = 5; -- 읽을 당시의 version 명시
-- affected rows = 0이면 다른 세션이 먼저 변경한 것 → 재시도 또는 에러 처리
-- ✅ 낙관적 잠금 적합 상황: 읽기 비중 높고 충돌 드문 경우 (상품 상세 페이지 수정 등)
-- ❌ 낙관적 잠금 부적합: 재고 차감처럼 동시 경합이 많은 경우 → 재시도 폭풍 발생 가능
| 전략 | 잠금 시점 | 장점 | 단점 |
|---|---|---|---|
| 비관적 잠금 | 읽기 즉시 | 충돌 자체를 방지, 정합성 강함 | 잠금 대기·데드락 위험 |
| 낙관적 잠금 | 갱신 시 버전 확인 | 읽기 성능 높음, 잠금 경합 없음 | 충돌 시 재시도 로직 필요 |
잠금 대기·타임아웃 튜닝과 데드락 회피 설계
잠금 대기 타임아웃 설정
-- 현재 설정 확인 (기본값 50초)
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
-- 세션 수준에서 조정 (짧게 설정하면 빠른 실패로 재시도 유도)
SET SESSION innodb_lock_wait_timeout = 5;
-- 글로벌 수준 변경 (my.cnf에도 반영 권장)
SET GLOBAL innodb_lock_wait_timeout = 10;
⚠️ 주의 타임아웃이 너무 짧으면 정상적인 긴 트랜잭션도 실패하고, 너무 길면 잠금 대기 중인 커넥션이 쌓여 커넥션 풀이 고갈됩니다. 서비스 특성에 맞게 조정하고, 애플리케이션에서
Lock wait timeout exceeded에러를 반드시 처리해야 합니다.
데드락 자동 감지 설정
-- 데드락 감지 활성화 여부 (기본 ON)
SHOW VARIABLES LIKE 'innodb_deadlock_detect';
-- 고부하 환경에서 데드락 감지 비용이 크다면 OFF 고려 (lock_wait_timeout으로 대체)
-- 단, OFF 시 데드락이 timeout 전까지 지속될 수 있으므로 주의
SET GLOBAL innodb_deadlock_detect = ON;
데드락 회피를 위한 실전 체크리스트
-- 1. 인덱스 없는 컬럼 기반 잠금 쿼리 → 테이블 전체 잠금 위험
-- ❌ 나쁜 예
SELECT * FROM orders WHERE status = 'PENDING' FOR UPDATE;
-- (status에 인덱스 없으면 전체 행 잠금)
-- ✅ 좋은 예: 인덱스 생성 후 사용
CREATE INDEX idx_orders_status ON orders(status);
SELECT * FROM orders WHERE status = 'PENDING' FOR UPDATE;
-- 2. 트랜잭션을 최대한 짧게 유지
-- ❌ 나쁜 예: 트랜잭션 안에서 외부 API 호출
START TRANSACTION;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- 외부 결제 API 호출 (수 초~수십 초 소요)
UPDATE accounts SET balance = balance - amount WHERE id = 1;
COMMIT;
-- ✅ 좋은 예: 외부 작업은 트랜잭션 밖에서 처리
-- (결제 API 호출 완료 후)
START TRANSACTION;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
UPDATE accounts SET balance = balance - amount WHERE id = 1;
COMMIT;
-- 3. 배치 처리 시 잠금 범위 최소화
-- ❌ 나쁜 예: 대량 행을 한 번에 잠금
UPDATE large_table SET status = 'PROCESSED' WHERE status = 'PENDING';
-- ✅ 좋은 예: 청크 단위로 분할 처리
UPDATE large_table SET status = 'PROCESSED'
WHERE status = 'PENDING'
ORDER BY id
LIMIT 1000;
-- 반복 실행으로 전체 처리
잠금 현황 모니터링 쿼리 모음
-- 현재 대기 중인 잠금 요청 확인
SELECT
waiting_trx_id,
waiting_pid,
waiting_query,
blocking_trx_id,
blocking_pid,
blocking_query
FROM sys.innodb_lock_waits;
-- 오래 실행 중인 트랜잭션 확인 (5초 이상)
SELECT
trx_id,
trx_state,
trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS elapsed_sec,
trx_mysql_thread_id,
trx_query
FROM information_schema.innodb_trx
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 5
ORDER BY elapsed_sec DESC;
-- 특정 스레드 강제 종료 (데드락 해소용)
-- KILL <trx_mysql_thread_id>;
요약
- READ COMMITTED는 SELECT마다 새 스냅샷을 생성해 항상 최신 커밋 데이터를 읽고, REPEATABLE READ는 트랜잭션 첫 읽기 시점에 스냅샷을 고정해 일관성을 보장한다.
- **넥스트키 락(Next-Key Lock)**은 레코드 락과 갭 락의 조합으로, REPEATABLE READ에서 팬텀 리드를 방지하는 InnoDB의 핵심 잠금 메커니즘이다.
- 인텐션 락은 행 수준 잠금과 테이블 수준 잠금의 충돌을 효율적으로 감지하기 위한 메타 잠금이며, 애플리케이션에서 직접 제어하지 않고 InnoDB가 자동으로 처리한다.
- 데드락은 잠금 획득 순서를 일관되게 유지하고, 트랜잭션을 최대한 짧게 만들며, 잠금 범위를 최소화하는 것으로 대부분 예방할 수 있다.
- 비관적 잠금(
FOR UPDATE)은 경합이 높은 재고·포인트 처리에 적합하고, 낙관적 잠금(version 컬럼)은 충돌이 드문 문서 편집 등에 적합하다. SHOW ENGINE INNODB STATUS와sys.innodb_lock_waits뷰로 잠금 대기와 데드락을 실시간 진단하고,innodb_lock_wait_timeout으로 타임아웃을 서비스에 맞게 조정한다.
연습문제
-
다음 시나리오에서 격리수준이
READ COMMITTED일 때와REPEATABLE READ일 때 각각 세션 A의 두 번째 SELECT 결과 값이 무엇인지 설명하고, 그 이유를 MVCC 스냅샷 생성 시점과 연관지어 서술하세요.- 세션 A:
START TRANSACTION; SELECT price FROM items WHERE id=1;→ 결과: 1000 - 세션 B:
UPDATE items SET price=2000 WHERE id=1; COMMIT; - 세션 A:
SELECT price FROM items WHERE id=1;(두 번째)
힌트 격리수준별로 스냅샷을 언제 생성하는지 생각하세요.
- 세션 A:
-
orders테이블에서amount > 500인 행을FOR UPDATE로 잠금할 때,amount컬럼에 인덱스가 없는 경우와 있는 경우 각각 어떤 잠금이 어떤 범위에 걸리는지 설명하고, 인덱스를 생성하는 SQL을 작성하세요.힌트 인덱스 없으면 풀스캔, 있으면 레인지 스캔 — 각각 잠금 범위가 달라집니다.
-
두 트랜잭션이 데드락 없이
wallet테이블의user_id=1과user_id=2행에서 금액을 이체하는 코드를 SQL로 작성하세요. (이체: user_id=1 잔액 500 차감, user_id=2 잔액 500 증가)힌트 잠금 획득 순서를 일정하게 유지하는 것이 핵심입니다.
-
products테이블에version INT NOT NULL DEFAULT 0컬럼이 있다고 가정할 때, 낙관적 잠금으로id=5상품의stock을 1 감소시키는 UPDATE 쿼리를 작성하고,affected rows가 0인 경우의 처리 방법을 서술하세요.힌트 UPDATE의 WHERE 절에 version 조건을 포함시키고, 결과 행 수로 충돌을 감지합니다.
💡 연습문제 풀이
불러오는 중…
댓글 0
“MySQL 심화” 강좌에 대한 댓글입니다.