LOCK=NONE이 항상 무중단이 아닌 이유: MDL과 단계별 잠금
MySQL Online DDL의 단계별 메타데이터 잠금과 후속 조회의 대기 전파를 재현하고, 제한 시간·진단·중단 기준을 정리한다.
ALTER TABLE ... LOCK=NONE을 지정했는데 서비스 조회가 멈추는 상황은 모순이 아니다. 이 옵션은 지원되는 Online DDL의 주요 작업 중 동시 조회와 DML을 허용하라는 요구다. 테이블 정의를 보호하는 메타데이터 잠금(Metadata Lock, MDL)을 없애거나, 모든 단계의 대기 시간을 0으로 만드는 지시가 아니다.
운영에서 더 위험한 것은 DDL 한 연결의 대기보다 그 뒤로 유입되는 요청의 대기다. 끝나지 않은 트랜잭션이 테이블 정의를 붙잡고, DDL이 배타 MDL을 요청하면, 나중에 들어온 단순 조회까지 줄을 설 수 있다. 이 글은 알고리즘 선택 자체보다 잠금 보유자 → 대기 중인 DDL → 후속 업무 요청이라는 장애 경로를 설명한다.
기본 대상은 MySQL 8.0/8.4의 InnoDB다. SQL과 동시 세션 실험은 폐기용 Community MySQL 8.0에서 검증한다. 대용량 처리량, 실제 운영 트래픽, Aurora 클러스터에서의 지연을 측정한 결과는 아니다.
1. 행 잠금과 테이블 정의 잠금은 다른 문제다
InnoDB의 consistent read는 보통 읽은 레코드에 공유 행 잠금을 걸지 않는다. 그러나 SQL을 실행하려면 어느 열과 인덱스가 있는지, 테이블이 어떤 구조인지 일관되게 알아야 한다. 일반 SELECT도 이 정의를 보호하는 MDL을 획득한다.
명시적 트랜잭션에서 테이블을 읽었다면, 문장 실행이 끝났더라도 MDL은 대개 트랜잭션이 끝날 때까지 유지된다. 따라서 결과를 이미 받은 연결이 Sleep 상태로 대기하고 있어도 DDL의 차단자일 수 있다. 반대로 트랜잭션을 시작했다는 사실만으로 모든 테이블의 MDL을 보유하는 것은 아니다. 실제 접근한 객체와 잠금 행을 확인해야 한다.
| 구분 | InnoDB 데이터 잠금 | 메타데이터 잠금 |
|---|---|---|
| 보호 대상 | 레코드, 갭 등 데이터 접근 | 테이블 정의 등 데이터베이스 객체 |
| 대표 관측점 | performance_schema.data_locks, data_lock_waits |
performance_schema.metadata_locks |
| 일반 조회 | MVCC 읽기는 보통 행 잠금 없이 수행 | 테이블 접근에 MDL 필요 |
| 대표 대기 한도 | innodb_lock_wait_timeout |
lock_wait_timeout |
| 주의점 | 행 충돌이 없어도 다른 대기는 존재 | 조회를 끝낸 트랜잭션도 정의 변경을 차단 가능 |
data_lock_waits가 비어 있다는 이유로 잠금 장애가 아니라고 결론 내리면 안 된다. DDL 문제에서는 먼저 MDL과 Waiting for table metadata lock 상태를 확인한다.
2. Online DDL의 세 단계와 짧은 잠금의 함정
MySQL 공식 문서는 Online DDL을 초기화, 실행, 테이블 정의 커밋의 세 단계로 설명한다. 구체적인 잠금 획득·승격 시점은 작업 종류와 알고리즘에 따라 달라지므로 모든 ALTER TABLE이 정확히 같은 궤적을 밟는다고 해석하지 않는다.
| 단계 | 수행하는 일 | MDL 관점의 운영 위험 |
|---|---|---|
| 초기화 | 엔진 기능, 변경 항목, ALGORITHM과 LOCK의 가능 여부 판단 | 현재 정의를 보호하는 shared-upgradable 잠금 획득 |
| 준비·실행 | 필요한 준비 후 인덱스 구축 또는 데이터 재구성 등 수행 | 작업에 따라 준비 시 짧은 배타 잠금 필요; 주요 실행 구간에서는 동시 DML 허용 가능 |
| 정의 커밋 | 이전 정의를 교체하고 새 정의 확정 | 배타 MDL로 승격해야 하며 기존 트랜잭션 종료를 기다릴 수 있음 |
획득한 뒤 보유하는 시간이 짧다는 설명과, 획득하기까지 기다리는 시간이 짧다는 보장은 다르다. 긴 트랜잭션이 있으면 아주 짧게 사용할 배타 잠금을 오래 기다린다. 실행 중 새로 생긴 트랜잭션도 종료 단계에 영향을 줄 수 있어, 시작 전 한 번의 점검만으로 전체 작업이 안전해지지는 않는다.
flowchart TD
A[트랜잭션 A가 테이블 조회] --> B[공유 MDL 유지 후 유휴 상태]
C[Online DDL B 실행] --> D[배타 MDL 요청]
B --> E[B의 잠금 요청 대기]
D --> E
E --> F[후속 조회 C도 MDL 대기 가능]
F --> G[연결 풀 고갈과 응답 지연]
E --> H{운영 판단}
H --> I[대기 중 DDL 취소]
H --> J[원인 트랜잭션 정상 종료]
I --> K[대기열 해소 여부 확인]
J --> K
MDL 스케줄링은 단순 선착순이 아니다. 일반적으로 쓰기 성격의 요청에 높은 우선순위가 있으며, 대기 중인 배타 요청 때문에 뒤의 읽기 요청이 진행하지 못할 수 있다. 모든 잠금 모드 조합에 같은 규칙을 적용하거나, 우선순위 관련 전역 변수를 즉석에서 바꾸는 것을 해결책으로 삼지 않는다.
LOCK과 ALGORITHM을 별도 계약으로 읽는다
ALGORITHM=INPLACE는 서버의 전통적인 COPY 방식과 구별되는 알고리즘 요구다. 테이블 재구축이 없다는 뜻은 아니다.LOCK=NONE은 해당 작업에서 동시 조회·DML을 허용할 수 있어야 한다는 요구다. 지원하지 않으면 더 강한 잠금으로 조용히 대체하는 대신 오류를 반환하도록 하는 안전장치로 활용한다.ALGORITHM=INSTANT도 정의 변경을 위한 MDL은 필요하다. Instant 작업에는LOCK을 생략하며, 지정하려면 지원되는DEFAULT조건을 따른다.LOCK=NONE을 모든 DDL에 기계적으로 붙이지 않는다.- 동시 DML 허용은 처리량 보장이 아니다. 정렬, 임시 공간, 페이지 읽기·쓰기와 동시 변경의 기록·적용 때문에 업무 지연이 늘 수 있다.
3. 잠금 대기 한도와 관측 준비
아래 예제의 lock_wait_timeout=5는 검증용 정책이다. 운영 표준값을 뜻하지 않으며, 배포 전용 연결에서 업무의 응답 시간 예산에 맞추어 결정한다. MDL을 여러 번 획득하는 작업에서 이 값은 DDL 전체 실행 시간의 상한이 아니다. 서버 밖에서도 작업 경과 시간과 서비스 지표를 감시해야 한다.
SELECT VERSION() AS mysql_version;
SET SESSION lock_wait_timeout = 5;
SELECT @@session.lock_wait_timeout AS mdl_wait_seconds,
@@session.innodb_lock_wait_timeout AS row_wait_seconds;
SELECT NAME, ENABLED, TIMED
FROM performance_schema.setup_instruments
WHERE NAME = 'wait/lock/metadata/sql/mdl';
실행 결과(MySQL 8.0.x):
mysql> SELECT VERSION() AS mysql_version;
+---------------+
| mysql_version |
+---------------+
| 8.0.46 |
+---------------+
1 row in set (0.00 sec)
mysql> SET SESSION lock_wait_timeout = 5;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@session.lock_wait_timeout AS mdl_wait_seconds,
-> @@session.innodb_lock_wait_timeout AS row_wait_seconds;
+------------------+------------------+
| mdl_wait_seconds | row_wait_seconds |
+------------------+------------------+
| 5 | 50 |
+------------------+------------------+
1 row in set (0.00 sec)
mysql> SELECT NAME, ENABLED, TIMED
-> FROM performance_schema.setup_instruments
-> WHERE NAME = 'wait/lock/metadata/sql/mdl';
+----------------------------+---------+-------+
| NAME | ENABLED | TIMED |
+----------------------------+---------+-------+
| wait/lock/metadata/sql/mdl | YES | YES |
+----------------------------+---------+-------+
1 row in set (0.00 sec)
innodb_lock_wait_timeout을 낮춰도 MDL 대기를 직접 제한하지 못한다. lock_wait_timeout은 세션 설정이므로, 배포 도구가 ALTER를 실행하는 실제 연결에 적용했는지 확인한다. max_execution_time 역시 일반적인 ALTER 전체 제한 시간의 대체 수단으로 사용하지 않는다.
MDL instrumentation이 비활성이라면 관측 결과가 비어 있다는 것만으로 잠금이 없다고 판단할 수 없다. 대상 버전, Performance Schema 활성화, 계정의 조회 권한을 확인하고 계측 변경은 별도 승인 범위로 취급한다.
4. 조회가 끝나도 MDL이 남는 축소 실험
빈 테스트 스키마에서 다음 블록을 실행한다. mdl_hold_demo는 실험 전용 테이블이며 운영 테이블에 대입하지 않는다. 조회 직후 같은 트랜잭션 안에서 자신의 테이블 MDL을 관측하고, COMMIT 후 해제 여부를 확인한다.
CREATE TABLE mdl_hold_demo (id INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO mdl_hold_demo VALUES (1);
START TRANSACTION;
SELECT id FROM mdl_hold_demo;
SELECT m.LOCK_TYPE, m.LOCK_DURATION, m.LOCK_STATUS
FROM performance_schema.metadata_locks AS m
JOIN performance_schema.threads AS t
ON t.THREAD_ID = m.OWNER_THREAD_ID
WHERE t.PROCESSLIST_ID = CONNECTION_ID()
AND m.OBJECT_TYPE = 'TABLE'
AND m.OBJECT_SCHEMA = DATABASE()
AND m.OBJECT_NAME = 'mdl_hold_demo';
COMMIT;
SELECT COUNT(*) AS remaining_table_mdl
FROM performance_schema.metadata_locks AS m
JOIN performance_schema.threads AS t
ON t.THREAD_ID = m.OWNER_THREAD_ID
WHERE t.PROCESSLIST_ID = CONNECTION_ID()
AND m.OBJECT_TYPE = 'TABLE'
AND m.OBJECT_SCHEMA = DATABASE()
AND m.OBJECT_NAME = 'mdl_hold_demo';
DROP TABLE mdl_hold_demo;
실행 결과(MySQL 8.0.x):
mysql> CREATE TABLE mdl_hold_demo (id INT PRIMARY KEY) ENGINE=InnoDB;
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO mdl_hold_demo VALUES (1);
Query OK, 1 row affected (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT id FROM mdl_hold_demo;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
mysql> SELECT m.LOCK_TYPE, m.LOCK_DURATION, m.LOCK_STATUS
-> FROM performance_schema.metadata_locks AS m
-> JOIN performance_schema.threads AS t
-> ON t.THREAD_ID = m.OWNER_THREAD_ID
-> WHERE t.PROCESSLIST_ID = CONNECTION_ID()
-> AND m.OBJECT_TYPE = 'TABLE'
-> AND m.OBJECT_SCHEMA = DATABASE()
-> AND m.OBJECT_NAME = 'mdl_hold_demo';
+-------------+---------------+-------------+
| LOCK_TYPE | LOCK_DURATION | LOCK_STATUS |
+-------------+---------------+-------------+
| SHARED_READ | TRANSACTION | GRANTED |
+-------------+---------------+-------------+
1 row in set (0.01 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT COUNT(*) AS remaining_table_mdl
-> FROM performance_schema.metadata_locks AS m
-> JOIN performance_schema.threads AS t
-> ON t.THREAD_ID = m.OWNER_THREAD_ID
-> WHERE t.PROCESSLIST_ID = CONNECTION_ID()
-> AND m.OBJECT_TYPE = 'TABLE'
-> AND m.OBJECT_SCHEMA = DATABASE()
-> AND m.OBJECT_NAME = 'mdl_hold_demo';
+---------------------+
| remaining_table_mdl |
+---------------------+
| 0 |
+---------------------+
1 row in set (0.00 sec)
mysql> DROP TABLE mdl_hold_demo;
Query OK, 0 rows affected (0.01 sec)
SHARED_READ / TRANSACTION / GRANTED는 읽기용 정의 잠금을 트랜잭션 수명 동안 보유한다는 의미다. COMMIT 뒤 남은 잠금 수가 0인지 함께 확인해야 문장 종료와 트랜잭션 종료의 차이가 드러난다. 이 예제는 레코드 공유 잠금을 관측한 것이 아니다.
5. 세 연결로 보는 대기 전파와 DDL 취소
다음 실험은 단일 연결 SQL 검증과 별도로 서로 다른 클라이언트 연결을 유지하여 실행한다. 관측 연결도 따로 사용한다. 두 행의 작은 InnoDB 테이블 mdl_queue_demo(id, amount)를 준비하고, 다음 순서를 지킨다. 아래는 동시 실행 순서를 나타낸 것이며 한 연결에 순서대로 붙여 넣는 스크립트가 아니다.
연결 A: START TRANSACTION;
SELECT * FROM mdl_queue_demo;
결과를 받은 뒤에도 COMMIT하지 않고 연결 유지
연결 B: SET SESSION lock_wait_timeout = 30;
ALTER TABLE mdl_queue_demo
ADD INDEX idx_amount(amount), ALGORITHM=INPLACE, LOCK=NONE;
관측 연결: B의 EXCLUSIVE / PENDING 확인
연결 C: SELECT COUNT(*) FROM mdl_queue_demo;
관측 연결: C의 SHARED_READ / PENDING 확인
대기 중인 B의 연결 ID를 확인한 뒤 B의 문장만 취소
연결 A: 대기 해소를 관측한 뒤 COMMIT;
배포 취소를 검증하는 첫 실험에서는 A를 그대로 둔 상태에서 B의 대기 중인 문장에 KILL QUERY를 적용한다. C가 완료되고 A의 SHARED_READ가 여전히 남아 있다면, C가 반드시 A의 종료만을 기다린 것이 아니라 중간의 DDL 배타 요청 때문에 대기한 상황임을 확인할 수 있다. 이후 A를 정상 종료한다.
두 번째 실험에서는 다시 A를 열고 B의 세션 MDL 대기 한도를 3초로 둔다. B가 오류 1205로 종료되고 인덱스가 생성되지 않았는지 확인한다. A가 커밋한 후 같은 ALTER를 다시 실행하여 실제 인덱스가 만들어지고 기존 두 행이 유지되는지 확인한다.
실제 동시 세션 검증 결과
MySQL 8.0.46에서 다음 잠금 행을 관측했다. 연결 ID는 해당 실행에서의 값이며 재실행하면 달라진다. 동일 테이블의 MDL 결과에서 핵심 열만 발췌했다.
| 연결 | 상태 | LOCK_TYPE | LOCK_DURATION | LOCK_STATUS |
|---|---|---|---|---|
| A: 16 | Sleep | SHARED_READ | TRANSACTION | GRANTED |
| B: 18 | Waiting for table metadata lock | SHARED_UPGRADABLE | TRANSACTION | GRANTED |
| B: 18 | Waiting for table metadata lock | EXCLUSIVE | TRANSACTION | PENDING |
| C: 20 | Waiting for table metadata lock | SHARED_READ | TRANSACTION | PENDING |
B의 문장을 취소했을 때 실제 반환된 오류는 다음과 같다. 결과의 클라이언트 행 번호 표시는 생략했다.
ERROR 1317 (70100): Query execution was interrupted
C의 SELECT COUNT(*)는 2를 반환했고, 이 시점에 A의 SHARED_READ / GRANTED는 남아 있었다. 같은 테이블의 PENDING 행은 없어졌다. 즉, A를 강제 종료하지 않고 중간 DDL 요청을 제거하여 후속 조회를 진행시킨 경우다.
두 번째 실험에서는 B가 다음 오류로 종료되었다.
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
오류 직후 information_schema.STATISTICS에서 idx_amount의 행 수는 0이었다. A를 커밋한 뒤 동일 ALTER를 재실행하자 idx_amount / amount와 PRIMARY / id가 확인되었고, 전체 행은 (1, 100), (2, 200)으로 유지되었다. 대기 관측뿐 아니라 취소·시간 초과의 비정상 종료, 인덱스 미생성, 재실행 후 정의·데이터 일치를 각각 검사했다.
이 축소 시험은 배타 MDL 요청과 후속 조회의 대기, DDL 문장 취소에 따른 대기열 해소, MDL 제한 시간 실패 후 재실행을 검증한다. 작은 테이블의 잠금 관측만으로 배타 요청이 준비 단계인지 정의 커밋 단계인지 확정하지 않는다. 단계별 설명은 공식 동작 모델이며, 종료 단계의 대용량 작업을 별도로 지연시켜 계측한 시험은 아니다.
6. 운영 진단은 보유자와 대기자를 함께 본다
다음 쿼리는 현재 선택한 스키마에서 부여되었거나 대기 중인 테이블 MDL과 연결 상태를 보여준다. 운영에서는 정확한 스키마·테이블 조건을 추가한다. PROCESSLIST_ID는 클라이언트 연결 식별자이고 OWNER_THREAD_ID는 Performance Schema 내부 스레드 식별자다. 종료 대상에는 두 값을 혼동해서 사용하지 않는다.
SELECT m.OBJECT_NAME,
t.PROCESSLIST_ID AS connection_id,
t.PROCESSLIST_COMMAND AS command_name,
t.PROCESSLIST_STATE AS state_name,
m.LOCK_TYPE, m.LOCK_DURATION, m.LOCK_STATUS
FROM performance_schema.metadata_locks AS m
LEFT JOIN performance_schema.threads AS t
ON t.THREAD_ID = m.OWNER_THREAD_ID
WHERE m.OBJECT_TYPE = 'TABLE'
AND m.OBJECT_SCHEMA = DATABASE()
AND m.LOCK_STATUS IN ('GRANTED', 'PENDING')
ORDER BY m.OBJECT_NAME, m.LOCK_STATUS, t.PROCESSLIST_ID
LIMIT 30;
실행 결과(MySQL 8.0.x):
mysql> SELECT m.OBJECT_NAME,
-> t.PROCESSLIST_ID AS connection_id,
-> t.PROCESSLIST_COMMAND AS command_name,
-> t.PROCESSLIST_STATE AS state_name,
-> m.LOCK_TYPE, m.LOCK_DURATION, m.LOCK_STATUS
-> FROM performance_schema.metadata_locks AS m
-> LEFT JOIN performance_schema.threads AS t
-> ON t.THREAD_ID = m.OWNER_THREAD_ID
-> WHERE m.OBJECT_TYPE = 'TABLE'
-> AND m.OBJECT_SCHEMA = DATABASE()
-> AND m.LOCK_STATUS IN ('GRANTED', 'PENDING')
-> ORDER BY m.OBJECT_NAME, m.LOCK_STATUS, t.PROCESSLIST_ID
-> LIMIT 30;
Empty set (0.00 sec)
일반 검증 연결에서는 실험 테이블과 트랜잭션을 이미 정리했으므로 Empty set이 정상이다. 실제 대기 여부는 앞 절의 동시 세션 실험에서 별도로 확인했다. 결과가 30행에 도달하면 잠금이 30개뿐이라고 해석하지 말고 대상 객체를 좁혀 다시 조회한다.
같은 객체의 모든 GRANTED 행이 모든 PENDING 행의 직접 차단자인 것은 아니다. 호환 가능한 잠금도 함께 표시된다. sys.schema_table_lock_waits가 제공하는 대기·차단 연결 쌍을 보조 자료로 사용하고, 원본 잠금 모드와 실제 트랜잭션 상태를 함께 확인한다. 잠금 스냅샷은 읽는 동안에도 변할 수 있다.
Sleep은 트랜잭션이 없다는 뜻이 아니다. 현재 SQL이 비어 있거나 마지막 문장만 남아 있을 수 있으므로 애플리케이션 요청 식별자, 트랜잭션 시작 시각, 마지막 실행 이력을 결합한다. 종료 전에 누가 어떤 업무를 수행하던 연결인지 확인하는 절차가 필요하다.
7. 차단 원인 제거 후 실제 DDL 성공 확인
아래 블록은 차단 트랜잭션이 없는 별도 축소 예제다. 명시한 알고리즘과 잠금 조건으로 인덱스를 추가한 뒤 메타데이터와 기존 행을 확인한다. DDL 성공 메시지만으로 원하는 정의가 적용되었다고 결론 내리지 않는다.
CREATE TABLE mdl_alter_demo (
id INT PRIMARY KEY,
amount INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO mdl_alter_demo VALUES (1, 100), (2, 200);
SET SESSION lock_wait_timeout = 5;
ALTER TABLE mdl_alter_demo
ADD INDEX idx_amount(amount), ALGORITHM=INPLACE, LOCK=NONE;
SELECT INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'mdl_alter_demo'
ORDER BY INDEX_NAME, SEQ_IN_INDEX;
SELECT id, amount FROM mdl_alter_demo ORDER BY id;
DROP TABLE mdl_alter_demo;
실행 결과(MySQL 8.0.x):
mysql> CREATE TABLE mdl_alter_demo (
-> id INT PRIMARY KEY,
-> amount INT NOT NULL
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO mdl_alter_demo VALUES (1, 100), (2, 200);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> SET SESSION lock_wait_timeout = 5;
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE mdl_alter_demo
-> ADD INDEX idx_amount(amount), ALGORITHM=INPLACE, LOCK=NONE;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX
-> FROM information_schema.STATISTICS
-> WHERE TABLE_SCHEMA = DATABASE()
-> AND TABLE_NAME = 'mdl_alter_demo'
-> ORDER BY INDEX_NAME, SEQ_IN_INDEX;
+------------+-------------+--------------+
| INDEX_NAME | COLUMN_NAME | SEQ_IN_INDEX |
+------------+-------------+--------------+
| idx_amount | amount | 1 |
| PRIMARY | id | 1 |
+------------+-------------+--------------+
2 rows in set (0.00 sec)
mysql> SELECT id, amount FROM mdl_alter_demo ORDER BY id;
+----+--------+
| id | amount |
+----+--------+
| 1 | 100 |
| 2 | 200 |
+----+--------+
2 rows in set (0.00 sec)
mysql> DROP TABLE mdl_alter_demo;
Query OK, 0 rows affected (0.00 sec)
이 결과는 두 행의 기능 시험이다. 같은 ALTER가 큰 운영 테이블에서 같은 시간에 끝난다거나, 동시 쓰기가 많을 때도 오류 없이 완료된다는 의미가 아니다. 온라인 변경 로그의 한도, 임시 저장 공간, CPU·I/O 경쟁은 MDL과 별도의 중단 조건이다.
8. 장애가 시작됐을 때의 판단 순서
8.1 무조건 차단자를 종료하지 않는다
업무 요청이 이미 밀리고 있다면 새 배포 시도를 먼저 중지한다. 아직 잠금을 기다리는 DDL을 취소해 대기열의 원인을 제거하는 쪽이 장기 업무 트랜잭션을 강제 종료하는 것보다 영향이 작을 수 있다. 다만 실행 중인 DDL 취소에는 정리 작업이 필요할 수 있으므로, 취소 요청 접수와 잠금 해제 완료를 같은 시점으로 보지 않는다.
차단자가 유휴 트랜잭션이면 소유 애플리케이션에 정상 COMMIT 또는 ROLLBACK을 요청하는 것이 우선이다. 단순 KILL QUERY는 현재 문장만 중단하며 열린 트랜잭션을 반드시 종료하지 않는다. 특히 Sleep 연결에는 끝낼 실행 문장이 없을 수 있다. 연결 종료를 선택할 경우 미커밋 변경의 롤백 비용과 업무 재시도 영향을 평가한다. 이 글의 검증에서 사용한 DDL 취소를 일반 업무 연결 종료 지침으로 확대하지 않는다.
8.2 종료 직전에도 지표를 본다
긴 Online DDL의 주요 작업이 진행된 뒤 종료 단계에서 잠금을 못 잡는 경우가 있다. 작업 진행률이 높다고 업무 요청의 장시간 대기를 허용하면 안 된다. DDL 경과 시간과 함께 대기 중인 연결 수, 연결 풀 여유, 지연 상위 구간, 오류율, CPU·I/O, 복제 지연을 감시한다. Performance Schema stage 계측이 제공하는 진행 정보도 전체 완료 시각을 확정하는 보장은 아니다.
8.3 실패와 연결 단절을 구분한다
서버가 반환한 잠금 시간 초과와 클라이언트 연결 단절은 다르다. 연결이 끊겨 최종 응답을 못 받았다면 성공 여부를 새 연결에서 정의 조회로 확인한다. 즉시 같은 ALTER를 다시 보내면 중복 인덱스 오류나 추가 경쟁이 발생할 수 있다. DDL은 암묵적 커밋을 동반할 수 있으므로 업무 트랜잭션 안에 넣고 ROLLBACK으로 배포를 되돌리는 방식도 피한다.
재시도는 원인 트랜잭션과 대기열이 사라진 뒤 제한된 횟수로 수행한다. 짧은 간격의 무제한 재시도는 매번 새로운 배타 요청을 만들어 서비스 회복을 방해할 수 있다.
9. Aurora MySQL에서도 확인할 경계
Aurora의 분산 스토리지가 SQL 계층의 테이블 정의 잠금을 제거하는 것은 아니다. Aurora MySQL 버전 3과 8.4의 Instant DDL은 버전 2의 Fast DDL과 구분해야 하며, 정확한 엔진 버전과 변경 항목의 지원 조건을 확인한다.
DDL은 writer를 대상으로 계획하고, writer의 잠금·트랜잭션과 애플리케이션 지표를 함께 관찰한다. reader가 있다는 이유로 writer 쓰기의 MDL 대기나 연결 풀 고갈이 해결되지는 않는다. 반대로 reader 세션의 잠금을 로컬 writer의 차단자로 자동 간주해서도 안 된다. 진단 연결이 어느 인스턴스에 붙었는지부터 확인한다.
관리형 환경에서는 Performance Schema 계측과 종료 권한·절차가 Community의 root 연결과 다를 수 있다. Aurora의 지원되는 관리 절차를 사용하고, 복제 지연과 reader 영향도 별도 검증한다. 이 글은 AWS에서 MDL 장애나 reader 동작을 재현했다고 주장하지 않는다.
10. 실행 전·실행 중·완료 후 점검표
실행 전
실행 중
-
PENDING
완료 후
정리
LOCK=NONE은 동시성을 요구하는 유용한 안전장치지만 무중단 인증서가 아니다. Online DDL은 테이블 정의를 안전하게 교체해야 하며, 짧은 배타 MDL도 긴 트랜잭션과 만나면 서비스 전체의 대기열을 만들 수 있다. 알고리즘 선택에 더해 트랜잭션 수명 관리, MDL 관측, 세션 대기 한도, 서비스 기준의 중단·재시도 절차가 있어야 변경 작업의 위험을 통제할 수 있다. 이후 스키마 변경 도구를 평가할 때도 이 기준을 적용하면 된다. 데이터 복사를 외부 도구로 옮겨도 최종 정의 전환의 잠금 문제를 검토해야 한다.