카테고리 : MySQL/기술노트

wait/lock/metadata/sql/mdl로 MDL 병목 관찰하기

MySQL Performance Schema에서 Metadata Lock의 대기 관계를 식별하고 DDL 지연과 운영 장애를 진단하는 방법을 정리한다.

저자: MySQL 기술 노트 작성: 2026.08.19 약 11분 6,525자
다운로드

MySQL에서 ALTER TABLE이 오래 멈춰 있을 때 원인은 DDL 자체의 연산량이 아닐 수 있다. 먼저 시작된 트랜잭션이 테이블의 Metadata Lock(MDL)을 계속 보유하면, DDL은 실행 단계에 진입하지 못한 채 배타적 MDL을 기다린다. 더 까다로운 점은 대기 중인 DDL 뒤로 평범한 조회까지 줄지어 대기하여 장애 범위가 빠르게 넓어질 수 있다는 사실이다.

wait/lock/metadata/sql/mdl은 이러한 MDL의 획득과 대기를 Performance Schema에 노출하는 핵심 instrument다. 이 글에서는 MDL의 내부 역할, performance_schema.metadata_locks를 읽는 방법, 재현 가능한 대기 시나리오, 운영 진단 순서와 Aurora MySQL에서의 주의점을 다룬다.

1. MDL은 무엇을 보호하는가

MDL은 InnoDB의 record lock이나 gap lock과 목적이 다르다. InnoDB 잠금은 주로 행 데이터의 동시 변경과 트랜잭션 격리를 보호하지만, MDL은 SQL 계층의 객체 정의가 문장 실행 중에 갑자기 바뀌지 않도록 보호한다.

예를 들어 한 세션이 orders를 읽는 동안 다른 세션이 같은 테이블을 삭제하거나 컬럼 정의를 바꾸면, 이미 해석된 실행 계획과 실제 객체 정의가 어긋날 수 있다. MySQL 서버는 조회 세션에 공유 계열 MDL을 부여하고, DDL에는 배타 계열 MDL을 요구하여 이 충돌을 직렬화한다.

flowchart LR
    A[세션 A: 트랜잭션 시작] --> B[orders 조회]
    B --> C[SHARED_READ MDL 보유]
    D[세션 B: ALTER TABLE] --> E[EXCLUSIVE MDL 요청]
    C --> F{호환 가능한가?}
    E --> F
    F -->|아니오| G[세션 B: PENDING]
    G --> H[세션 A COMMIT 또는 ROLLBACK]
    H --> I[EXCLUSIVE MDL 획득]
    I --> J[DDL 실행]

MDL의 중요한 특성은 다음과 같다.

  • 객체 단위로 관리된다. 테이블뿐 아니라 schema, stored program, tablespace 등에도 적용될 수 있다.
  • 문장 또는 트랜잭션 수명을 가질 수 있다. 트랜잭션에서 접근한 테이블의 MDL은 일반적으로 트랜잭션 종료까지 유지된다.
  • SQL 계층에서 관리되므로 InnoDB 행 잠금만 조회해서는 보이지 않는다.
  • DDL이 대기열에 들어가면 이후의 공유 요청도 공정성 및 우선순위 규칙에 따라 대기할 수 있다.

따라서 “DDL이 아무 일도 하지 않는데 CPU도 낮다”는 현상은 비정상이 아니라, 필요한 MDL을 아직 얻지 못한 상태일 수 있다.

2. wait/lock/metadata/sql/mdl instrument의 역할

Performance Schema의 instrument 이름은 관측 대상의 계층을 나타낸다.

  • wait: 대기 이벤트 계열
  • lock: 잠금 획득 대기
  • metadata: 데이터가 아니라 객체 메타데이터
  • sql/mdl: SQL 계층의 Metadata Lock 구현

이 instrument가 활성화되면 현재 보유 중이거나 요청 중인 MDL을 performance_schema.metadata_locks에서 관찰할 수 있다. 먼저 대상 버전과 활성화 상태를 확인한다.

SELECT VERSION() AS mysql_version;

SELECT NAME, ENABLED, TIMED
FROM performance_schema.setup_instruments
WHERE NAME = 'wait/lock/metadata/sql/mdl';

SHOW TABLES FROM performance_schema LIKE 'metadata_locks';

