Programing

MySQL에서 잠금 대기 시간 초과를 디버깅하는 방법은 무엇입니까?

lottogame 2020. 4. 5. 19:52
반응형

MySQL에서 잠금 대기 시간 초과를 디버깅하는 방법은 무엇입니까?


생산 오류 로그에 때때로 다음과 같은 내용이 표시됩니다.

SQLSTATE [HY000] : 일반 오류 : 1205 잠금 대기 시간 초과가 초과되었습니다. 거래를 다시 시작하십시오

그 시점에서 어떤 쿼리가 데이터베이스에 액세스하려고하는지 알고 있지만 정확한 쿼리에서 어떤 쿼리가 잠금을 가지고 있는지 확인할 수 있습니까?


이것을주는 것은 거래 라는 단어 입니다. 쿼리가 하나 이상의 InnoDB 테이블에서 하나 이상의 행을 변경하려고 시도했음을 알 수 있습니다.

쿼리를 알고 있으므로 액세스되는 모든 테이블이 범인이 될 수 있습니다.

거기에서, 당신은 실행할 수 있어야합니다 SHOW ENGINE INNODB STATUS\G

영향을받는 테이블을 볼 수 있어야합니다

모든 종류의 추가 잠금 및 뮤텍스 정보를 얻습니다.

내 고객 중 한 사람의 샘플은 다음과 같습니다.

mysql> show engine innodb status\G
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
110514 19:44:14 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 4 seconds
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 9014315, signal count 7805377
Mutex spin waits 0, rounds 11487096053, OS waits 7756855
RW-shared spins 722142, OS waits 211221; RW-excl spins 787046, OS waits 39353
------------------------
LATEST FOREIGN KEY ERROR
------------------------
110507 21:41:35 Transaction:
TRANSACTION 0 606162814, ACTIVE 0 sec, process no 29956, OS thread id 1223895360 updating or deleting, thread declared inside InnoDB 499
mysql tables in use 1, locked 1
14 lock struct(s), heap size 3024, 8 row lock(s), undo log entries 1
MySQL thread id 3686635, query id 124164167 10.64.89.145 viget updating
DELETE FROM file WHERE file_id in ('6dbafa39-7f00-0001-51f2-412a450be5cc' )
Foreign key constraint fails for table `backoffice`.`attachment`:
,
  CONSTRAINT `attachment_ibfk_2` FOREIGN KEY (`file_id`) REFERENCES `file` (`file_id`)
Trying to delete or update in parent table, in index `PRIMARY` tuple:
DATA TUPLE: 17 fields;
 0: len 36; hex 36646261666133392d376630302d303030312d353166322d343132613435306265356363; asc 6dbafa39-7f00-0001-51f2-412a450be5cc;; 1: len 6; hex 000024214f7e; asc   $!O~;; 2: len 7; hex 000000400217bc; asc    @   ;; 3: len 2; hex 03e9; asc   ;; 4: len 2; hex 03e8; asc   ;; 5: len 36; hex 65666635323863622d376630302d303030312d336632662d353239626433653361333032; asc eff528cb-7f00-0001-3f2f-529bd3e3a302;; 6: len 40; hex 36646234376337652d376630302d303030312d353166322d3431326132346664656366352e6d7033; asc 6db47c7e-7f00-0001-51f2-412a24fdecf5.mp3;; 7: len 21; hex 416e67656c73204e6f7720436f6e666572656e6365; asc Angels Now Conference;; 8: len 34; hex 416e67656c73204e6f7720436f6e666572656e6365204a756c7920392c2032303131; asc Angels Now Conference July 9, 2011;; 9: len 1; hex 80; asc  ;; 10: len 8; hex 8000124a5262bdf4; asc    JRb  ;; 11: len 8; hex 8000124a57669dc3; asc    JWf  ;; 12: SQL NULL; 13: len 5; hex 8000012200; asc    " ;; 14: len 1; hex 80; asc  ;; 15: len 2; hex 83e8; asc   ;; 16: len 4; hex 8000000a; asc     ;;

