MySQL MVCC: consistent read와 current read의 차이
InnoDB MVCC에서 consistent read와 current read가 서로 다른 버전을 읽는 원리와 격리 수준별 운영 영향을 정리한다.
MySQL에서 같은 SELECT처럼 보이는 문장이 서로 다른 데이터를 반환하는 경우가 있다. 장시간 실행 중인 트랜잭션의 일반 조회에는 과거 값이 보이지만, 같은 트랜잭션의 SELECT ... FOR SHARE나 UPDATE에는 이미 커밋된 최신 값이 적용되는 상황이 대표적이다. 이는 캐시 불일치가 아니라 InnoDB가 consistent read(일관 읽기)와 current read(현재 읽기)를 구분하기 때문이다.
이 구분은 단순한 용어 문제가 아니다. Read View의 수명, undo log 보존, 잠금 대기, 재시도 로직, 재고·잔액 변경의 정확성에 직접 영향을 준다. 운영자는 “트랜잭션 안에서 읽었으니 모두 같은 시점의 데이터일 것”이라는 가정을 버리고, 각 SQL 문이 어떤 읽기 방식을 선택하는지 판단해야 한다.
1. 먼저 구분해야 할 두 읽기 방식
1.1 consistent read
consistent read는 특정 Read View가 허용하는 행 버전을 읽는다. 일반적인 SELECT가 이에 해당하며, 대개 레코드 잠금을 획득하지 않는다. 다른 트랜잭션이 행을 변경하고 있어도 필요한 이전 버전을 undo log에서 재구성할 수 있으므로 읽기와 쓰기의 충돌을 줄인다.
핵심 특성은 다음과 같다.
- 읽을 수 있는 트랜잭션 버전의 경계를 Read View로 판단한다.
- 최신 물리 레코드가 보이지 않으면 undo chain을 따라 과거 버전을 재구성한다.
- 일반 조회는 대상 레코드에
Srecord lock을 설정하지 않는다. REPEATABLE READ에서는 보통 첫 consistent read가 만든 Read View를 트랜잭션 동안 재사용한다.READ COMMITTED에서는 각 consistent read 문장마다 새 Read View를 만든다.
1.2 current read
current read는 SQL 실행 시점에 커밋된 최신 버전을 기준으로 읽고, 필요한 잠금을 획득한다. 변경 대상이 과거 스냅샷에 머물러 있으면 현재 행을 안전하게 수정할 수 없기 때문이다.
대표적인 current read는 다음과 같다.
SELECT ... FOR SHARESELECT ... FOR UPDATEUPDATEDELETEINSERT가 수행하는 중복 키 및 참조 무결성 검사
UPDATE와 DELETE는 “먼저 읽고 나중에 쓰는” 하나의 원자적 실행 경로를 가진다. 대상 탐색에서 최신 버전을 확인하고, 충돌하는 잠금이 있으면 기다린 다음, 조건을 다시 평가한 뒤 변경한다. 애플리케이션의 앞선 일반 SELECT 결과를 자동으로 재사용하는 것이 아니다.
2. 내부 동작: 최신 레코드와 undo chain
InnoDB 레코드에는 사용자 컬럼만 있는 것이 아니다. 내부적으로 변경 트랜잭션을 식별하는 정보와 이전 버전을 찾기 위한 undo 포인터가 연결된다. 다른 트랜잭션이 같은 행을 여러 번 변경하면 최신 레코드에서 이전 버전으로 이어지는 논리적 사슬이 만들어진다.
flowchart LR
Q[SQL 문장] --> T{읽기 종류}
T -->|일반 SELECT| C[consistent read]
T -->|FOR SHARE / FOR UPDATE / DML| R[current read]
C --> V{Read View에서<br/>최신 버전이 보이는가}
V -->|예| L[최신 레코드 반환]
V -->|아니요| U[undo chain 탐색]
U --> P[보이는 과거 버전 재구성]
R --> K[최신 버전 확인 및 잠금]
K --> W{충돌 잠금 존재}
W -->|예| S[대기 또는 timeout/deadlock]
W -->|아니요| N[조건 재평가 후 반환·변경]
Read View는 데이터의 복사본이 아니다. “어떤 트랜잭션의 변경을 볼 수 있는가”를 판정하는 가시성 규칙이다. 개념적으로는 Read View 생성 시점에 활성 상태였던 트랜잭션 ID 집합과 경계값을 이용한다. 레코드의 생성·변경 트랜잭션이 보이지 않으면 InnoDB가 undo record를 따라가며 보이는 버전을 찾는다.
따라서 오래된 Read View가 남아 있으면 purge가 불필요한 과거 버전을 즉시 제거할 수 없다. 장시간 트랜잭션이 직접 많은 행을 변경하지 않더라도 undo 보존량과 history list 증가에 영향을 줄 수 있는 이유다.
3. 격리 수준에 따라 달라지는 Read View 수명
3.1 REPEATABLE READ
InnoDB의 기본 격리 수준인 REPEATABLE READ에서 일반적인 첫 consistent read는 Read View를 생성하며, 이후 일반 조회가 이를 재사용한다. 다른 세션이 중간에 커밋해도 같은 트랜잭션의 일반 SELECT에는 이전 값이 계속 보일 수 있다.
다만 current read는 그 스냅샷에 고정되지 않는다. 최신 커밋 버전을 대상으로 잠금을 획득해야 하므로 일반 SELECT와 다른 값을 볼 수 있다. 하나의 트랜잭션 안에서 “100을 조회한 뒤 FOR SHARE로 130을 조회하고, 다시 일반 조회하면 100이 보이는” 현상도 이 원리로 설명된다.
START TRANSACTION WITH CONSISTENT SNAPSHOT은 트랜잭션 시작과 함께 Read View를 명시적으로 확보할 때 유용하다. 일반 START TRANSACTION은 트랜잭션을 시작하지만, 일관 읽기 시점은 보통 첫 consistent read까지 늦춰질 수 있다.
다음 예제는 Event Scheduler를 별도 서버 세션으로 사용해 동시 커밋을 재현한다. 검증 전용 테이블을 만들며, 운영 서버에서는 Event Scheduler를 임의로 활성화하지 말아야 한다.
DROP EVENT IF EXISTS mvcc_rr_update;
DROP TABLE IF EXISTS mvcc_account;
CREATE TABLE mvcc_account (
account_id BIGINT PRIMARY KEY,
balance INT NOT NULL
) ENGINE = InnoDB;
INSERT INTO mvcc_account VALUES (1, 100);
SET GLOBAL event_scheduler = ON;
CREATE EVENT mvcc_rr_update
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 2 SECOND
ON COMPLETION NOT PRESERVE
DO UPDATE mvcc_account
SET balance = 130
WHERE account_id = 1;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
SELECT balance AS rr_consistent_before
FROM mvcc_account
WHERE account_id = 1;
DO SLEEP(3);
SELECT balance AS rr_consistent_after_commit
FROM mvcc_account
WHERE account_id = 1;
SELECT balance AS rr_current_read
FROM mvcc_account
WHERE account_id = 1
FOR SHARE;
SELECT balance AS rr_consistent_again
FROM mvcc_account
WHERE account_id = 1;
COMMIT;
SELECT balance AS committed_value
FROM mvcc_account
WHERE account_id = 1;
실행 결과(MySQL 8.0.x, 준비 구문과 대기 출력은 줄이고 핵심 결과만 발췌):
mysql> CREATE TABLE mvcc_account (...);
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO mvcc_account VALUES (1, 100);
Query OK, 1 row affected (0.00 sec)
mysql> CREATE EVENT mvcc_rr_update ...;
Query OK, 0 rows affected (0.00 sec)
mysql> START TRANSACTION WITH CONSISTENT SNAPSHOT;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT balance AS rr_consistent_before FROM mvcc_account WHERE account_id = 1;
+----------------------+
| rr_consistent_before |
+----------------------+
| 100 |
+----------------------+
1 row in set (0.00 sec)
mysql> SELECT balance AS rr_consistent_after_commit FROM mvcc_account WHERE account_id = 1;
+----------------------------+
| rr_consistent_after_commit |
+----------------------------+
| 100 |
+----------------------------+
1 row in set (0.00 sec)
mysql> SELECT balance AS rr_current_read FROM mvcc_account WHERE account_id = 1 FOR SHARE;
+-----------------+
| rr_current_read |
+-----------------+
| 130 |
+-----------------+
1 row in set (0.00 sec)
mysql> SELECT balance AS rr_consistent_again FROM mvcc_account WHERE account_id = 1;
+---------------------+
| rr_consistent_again |
+---------------------+
| 100 |
+---------------------+
1 row in set (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT balance AS committed_value FROM mvcc_account WHERE account_id = 1;
+-----------------+
| committed_value |
+-----------------+
| 130 |
+-----------------+
1 row in set (0.00 sec)
관찰해야 할 것은 행 값 자체보다 읽기 경로다. 처음과 두 번째 일반 조회는 같은 Read View를 사용하므로 100을 반환한다. FOR SHARE는 current read이므로 이벤트가 커밋한 130을 반환한다. 이 current read가 기존 Read View를 새로 만드는 것은 아니므로 뒤이은 일반 조회는 다시 100을 반환한다. 트랜잭션을 끝낸 다음에는 130이 보인다.
3.2 READ COMMITTED
READ COMMITTED의 일반 SELECT도 consistent read이지만, Read View의 수명이 문장 단위다. 같은 트랜잭션 안에서도 두 번째 SELECT가 시작되기 전에 다른 세션이 커밋했다면 그 변경을 볼 수 있다.
UPDATE mvcc_account SET balance = 100 WHERE account_id = 1;
DROP EVENT IF EXISTS mvcc_rc_update;
CREATE EVENT mvcc_rc_update
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 2 SECOND
ON COMPLETION NOT PRESERVE
DO UPDATE mvcc_account
SET balance = 130
WHERE account_id = 1;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance AS rc_consistent_before
FROM mvcc_account
WHERE account_id = 1;
DO SLEEP(3);
SELECT balance AS rc_consistent_after_commit
FROM mvcc_account
WHERE account_id = 1;
SELECT balance AS rc_current_read
FROM mvcc_account
WHERE account_id = 1
FOR SHARE;
COMMIT;
실행 결과(MySQL 8.0.x):
mysql> UPDATE mvcc_account SET balance = 100 WHERE account_id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> DROP EVENT IF EXISTS mvcc_rc_update;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE EVENT mvcc_rc_update
-> ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 2 SECOND
-> ON COMPLETION NOT PRESERVE
-> DO UPDATE mvcc_account
-> SET balance = 130
-> WHERE account_id = 1;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT balance AS rc_consistent_before
-> FROM mvcc_account
-> WHERE account_id = 1;
+----------------------+
| rc_consistent_before |
+----------------------+
| 100 |
+----------------------+
1 row in set (0.00 sec)
mysql> DO SLEEP(3);
Query OK, 0 rows affected (3.00 sec)
mysql> SELECT balance AS rc_consistent_after_commit
-> FROM mvcc_account
-> WHERE account_id = 1;
+----------------------------+
| rc_consistent_after_commit |
+----------------------------+
| 130 |
+----------------------------+
1 row in set (0.00 sec)
mysql> SELECT balance AS rc_current_read
-> FROM mvcc_account
-> WHERE account_id = 1
-> FOR SHARE;
+-----------------+
| rc_current_read |
+-----------------+
| 130 |
+-----------------+
1 row in set (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
첫 조회는 100, 이벤트 커밋 뒤의 두 번째 일반 조회는 130을 반환한다. 두 문장 모두 consistent read이지만 서로 다른 Read View를 사용한다. READ COMMITTED는 최신 커밋 데이터에 더 빨리 접근하는 대신, 트랜잭션 전체에서 같은 조회 결과가 반복된다는 보장을 제공하지 않는다.
3.3 READ UNCOMMITTED와 SERIALIZABLE
READ UNCOMMITTED는 커밋되지 않은 변경까지 읽을 수 있어 일반적인 업무 트랜잭션에는 적합하지 않다. 단순히 “가장 빠른 격리 수준”으로 선택해서는 안 된다. 롤백될 중간 상태를 업무 판단에 사용하면 데이터 정합성을 애플리케이션에서 복구하기 어렵다.
SERIALIZABLE에서는 autocommit이 꺼진 일반 SELECT가 공유 잠금을 사용하는 방향으로 동작해 동시성이 크게 낮아질 수 있다. 분석 쿼리를 안전하게 만든다는 이유만으로 적용하면 쓰기 대기와 deadlock이 증가할 수 있다. 격리 수준은 이름이 주는 강도보다 실제 읽기·잠금 경로를 기준으로 선택해야 한다.
4. 같은 트랜잭션에서 서로 다른 값이 보여도 모순이 아닌 이유
다음 순서를 생각해 보자.
- 세션 A가
REPEATABLE READ에서 일반SELECT로 잔액100을 읽는다. - 세션 B가 잔액을
130으로 변경하고 커밋한다. - 세션 A의 일반
SELECT는 기존 Read View를 사용해 다시100을 읽는다. - 세션 A가
SELECT ... FOR UPDATE를 실행하면 최신 버전130을 잠그고 읽는다. - 세션 A가 다시 일반
SELECT를 실행하면 기존 Read View에 따라100을 읽을 수 있다.
여기서 세션 A가 하나의 “현재 시점”을 공유한다고 가정하면 결과가 모순처럼 보인다. 실제로는 SQL 문장마다 consistent read 또는 current read 경로가 선택된다. current read가 수행됐다고 해서 기존 Read View가 폐기되거나 최신 버전으로 이동하지 않는다.
이 특성 때문에 다음과 같은 코드는 주의해야 한다.
BEGIN;
SELECT balance FROM account WHERE account_id = ?; -- consistent read
-- 애플리케이션에서 새 값을 계산
UPDATE account SET balance = ? WHERE account_id = ?; -- current read 기반 변경
COMMIT;
첫 조회 이후 다른 트랜잭션이 값을 바꾸면 애플리케이션이 오래된 값을 기준으로 새 값을 계산해 덮어쓸 수 있다. MVCC가 lost update를 자동으로 모두 방지하는 것은 아니다.
재고 차감처럼 현재 값을 기준으로 원자적으로 계산할 수 있다면 다음 형태가 더 안전하다. 아래 문장은 실제 스키마에 맞춰 적용해야 하는 설계 예시이므로 검증 대상 SQL이 아닌 text로 제시한다.
UPDATE inventory
SET quantity = quantity - :requested_quantity
WHERE product_id = :product_id
AND quantity >= :requested_quantity;
영향받은 행 수가 1이면 성공, 0이면 재고 부족 또는 대상 없음으로 처리한다. 복잡한 검증이 필요하면 먼저 SELECT ... FOR UPDATE로 최신 행을 잠근 뒤 동일 트랜잭션에서 판단하고 변경한다. 잠금 범위와 접근 인덱스가 적절한지는 별도로 확인해야 한다.
5. current read의 잠금 범위는 WHERE 조건만으로 결정되지 않는다
current read가 획득하는 잠금은 논리 조건뿐 아니라 실행 계획과 격리 수준의 영향을 받는다. 적절한 인덱스 없이 넓은 범위를 탐색하면 의도보다 많은 레코드나 인덱스 구간이 잠길 수 있다.
REPEATABLE READ에서는 phantom 방지를 위해 검색 조건과 실행 계획에 따라 record lock뿐 아니라 gap lock 또는 next-key lock이 사용될 수 있다. READ COMMITTED는 일반적으로 gap lock 사용을 줄이지만, 외래 키 검사와 중복 키 검사 등 예외가 있다. 따라서 격리 수준을 바꾸면 다음을 함께 검토해야 한다.
- 같은 업무 조건에서 잠금 대상 레코드 수가 어떻게 달라지는가
EXPLAIN에서 선택한 인덱스와 접근 범위가 충분히 좁은가- lock wait timeout과 deadlock 빈도가 어떻게 변하는가
- statement-based replication과의 호환성 또는 binlog 설정 제약이 있는가
- 애플리케이션이 non-repeatable read를 허용하는가
SELECT ... FOR UPDATE를 추가하는 것만으로 안전해진다고 단정할 수 없다. 조건을 만족하는 행을 찾는 과정이 넓으면 동시성을 크게 떨어뜨릴 수 있고, 여러 트랜잭션이 서로 다른 순서로 잠그면 deadlock 가능성도 높아진다.
6. 운영 진단: 오래된 스냅샷과 잠금 대기를 분리해서 본다
MVCC 문제와 잠금 문제는 증상이 비슷할 수 있지만 관측 지점이 다르다.
- 일반 조회가 오래된 값을 본다: 격리 수준, Read View 생성 시점, 트랜잭션 경계를 확인한다.
- undo/history가 오래 유지된다: 장시간 열린 트랜잭션과 purge 지연을 확인한다.
- current read가 멈춘다:
performance_schema.data_lock_waits와data_locks에서 대기 관계를 확인한다. Lock wait timeout exceeded가 발생한다: blocker의 트랜잭션과 SQL, 잠금 범위, 트랜잭션 수명을 확인한다.- deadlock이 발생한다: 가장 최근 deadlock 정보와 애플리케이션 재시도 정책을 함께 점검한다.
다음 쿼리는 MySQL 8.0 이상에서 현재 열린 InnoDB 트랜잭션과 잠금 대기 간선을 확인하는 기본 진단이다. 문제가 없는 새 검증 인스턴스에서는 Empty set이 정상이다.
SELECT trx_id,
trx_state,
trx_started,
trx_mysql_thread_id,
trx_isolation_level,
trx_rows_modified
FROM information_schema.INNODB_TRX
ORDER BY trx_started
LIMIT 20;
SELECT requesting_engine_transaction_id,
blocking_engine_transaction_id,
requesting_thread_id,
blocking_thread_id
FROM performance_schema.data_lock_waits
LIMIT 20;
SELECT @@session.transaction_isolation AS transaction_isolation,
@@session.autocommit AS autocommit;
DROP TABLE mvcc_account;
실행 결과(MySQL 8.0.x):
mysql> SELECT trx_id,
-> trx_state,
-> trx_started,
-> trx_mysql_thread_id,
-> trx_isolation_level,
-> trx_rows_modified
-> FROM information_schema.INNODB_TRX
-> ORDER BY trx_started
-> LIMIT 20;
Empty set (0.00 sec)
mysql> SELECT requesting_engine_transaction_id,
-> blocking_engine_transaction_id,
-> requesting_thread_id,
-> blocking_thread_id
-> FROM performance_schema.data_lock_waits
-> LIMIT 20;
Empty set (0.00 sec)
mysql> SELECT @@session.transaction_isolation AS transaction_isolation,
-> @@session.autocommit AS autocommit;
+-----------------------+------------+
| transaction_isolation | autocommit |
+-----------------------+------------+
| REPEATABLE-READ | 1 |
+-----------------------+------------+
1 row in set (0.00 sec)
mysql> DROP TABLE mvcc_account;
Query OK, 0 rows affected (0.00 sec)
실제 장애 분석에서는 트랜잭션 ID만 보고 즉시 세션을 종료하지 않는다. 요청 주체, 실행 SQL, 변경 행 수, 업무 영향, 복제 또는 배치 작업 여부를 확인해야 한다. INNODB_TRX에 오래된 트랜잭션이 있다고 해서 모두 blocker인 것도 아니다. 잠금은 없지만 오래된 Read View 때문에 purge를 지연시키는 읽기 전용 트랜잭션일 수 있다.
장시간 트랜잭션을 조사할 때는 다음 질문을 순서대로 적용하는 편이 안전하다.
- 트랜잭션이 정말 아직 열려 있는가, 아니면 connection pool이 커밋을 누락했는가?
- current read 대기를 만들고 있는가, 아니면 오래된 snapshot만 유지하는가?
- 변경 행 수와 undo 보존 영향은 어느 정도인가?
- 강제 종료 시 롤백 시간이 서비스에 더 큰 부담을 주지 않는가?
- 애플리케이션이 실패한 트랜잭션 전체를 안전하게 재시도할 수 있는가?
7. 흔한 오해와 실패 패턴
7.1 “REPEATABLE READ면 모든 SQL이 같은 값을 본다”
반복 읽기 보장은 consistent read에 적용되는 개념으로 이해해야 한다. current read는 최신 버전을 대상으로 한다. 일반 SELECT와 locking read를 혼합하면 같은 트랜잭션에서도 값이 다를 수 있다.
7.2 “일반 SELECT도 읽는 동안 행을 잠근다”
InnoDB의 일반 consistent read는 보통 record lock을 획득하지 않는다. 대신 undo를 이용해 보이는 버전을 재구성한다. 다만 SERIALIZABLE, 명시적 locking clause, 메타데이터 잠금 등은 별개의 문제다.
7.3 “FOR UPDATE를 쓰면 lost update가 자동으로 해결된다”
잠금을 트랜잭션 시작 직후 획득하고, 읽기·판단·변경·커밋을 같은 트랜잭션 안에서 수행해야 한다. autocommit 경계가 잘못되거나 잠금 조회 뒤에 커밋한 다음 업데이트하면 보호 효과가 사라진다.
7.4 “오래된 값은 replica lag 때문이다”
writer 인스턴스에서도 오래된 Read View 때문에 과거 값이 보일 수 있다. 반대로 reader endpoint에서는 복제 지연이나 Aurora Replica의 가시성 지연이 원인일 수 있다. 접속 대상과 트랜잭션 격리 수준을 먼저 분리해 확인해야 한다.
7.5 “격리 수준을 READ COMMITTED로 바꾸면 undo 문제가 사라진다”
문장 단위 Read View는 오래된 snapshot의 수명을 줄이는 데 도움이 될 수 있지만, 긴 트랜잭션과 대량 변경 자체가 사라지는 것은 아니다. 격리 수준 변경은 업무 정합성, 잠금 패턴, 재시도 전략을 포함해 검증해야 한다.
8. Aurora MySQL에서의 해석
Aurora MySQL도 MySQL 호환 트랜잭션 계층에서 consistent read와 current read의 구분을 이해해야 한다. 그러나 클러스터 endpoint 선택이 관찰 결과에 추가 변수를 만든다.
- writer endpoint의 한 트랜잭션 안에서 값이 다르면 먼저 Read View와 읽기 종류를 확인한다.
- reader endpoint에서 최신 커밋이 늦게 보이면 replica 가시성 지연과 세션의 트랜잭션 상태를 함께 확인한다.
- writer와 reader를 오가는 read-after-write 경로는 단일 InnoDB 트랜잭션의 MVCC 문제와 구분한다.
- Aurora의 스토리지 구조가 다르더라도 애플리케이션이 일반 조회와 locking read의 의미를 혼동해도 된다는 뜻은 아니다.
- 장애 조치 후에는 재연결된 세션이 기존 트랜잭션을 이어가는 것이 아니므로, 실패한 트랜잭션의 전체 재시도와 멱등성을 보장해야 한다.
Aurora에서 locking read는 writer에서 수행하는 것이 기본이다. reader는 읽기 확장용이며 쓰기 트랜잭션과 동일한 잠금 기반 직렬화 지점으로 간주해서는 안 된다. “어느 endpoint에서 어떤 격리 수준으로 어떤 SQL을 실행했는가”를 로그와 추적 정보에 남기면 오래된 읽기 원인을 훨씬 빠르게 좁힐 수 있다.
9. 설계 및 장애 대응 체크리스트
SQL 설계
- 일반
SELECT - 읽은 값을 기반으로 쓸 때 원자적 조건부
UPDATE - 불가능하다면
SELECT ... FOR UPDATE
트랜잭션 경계
- connection pool 반환 전에
COMMIT또는ROLLBACK -
REPEATABLE READ
운영 진단
-
INNODB_TRX -
data_lock_waits
10. 정리
consistent read는 Read View와 undo chain을 이용해 특정 시점에 보이는 버전을 반환하고, current read는 최신 버전을 확인하면서 필요한 잠금을 획득한다. REPEATABLE READ에서는 일반 조회의 Read View가 트랜잭션 동안 유지되지만, locking read와 DML은 그 스냅샷에 고정되지 않는다. READ COMMITTED에서는 일반 조회도 문장마다 새 Read View를 사용한다.
운영에서 중요한 질문은 “이 트랜잭션의 격리 수준이 무엇인가”에 그치지 않는다. 각 SQL이 consistent read인지 current read인지, Read View는 언제 만들어졌는지, current read는 어떤 인덱스 범위를 잠그는지까지 확인해야 한다. 다음 잠금 주제에서는 record lock, gap lock, next-key lock이 current read의 검색 범위와 결합해 동시성에 어떤 영향을 주는지 더 구체적으로 살펴볼 수 있다.