실행 결과(MySQL 8.0.x):

mysql> SELECT VERSION() AS mysql_version;

+---------------+
| mysql_version |
+---------------+
| 8.0.46        |
+---------------+
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)

mysql> SHOW TABLES FROM performance_schema LIKE 'metadata_locks';

+-----------------------------------------------+
| Tables_in_performance_schema (metadata_locks) |
+-----------------------------------------------+
| metadata_locks                                |
+-----------------------------------------------+
1 row in set (0.00 sec)

ENABLED='YES'이면 이벤트를 수집한다. TIMED='YES'는 해당 instrument의 시간 측정을 허용한다. 다만 MDL 장애의 핵심 증거는 대개 metadata_locksGRANTEDPENDING 관계, 그리고 요청 세션의 SQL이다. 시간 집계 하나만으로 blocker를 특정할 수는 없다.

운영 환경에서 비활성 상태라면 다음과 같이 활성화할 수 있다. Performance Schema 설정 변경은 서버 재시작 후 유지된다고 가정해서는 안 되며, 배포 표준과 parameter 관리 정책에 반영해야 한다.

UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME = 'wait/lock/metadata/sql/mdl';

SELECT NAME, ENABLED, TIMED
FROM performance_schema.setup_instruments
WHERE NAME = 'wait/lock/metadata/sql/mdl';

실행 결과(MySQL 8.0.x):

mysql> UPDATE performance_schema.setup_instruments
    -> SET ENABLED = 'YES', TIMED = 'YES'
    -> WHERE NAME = 'wait/lock/metadata/sql/mdl';

Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0

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)

관측 기능을 켜는 작업과 문제를 해결하는 작업은 구분해야 한다. instrument 활성화는 잠금을 해제하지 않으며, 이미 누적된 대기열을 자동으로 정리하지도 않는다.

3. metadata_locks의 핵심 열 읽기

performance_schema.metadata_locks는 MDL 요청을 한 행으로 표현한다. 실무에서 먼저 볼 열은 다음과 같다.

의미 운영 해석
OBJECT_TYPE 잠금 대상 객체 종류 TABLE, SCHEMA 등 범위를 구분한다.
OBJECT_SCHEMA schema 이름 동일한 테이블명이 여러 schema에 있을 때 구분한다.
OBJECT_NAME 객체 이름 장애 대상 DDL과 직접 연결한다.
LOCK_TYPE 요청한 MDL 모드 공유 계열인지 배타 계열인지 확인한다.
LOCK_DURATION 잠금 수명 STATEMENT, TRANSACTION, EXPLICIT를 구분한다.
LOCK_STATUS 획득 상태 GRANTED는 보유, PENDING은 대기를 뜻한다.
OWNER_THREAD_ID Performance Schema thread ID performance_schema.threads와 연결하는 키다.
OWNER_EVENT_ID 요청 이벤트 ID statement event와의 상관관계에 활용한다.

LOCK_TYPE 이름만 보고 blocker를 단정해서는 안 된다. 같은 객체의 GRANTED 행과 PENDING 행을 함께 모으고, 각 행의 OWNER_THREAD_ID를 세션과 SQL에 연결해야 한다. 또한 schema-level lock과 table-level lock이 동시에 관측될 수 있으므로 객체 이름만 필터링하면 필요한 상위 객체 잠금을 놓칠 수 있다.

4. MDL 대기 재현과 관찰

다음 예제는 Event Scheduler를 두 번째 서버 세션처럼 사용한다. 현재 세션이 mdl_demo를 읽은 뒤 트랜잭션을 끝내지 않고, 예약된 ALTER TABLE이 배타적 MDL을 요청하도록 만든다. SLEEP() 중 DDL 세션이 대기열에 들어가며, 이어지는 조회에서 GRANTEDPENDING을 함께 확인한다.

이 예제는 전용 검증 인스턴스를 위한 것이다. 운영 서버에서 Event Scheduler를 임의로 활성화하거나 동일한 DDL을 실행하지 않는다.

SET GLOBAL event_scheduler = ON;
DROP EVENT IF EXISTS mdl_wait_event;
DROP TABLE IF EXISTS mdl_demo;

CREATE TABLE mdl_demo (
    id BIGINT NOT NULL PRIMARY KEY,
    payload VARCHAR(100) NOT NULL
) ENGINE = InnoDB;