But in child table `backoffice`.`attachment`, in index `PRIMARY`, there is a record:
PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 30; hex 36646261666133392d376630302d303030312d353166322d343132613435; asc 6dbafa39-7f00-0001-51f2-412a45;...(truncated); 1: len 30; hex 38666164663561652d376630302d303030312d326436612d636164326361; asc 8fadf5ae-7f00-0001-2d6a-cad2ca;...(truncated); 2: len 6; hex 00002297b3ff; asc   "   ;; 3: len 7; hex 80000040070110; asc    @   ;; 4: len 2; hex 0000; asc   ;; 5: len 30; hex 416e67656c73204e6f7720436f6e666572656e636520446f63756d656e74; asc Angels Now Conference Document;;

------------
TRANSACTIONS
------------
Trx id counter 0 620783814
Purge done for trx's n:o < 0 620783800 undo n:o < 0 0
History list length 35
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1192212800
MySQL thread id 5341758, query id 189708501 127.0.0.1 lwdba
show innodb status
---TRANSACTION 0 620783788, not started, process no 29956, OS thread id 1196472640
MySQL thread id 5341773, query id 189708353 10.64.89.143 viget
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1223895360
MySQL thread id 5341667, query id 189706152 10.64.89.145 viget
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1227888960
MySQL thread id 5341556, query id 189699857 172.16.135.63 lwdba
---TRANSACTION 0 620781112, not started, process no 29956, OS thread id 1222297920
MySQL thread id 5341511, query id 189696265 10.64.89.143 viget
---TRANSACTION 0 620783736, not started, process no 29956, OS thread id 1229752640
MySQL thread id 5339005, query id 189707998 10.64.89.144 viget
---TRANSACTION 0 620783785, not started, process no 29956, OS thread id 1198602560
MySQL thread id 5337583, query id 189708349 10.64.89.145 viget
---TRANSACTION 0 620783469, not started, process no 29956, OS thread id 1224161600
MySQL thread id 5333500, query id 189708478 10.64.89.144 viget
---TRANSACTION 0 620781240, not started, process no 29956, OS thread id 1198336320
MySQL thread id 5324256, query id 189708493 10.64.89.145 viget
---TRANSACTION 0 617458223, not started, process no 29956, OS thread id 1195141440
MySQL thread id 736, query id 175038790 Has read all relay log; waiting for the slave I/O thread to update it
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (write thread)
Pending normal aio reads: 0, aio writes: 0,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
519878 OS file reads, 18962880 OS file writes, 13349046 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 6.25 writes/s, 4.50 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 1190, seg size 1192,
174800 inserts, 174800 merged recs, 54439 merges
Hash table size 35401603, node heap has 35160 buffer(s)
0.50 hash searches/s, 11.75 non-hash searches/s
---
LOG
---
Log sequence number 28 1235093534
Log flushed up to   28 1235093534
Last checkpoint at  28 1235091275
0 pending log writes, 0 pending chkp writes
12262564 log i/o's done, 3.25 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 18909316674; in additional pool allocated 1048576
Dictionary memory allocated 2019632
Buffer pool size   1048576
Free buffers       175763
Database pages     837653
Modified db pages  6
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages read 770138, created 108485, written 7795318
0.00 reads/s, 0.00 creates/s, 4.25 writes/s
Buffer pool hit rate 1000 / 1000
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
1 read views open inside InnoDB
Main thread process no. 29956, id 1185823040, state: sleeping
Number of rows inserted 6453767, updated 4602534, deleted 3638793, read 388349505551
0.25 inserts/s, 1.25 updates/s, 0.00 deletes/s, 2.75 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

1 row in set, 1 warning (0.00 sec)

innodb_lock_wait_timeout 을 설정하여 InnoDB의 잠금 대기 제한 시간 값을 늘리는 것을 고려해야합니다 . 기본값은 50 초입니다.

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set (0.01 sec)

/etc/my.cnf이 줄을 사용 하여 영구적으로 더 높은 값으로 설정할 수 있습니다

[mysqld]
innodb_lock_wait_timeout=120

mysql을 다시 시작하십시오. 지금 mysql을 다시 시작할 수 없으면 다음을 실행하십시오.

SET GLOBAL innodb_lock_wait_timeout = 120; 

세션 기간 동안 설정할 수도 있습니다.

SET innodb_lock_wait_timeout = 120; 

그 뒤에 쿼리


이 문제와 관련하여 많은 SO 스레드 중 하나에서 언급 한 것처럼 때로는 테이블을 잠근 프로세스가 프로세스 목록에서 잠자기 상태로 표시됩니다! 문제의 데이터베이스에서 열려있는 모든 수면 실을 죽일 때까지 머리를 찢었습니다 (당시 아무도 활동하지 않았습니다). 결국 테이블의 잠금이 해제되고 업데이트 쿼리가 실행되었습니다.

이 제안자는 "MySQL 스레드가 테이블을 잠근 다음 MySQL과 관련이없는 무언가가 발생할 때까지 대기하는 경우와 비슷하다"고 말했다.

show engine innodb status로그를 다시 검토 한 후 (잠금을 담당 한 클라이언트를 추적 한 후) 문제가 발생한 스레드가 트랜잭션 목록의 맨 아래에 오류가 발생한 활성 쿼리 아래에 나열되어 있음을 알았습니다. 고정 된 잠금으로 인해

------------------
---TRANSACTION 2744943820, ACTIVE 1154 sec(!!)
2 lock struct(s), heap size 376, 2 row lock(s), undo log entries 1
MySQL thread id 276558, OS thread handle 0x7f93762e7710, query id 59264109 [ip] [database] cleaning up
Trx read view will not see trx with id >= 2744943821, sees < 2744943821

( "Trx 읽기보기"메시지가 고정 잠금과 관련이 있는지 확실하지 않지만 다른 활성 트랜잭션과 달리이 메시지는 발행 된 쿼리와 함께 표시되지 않고 트랜잭션이 "정리 중"이라고 주장하지만 아직 여러 행 잠금)

이야기의 교훈은 스레드 가 잠자고 있어도 트랜잭션 이 활성화 될 수 있다는 것 입니다.


MySQL의 인기로 인해 잠금 대기 시간 초과가 초과 된 것은 놀라운 일이 아닙니다 . 트랜잭션 예외를 다시 시작 하면 SO에 많은 관심을 얻습니다.

경합이 많을수록 교착 상태가 발생할 가능성이 높아지며 교착 상태가 발생한 트랜잭션 중 하나를 시간 초과하여 DB 엔진이 해결할 수 있습니다. 또한 많은 수의 항목 을 수정 (예 : UPDATE또는 DELETE)하는 장기 실행 트랜잭션 ( 고성능 Java Persistence 책에 설명 된대로 더티 쓰기 이상을 피하기 위해 잠금을 설정 함 )은 다른 트랜잭션과 충돌을 일으킬 가능성이 더 큽니다.

InnoDB MVCC이지만 절을 사용하여 명시 적 잠금을FOR UPDATE 계속 요청할 수 있습니다 . 그러나 다른 일반적인 DB (Oracle, MSSQL, PostgreSQL, DB2)와 달리 MySQL REPEATABLE_READ기본 격리 수준으로 사용 합니다 .

이제, 행을 수정하거나 명시 적 잠금을 사용하여 획득 한 잠금은 현재 실행중인 트랜잭션 기간 동안 유지됩니다. 잠금 REPEATABLE_READ의 차이점에 대한 좋은 설명을 원한다면 이 Percona 기사READ COMMITTED읽으십시오 .

반복 읽기에서 트랜잭션 중에 획득 한 모든 잠금은 트랜잭션 기간 동안 유지됩니다.

READ COMMITTED에서 STATEMENT가 완료된 후 스캔과 일치하지 않는 잠금이 해제됩니다.

...

이는 READ COMMITTED에서 다른 트랜잭션은 UPDATE 문이 완료되면 갱신 할 수없는 (REPEATABLE READ에서) 행을 자유롭게 갱신 할 수 있음을 의미합니다.

따라서 격리 수준 ( REPEATABLE_READ, SERIALIZABLE)이 제한적 일수록 교착 상태가 발생할 가능성이 커집니다. 이것은 "그 자체"라는 문제가 아니라, 절충입니다.