INSERT INTO mdl_demo VALUES (1, 'metadata lock demo');

CREATE EVENT mdl_wait_event
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 2 SECOND
ON COMPLETION PRESERVE
DO ALTER TABLE mdl_demo ADD COLUMN note VARCHAR(30) NULL;

START TRANSACTION;
SELECT * FROM mdl_demo WHERE id = 1;
DO SLEEP(4);

SELECT ml.OBJECT_TYPE,
       ml.OBJECT_SCHEMA,
       ml.OBJECT_NAME,
       ml.LOCK_TYPE,
       ml.LOCK_DURATION,
       ml.LOCK_STATUS,
       t.PROCESSLIST_ID,
       t.PROCESSLIST_STATE
FROM performance_schema.metadata_locks AS ml
LEFT JOIN performance_schema.threads AS t
  ON t.THREAD_ID = ml.OWNER_THREAD_ID
WHERE ml.OBJECT_SCHEMA = DATABASE()
  AND ml.OBJECT_NAME = 'mdl_demo'
ORDER BY ml.LOCK_STATUS, ml.LOCK_TYPE;

COMMIT;
DO SLEEP(3);
SHOW COLUMNS FROM mdl_demo;

DROP EVENT IF EXISTS mdl_wait_event;
DROP TABLE mdl_demo;
SET GLOBAL event_scheduler = OFF;

실행 결과(MySQL 8.0.x):

아래 결과는 준비·정리 DDL의 반복 메시지를 줄이고, MDL 대기와 DDL 완료를 입증하는 핵심 구간만 발췌한 것이다.

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM mdl_demo WHERE id = 1;
+----+--------------------+
| id | payload            |
+----+--------------------+
|  1 | metadata lock demo |
+----+--------------------+
1 row in set (0.00 sec)

mysql> SELECT ml.OBJECT_TYPE, ...
    -> FROM performance_schema.metadata_locks AS ml ...;
+-------------+-----------------+-------------+-------------------+---------------+-------------+----------------+---------------------------------+
| OBJECT_TYPE | OBJECT_SCHEMA   | OBJECT_NAME | LOCK_TYPE         | LOCK_DURATION | LOCK_STATUS | PROCESSLIST_ID | PROCESSLIST_STATE               |
+-------------+-----------------+-------------+-------------------+---------------+-------------+----------------+---------------------------------+
| TABLE       | mysql_tech_note | mdl_demo    | SHARED_READ       | TRANSACTION   | GRANTED     |             13 | executing                       |
| TABLE       | mysql_tech_note | mdl_demo    | SHARED_UPGRADABLE | TRANSACTION   | GRANTED     |             14 | Waiting for table metadata lock |
| TABLE       | mysql_tech_note | mdl_demo    | EXCLUSIVE         | TRANSACTION   | PENDING     |             14 | Waiting for table metadata lock |
+-------------+-----------------+-------------+-------------------+---------------+-------------+----------------+---------------------------------+
3 rows in set (0.01 sec)

mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW COLUMNS FROM mdl_demo;
+---------+--------------+------+-----+---------+-------+
| Field   | Type         | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| id      | bigint       | NO   | PRI | NULL    |       |
| payload | varchar(100) | NO   |     | NULL    |       |
| note    | varchar(30)  | YES  |     | NULL    |       |
+---------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

관찰 시점에는 조회 트랜잭션의 공유 계열 잠금이 GRANTED, DDL이 요구하는 배타 계열 잠금이 PENDING으로 나타나야 한다. COMMIT 이후에는 공유 잠금이 해제되고 DDL이 진행되어 note 컬럼이 추가된다.

이 재현이 보여주는 핵심은 DDL 세션이 원인이 아니라 피해자일 수 있다는 점이다. 대기 중인 ALTER TABLE을 종료하면 당장의 대기열은 완화될 수 있지만, 장기 트랜잭션이 남아 있으면 다음 DDL에서도 같은 문제가 반복된다.

5. 운영 진단 쿼리

5.1 PENDING 요청과 세션 연결

다음 쿼리는 대기 중인 MDL 요청을 세션 정보 및 현재 statement와 연결한다. events_statements_current.SQL_TEXT는 instrumentation 상태와 문장 수명에 따라 NULL일 수 있으므로, 빈 값만으로 세션이 유휴 상태라고 단정하지 않는다.