여러 HTTP 요청에 걸친 논리적 트랜잭션을 사용할 때 응용 프로그램 수준의 손실 된 업데이트 방지READ_COMMITED 가 필요 하므로을 사용하여 매우 좋은 결과를 얻을 수 있습니다 . 낙관적 잠금 방식의 목표는 업데이트 손실 당신이 사용하는 경우에도 발생할 수있는 SERIALIZABLE격리 수준을 사용할 수 있도록하여 잠금 경합을 감소하면서 READ_COMMITED.


레코드의 경우 교착 상태가 있고 MySQL이이를 감지 할 수없는 경우에도 잠금 대기 시간 초과 예외가 발생하므로 시간이 초과됩니다. 또 다른 이유는 매우 오래 실행되는 쿼리 일 수 있으며, 해결 / 수리가 더 쉬울 수 있지만 여기서는이 사례에 대해 설명하지 않습니다.

MySQL은 일반적으로 교착 상태를 두 트랜잭션 내에서 "적절하게"구축하면 처리 할 수 ​​있습니다. 그런 다음 MySQL은 더 적은 수의 잠금을 소유 한 하나의 트랜잭션 (행이 적을수록 중요하지 않음)을 종료 / 롤백하고 다른 하나는 완료하도록합니다.

이제 두 개의 프로세스 A와 B와 3 개의 트랜잭션이 있다고 가정 해 봅시다.

Process A Transaction 1: Locks X
Process B Transaction 2: Locks Y
Process A Transaction 3: Needs Y => Waits for Y
Process B Transaction 2: Needs X => Waits for X
Process A Transaction 1: Waits for Transaction 3 to finish

(see the last two paragraph below to specify the terms in more detail)

=> deadlock 

MySQL은 교착 상태가 있음을 알 수 없기 때문에 매우 불행한 설정입니다 (3 트랜잭션 내에서 확장 됨). MySQL이하는 일은 ... 아무것도 아닙니다! 무엇을 해야할지 모르기 때문에 기다립니다. 첫 번째로 획득 한 잠금이 시간 초과 (프로세스 A 트랜잭션 1 : 잠금 X)를 초과 할 때까지 대기 한 다음 잠금 X를 차단 해제하여 트랜잭션 2 등을 잠금 해제합니다.

이 기술은 첫 번째 잠금 (Lock X)의 원인 (어떤 쿼리)을 찾는 것입니다. show engine innodb status트랜잭션 3이 트랜잭션 2를 기다리는 것을 쉽게 볼 수 있지만 ( ) 트랜잭션 2를 기다리는 트랜잭션 (트랜잭션 1)을 볼 수는 없습니다. MySQL은 Transaction 1과 관련된 잠금 또는 쿼리를 인쇄하지 않습니다. 유일한 힌트는 ( show engine innodb status인쇄의) 트랜잭션 목록 맨 아래에 Transaction 1이 아무 것도 수행하지 않는 것입니다. 끝).

대기중인 지정된 트랜잭션에 대해 잠금 (Lock X)이 부여되는 SQL 쿼리를 찾는 방법은 여기에 설명되어 있습니다. Tracking MySQL query history in long running transactions

프로세스와 트랜잭션이 정확히 무엇인지 궁금하다면. 프로세스는 PHP 프로세스입니다. 트랜잭션은 innodb-trx-table에 의해 정의 된 트랜잭션 입니다. 필자의 경우에는 두 개의 PHP 프로세스가 있었고 각 프로세스에서 수동으로 트랜잭션을 시작했습니다. 흥미로운 부분은 프로세스에서 하나의 트랜잭션을 시작했지만 MySQL은 실제로 두 개의 별도 트랜잭션을 내부적으로 사용했다는 것입니다 (아마도 일부 MySQL 개발자가 설명 할 수있는 단서가 없습니다).