SELECT ml.OBJECT_TYPE,
       ml.OBJECT_SCHEMA,
       ml.OBJECT_NAME,
       ml.LOCK_TYPE,
       ml.LOCK_DURATION,
       ml.LOCK_STATUS,
       t.PROCESSLIST_ID AS connection_id,
       t.PROCESSLIST_USER AS user_name,
       t.PROCESSLIST_TIME AS state_seconds,
       t.PROCESSLIST_STATE AS process_state,
       esc.SQL_TEXT AS current_sql
FROM performance_schema.metadata_locks AS ml
LEFT JOIN performance_schema.threads AS t
  ON t.THREAD_ID = ml.OWNER_THREAD_ID
LEFT JOIN performance_schema.events_statements_current AS esc
  ON esc.THREAD_ID = ml.OWNER_THREAD_ID
WHERE ml.LOCK_STATUS = 'PENDING'
ORDER BY t.PROCESSLIST_TIME DESC, ml.OBJECT_SCHEMA, ml.OBJECT_NAME;

실행 결과(MySQL 8.0.x):

mysql> SELECT ml.OBJECT_TYPE,
    ->        ml.OBJECT_SCHEMA,
    ->        ml.OBJECT_NAME,
    ->        ml.LOCK_TYPE,
    ->        ml.LOCK_DURATION,
    ->        ml.LOCK_STATUS,
    ->        t.PROCESSLIST_ID AS connection_id,
    ->        t.PROCESSLIST_USER AS user_name,
    ->        t.PROCESSLIST_TIME AS state_seconds,
    ->        t.PROCESSLIST_STATE AS process_state,
    ->        esc.SQL_TEXT AS current_sql
    -> FROM performance_schema.metadata_locks AS ml
    -> LEFT JOIN performance_schema.threads AS t
    ->   ON t.THREAD_ID = ml.OWNER_THREAD_ID
    -> LEFT JOIN performance_schema.events_statements_current AS esc
    ->   ON esc.THREAD_ID = ml.OWNER_THREAD_ID
    -> WHERE ml.LOCK_STATUS = 'PENDING'
    -> ORDER BY t.PROCESSLIST_TIME DESC, ml.OBJECT_SCHEMA, ml.OBJECT_NAME;

Empty set (0.00 sec)

결과가 Empty set이면 조회 순간에 노출된 PENDING MDL이 없다는 뜻이다. 과거에 대기가 없었다는 뜻도 아니고, instrument가 꺼진 상태와도 자동으로 구분되지 않는다. 반드시 setup_instruments 상태를 함께 확인한다.

5.2 sys schema에서 blocker 관계 보기

MySQL의 sys.schema_table_lock_waits는 table MDL의 waiting/blocking 관계를 운영자가 읽기 쉬운 형태로 제공한다. 원본 metadata_locks를 직접 조인하기 전에 빠르게 상황을 파악하는 데 유용하다.

SELECT object_schema,
       object_name,
       waiting_pid,
       waiting_account,
       waiting_lock_type,
       waiting_query_secs,
       blocking_pid,
       blocking_account,
       blocking_lock_type
FROM sys.schema_table_lock_waits
ORDER BY waiting_query_secs DESC;

실행 결과(MySQL 8.0.x):

mysql> SELECT object_schema,
    ->        object_name,
    ->        waiting_pid,
    ->        waiting_account,
    ->        waiting_lock_type,
    ->        waiting_query_secs,
    ->        blocking_pid,
    ->        blocking_account,
    ->        blocking_lock_type
    -> FROM sys.schema_table_lock_waits
    -> ORDER BY waiting_query_secs DESC;

Empty set (0.00 sec)

이 view가 비어 있어도 Performance Schema 설정, 대상 객체 종류, 관찰 시점을 확인해야 한다. 또한 sql_kill_blocking_connection 같은 생성형 열이 제공되더라도 내용을 검토하지 않고 실행해서는 안 된다. blocker가 중요한 배치나 복구 트랜잭션일 수 있으며, 연결 종료는 rollback 비용과 추가 부하를 유발한다.

5.3 장기 트랜잭션과 함께 해석하기