MySQL은 자체 트랜잭션을 내부적으로 관리하고 있으며 두 가지 트랜잭션을 사용하여 PHP 프로세스 (프로세스 A)에서 오는 모든 SQL 요청을 처리하기로 결정했습니다. 트랜잭션 1이 트랜잭션 3이 완료되기를 기다리고 있다는 문장은 내부 MySQL입니다. MySQL은 트랜잭션 1과 트랜잭션 3을 하나의 "트랜잭션"요청 (프로세스 A)의 일부로 실제로 인스턴스화했습니다. 트랜잭션 3 ( "트랜잭션"의 하위 부분)이 차단되었으므로 "트랜잭션"전체가 차단되었습니다. "트랜잭션"이 트랜잭션 1 ( "트랜잭션"의 하위 부분)을 완료 할 수 없기 때문에 완료되지 않은 것으로 표시되었습니다. 이것이 바로 "트랜잭션 1이 트랜잭션 3이 완료되기를 기다립니다"라는 의미입니다.


이 예외의 큰 문제는 일반적으로 테스트 환경에서 재현 할 수 없으며 prod에서 발생할 때 innodb 엔진 상태를 실행하지 않는다는 것입니다. 따라서 프로젝트 중 하나 에서이 예외의 catch 블록에 아래 코드를 넣었습니다. 예외가 발생했을 때 엔진 상태를 파악하는 데 도움이되었습니다. 그것은 많은 도움이되었습니다.

Statement st = con.createStatement();
ResultSet rs =  st.executeQuery("SHOW ENGINE INNODB STATUS");
while(rs.next()){
    log.info(rs.getString(1));
    log.info(rs.getString(2));
    log.info(rs.getString(3));
}

pt-deadlock-logger유틸리티 매뉴얼 페이지를 살펴보십시오 .

brew install percona-toolkit
pt-deadlock-logger --ask-pass server_name

engine innodb status위에서 언급 한 정보를 추출 daemon하고 30 초마다 실행 되는 정보를 만드는 데 사용할 수 있습니다 .


위의 Rolando의 대답에서 추정하면 쿼리를 차단하는 것은 다음과 같습니다.

---TRANSACTION 0 620783788, not started, process no 29956, OS thread id 1196472640
MySQL thread id 5341773, query id 189708353 10.64.89.143 viget

쿼리를 실행해야하고 다른 쿼리가 실행될 때까지 기다릴 수 없다면 MySQL 스레드 ID를 사용하여 쿼리를 종료하십시오.

kill 5341773

(쉘이 아닌 mysql 내에서)

다음에서 스레드 ID를 찾아야합니다.

show engine innodb status\G

명령을 입력하고 데이터베이스를 차단하는 명령을 찾으십시오.


당신이 사용할 수있는:

show full processlist

MySQL의 모든 연결과 현재 연결 상태 및 실행중인 쿼리가 나열됩니다. show processlist;연결 통계뿐만 아니라 잘린 쿼리를 표시 하는 짧은 변형도 있습니다 .


다음은 "다른 쿼리"가 잠금 시간 종료 문제를 일으킨 원인을 알아 내기 위해 궁극적으로해야 할 일입니다. 응용 프로그램 코드에서는 보류중인 모든 데이터베이스 호출을이 작업 전용의 별도 스레드에서 추적합니다. DB 호출이 N 초보다 오래 걸리면 (30 초) 다음과 같이 기록됩니다.

// Pending InnoDB transactions
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started; 

// Optionally, log what transaction holds what locks
SELECT * FROM information_schema.innodb_locks;

위와 같이 교착 상태를 일으키는 행을 잠근 동시 쿼리를 정확하게 찾아 낼 수있었습니다. 필자의 경우 INSERT ... SELECT일반 SELECT와 달리 기본 행을 잠그는 것과 같은 문장이었습니다 . 그런 다음 코드를 재구성하거나 커밋되지 않은 읽기와 같은 다른 트랜잭션 격리를 사용할 수 있습니다.

행운을 빕니다!


JDBC를 사용하는 경우
includeInnodbStatusInDeadlockExceptions = true 옵션이 있습니다.

https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-configuration-properties.html


MySQL general.log (디스크 집중)를 활성화하고 mysql_analyse_general_log.pl사용 하여 다음과 같이 장기 실행 트랜잭션을 추출하십시오.

--min-duration = innodb_lock_wait_timeout 값

그 후 general.log를 비활성화하십시오.

참고 URL : https://stackoverflow.com/questions/6000336/how-to-debug-lock-wait-timeout-exceeded-on-mysql

반응형