MDL blocker는 현재 활발히 SQL을 실행하는 세션이 아니라, 문장 실행을 마친 뒤 트랜잭션만 열린 세션일 수 있다. 따라서 다음 세 신호를 함께 본다.

  1. metadata_locks: 어떤 객체의 어떤 잠금이 GRANTED 또는 PENDING인가
  2. threads와 statement events: 어느 연결이 요청했으며 현재 무엇을 하는가
  3. information_schema.INNODB_TRX: 트랜잭션 시작 시각, 상태, 변경 행 수가 어떠한가

MDL만 보고 가장 오래된 연결을 종료하거나, 반대로 INNODB_TRX만 보고 행 잠금 문제로 결론 내리는 것은 모두 위험하다. 객체 잠금과 트랜잭션 수명을 연결해야 한다.

6. 대기열 증폭: 왜 조회까지 멈추는가

대표적인 장애 순서는 다음과 같다.

  1. 세션 A가 트랜잭션을 시작하고 테이블을 조회한다.
  2. 세션 A가 COMMIT 또는 ROLLBACK 없이 유휴 상태가 된다.
  3. 세션 B의 DDL이 EXCLUSIVE MDL을 요청하고 PENDING이 된다.
  4. 이후 세션 C, D의 조회가 같은 객체로 들어온다.
  5. 선행 배타 요청과 MDL 대기열 정책 때문에 후속 조회도 기다릴 수 있다.
  6. 애플리케이션 connection pool이 대기 세션으로 채워지고 장애가 확대된다.

이 현상은 단순히 “DDL 한 개가 느리다”가 아니다. DDL 배포가 오래된 트랜잭션을 드러내는 계기가 되고, 대기열이 애플리케이션 자원 고갈로 연결되는 복합 장애다.

운영 대응의 우선순위는 보통 다음과 같다.

  • 신규 대기 증가를 막기 위해 문제 DDL의 중단 여부를 판단한다.
  • 원래의 GRANTED blocker와 열린 트랜잭션을 식별한다.
  • 업무 영향과 rollback 비용을 평가한 뒤 정상 종료, 애플리케이션 취소, 연결 종료 중 하나를 선택한다.
  • DDL을 재시도하기 전에 트랜잭션 수명과 배포 사전 점검 절차를 수정한다.

7. 흔한 오해와 실패 패턴

7.1 SHOW PROCESSLIST만 보면 충분하다

SHOW PROCESSLISTWaiting for table metadata lock 같은 상태를 보여줄 수 있지만, 잠금 대상과 보유자 관계를 완전하게 표현하지 않는다. waiting 세션을 발견한 뒤 metadata_locks.OWNER_THREAD_IDthreads.THREAD_ID를 연결해야 한다.

7.2 DDL을 실행한 세션이 blocker다

대기 상태의 DDL은 흔히 blocker가 아니라 waiter다. 실제 blocker는 더 일찍 테이블에 접근한 장기 트랜잭션이다. PENDING 행만 보지 말고 동일 객체의 GRANTED 행을 함께 찾는다.

7.3 유휴 세션에는 잠금이 없다

클라이언트 관점에서 아무 SQL도 실행하지 않는 세션이라도 열린 트랜잭션은 MDL과 InnoDB 잠금을 유지할 수 있다. Sleep 상태와 트랜잭션 종료는 같은 개념이 아니다.

7.4 DDL timeout만 늘리면 해결된다

timeout 증가는 대기 허용 시간을 늘릴 뿐 blocker를 제거하지 않는다. 오히려 connection pool 점유 시간이 늘어 장애 범위를 키울 수 있다. DDL의 허용 대기 시간, 애플리케이션 query timeout, 배포 rollback 정책을 함께 설계해야 한다.

7.5 performance_schema.metadata_locks가 비어 있으므로 MDL이 없다

instrument가 비활성화되었거나 관찰 시점을 놓쳤을 수 있다. wait/lock/metadata/sql/mdlENABLED 상태를 먼저 확인하고, 재발성 문제라면 주기 수집 또는 경보 체계를 마련한다.

7.6 모든 GRANTED 잠금을 종료해야 한다

정상적인 문장도 실행 중 MDL을 보유한다. GRANTED 자체는 장애가 아니다. 동일 객체의 PENDING, 보유 기간, 트랜잭션 수명, 업무 중요도를 함께 판단해야 한다.

8. Aurora MySQL에서의 운영 해석

Aurora MySQL도 MySQL 호환 SQL 계층에서 MDL을 사용하므로 기본 진단 원리는 같다. performance_schema.metadata_locks, performance_schema.threads, sys.schema_table_lock_waits를 우선 확인한다. 다만 관리형 환경에서는 다음 차이를 고려한다.

  • Performance Schema 설정과 메모리 관련 옵션은 DB parameter group 및 엔진 버전의 영향을 받는다.
  • writer instance에서 수행되는 DDL과 트랜잭션이 진단의 중심이다. reader endpoint의 조회 지연과 writer의 MDL 대기를 같은 현상으로 혼동하지 않는다.
  • Performance Insights 또는 Database Insights의 대기 분류는 병목이 발생한 시간대를 찾는 보조 수단이다. 최종 blocker 관계는 당시의 세션 및 잠금 자료와 연결해야 한다.
  • failover는 MDL 문제의 일반적인 해결책이 아니다. 연결과 진행 중 트랜잭션에 큰 영향을 주며, 원인이 애플리케이션의 긴 트랜잭션 수명이라면 새 writer에서도 재발할 수 있다.
  • DDL 배포 전 writer의 장기 트랜잭션, 활성 세션 수, 배포 도구의 timeout과 재시도 정책을 확인한다.

Aurora의 분산 스토리지 구조는 백업과 복제 방식에 큰 차이를 만들지만, SQL 계층 객체 정의의 일관성을 보호해야 한다는 요구는 사라지지 않는다.

9. 예방 설계

애플리케이션 트랜잭션

  • 트랜잭션 범위를 HTTP 요청이나 작업 단위보다 불필요하게 길게 잡지 않는다.
  • 예외 경로에서도 COMMIT 또는 ROLLBACK이 보장되도록 한다.
  • connection pool 반환 전에 열린 트랜잭션을 정리한다.
  • 사용자 입력이나 외부 API 응답을 기다리는 동안 DB 트랜잭션을 유지하지 않는다.

DDL 배포

  • 배포 직전에 대상 객체의 PENDING MDL과 장기 트랜잭션을 확인한다.
  • 낮은 lock_wait_timeout을 배포 세션에 적용하여 무한정 대기하지 않도록 검토한다.
  • 온라인 DDL 옵션은 작업 단계의 동시성을 개선할 수 있지만, 시작과 종료에 필요한 MDL까지 없애지는 못한다.
  • 재시도는 이전 DDL 세션이 실제로 종료되었는지 확인한 뒤 수행한다.
  • 대규모 변경은 rollback 조건과 업무 영향도를 사전에 문서화한다.

관측과 경보

  • PENDING MDL 개수뿐 아니라 가장 오래된 대기 시간과 대상 객체를 수집한다.
  • 동일 객체의 waiting/blocking 세션 관계를 함께 보존한다.
  • 장기 트랜잭션, connection pool 사용률, DDL 배포 이벤트와 같은 시간축에 배치한다.
  • 순간적인 정상 MDL 획득을 과도하게 경보하지 않도록 지속 시간 조건을 둔다.

10. 장애 대응 체크리스트

  • wait/lock/metadata/sql/mdl
  • metadata_locks에서 PENDING
  • 동일 객체의 GRANTED
  • OWNER_THREAD_IDthreads

11. 결론

wait/lock/metadata/sql/mdl은 DDL 지연을 막연한 “테이블 락”이 아니라 관찰 가능한 waiting/blocking 관계로 바꾸는 출발점이다. 핵심은 metadata_locksPENDING 요청만 보는 것이 아니라, 같은 객체의 GRANTED 보유자와 열린 트랜잭션, 현재 또는 최근 SQL을 함께 연결하는 데 있다.

MDL 장애는 대개 DDL 한 문장의 문제가 아니라 긴 트랜잭션, 배포 timeout, 대기열 증폭, connection pool 고갈이 결합된 운영 문제다. 다음 단계에서는 Performance Schema의 stage와 statement 자료를 함께 사용하여 DDL이 MDL 획득 이후 실제 어느 실행 단계에서 시간을 소비하는지 구분하는 방법을 다룰 수 있다.