MySQL 교착 상태 문제가 온라인에서 자주 발생합니다! 자신의 교과서와 같은 조사 및 분석 프로세스 공유

1. 로그

1.1 비즈니스 로그

반년 이상 원활하게 실행 된 코드에서 지난 며칠 동안 갑자기 교착 상태 이상이 자주 발생했습니다. 다음 비즈니스 로그는 피크 비즈니스 기간 동안 약 2 일마다 비즈니스 컴퓨터에서 발생합니다.

 INFO 57553 --- [ConsumerThread2] org.example.controller.TestController    : 全局链路跟踪id:2的日志:[TransactionReqVO(userId=4, money=4), TransactionReqVO(userId=2, money=2), TransactionReqVO(userId=5, money=5)]
 INFO 57553 --- [ConsumerThread1] org.example.controller.TestController    : 全局链路跟踪id:1的日志:[TransactionReqVO(userId=5, money=5), TransactionReqVO(userId=1, money=1), TransactionReqVO(userId=4, money=4)]
ERROR 57553 --- [ConsumerThread2] org.example.controller.TestController    : 全局链路跟踪id:2的异常:
### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
### The error may exist in org/example/mapper/TestTableMapper.java (best guess)
### The error may involve org.example.mapper.TestTableMapper.update-Inline
### The error occurred while setting parameters
### SQL: UPDATE test_table SET money = money + ? WHERE user_id = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
; Deadlock found when trying to get lock; try restarting transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

org.springframework.dao.DeadlockLoserDataAccessException: 
### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
### The error may exist in org/example/mapper/TestTableMapper.java (best guess)
### The error may involve org.example.mapper.TestTableMapper.update-Inline
### The error occurred while setting parameters
### SQL: UPDATE test_table SET money = money + ? WHERE user_id = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
; Deadlock found when trying to get lock; try restarting transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
 at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:266) ~[spring-jdbc-5.0.13.RELEASE.jar:5.0.13.RELEASE]
 at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) ~[spring-jdbc-5.0.13.RELEASE.jar:5.0.13.RELEASE]
 at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73) ~[mybatis-spring-2.0.1.jar:2.0.1]
 at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446) ~[mybatis-spring-2.0.1.jar:2.0.1]
 at com.sun.proxy.$Proxy59.update(Unknown Source) ~[na:na]
 at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:294) ~[mybatis-spring-2.0.1.jar:2.0.1]
 at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:67) ~[mybatis-3.5.1.jar:3.5.1]
 at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:58) ~[mybatis-3.5.1.jar:3.5.1]
 at com.sun.proxy.$Proxy62.update(Unknown Source) ~[na:na]
 at org.example.service.impl.TestServiceImpl.update(TestServiceImpl.java:16) ~[classes/:na]
 at org.example.manager.impl.BizManagerImpl.transactionMoney(BizManagerImpl.java:25) ~[classes/:na]
 at org.example.manager.impl.BizManagerImpl$$FastClassBySpringCGLIB$$824241b9.invoke(<generated>) ~[classes/:na]

교착 상태는 비즈니스에 교착 상태가 있음을 나타내는 매우 눈에 띄며 비즈니스 문제 여야합니다. 하지만 비즈니스 코드가 반년 이상 실행되고 있었는데 Git 레코드를 확인한 결과 최근에 비즈니스 관련 코드를 이동 한 사람이 아무도 없다는 사실을 발견했습니다. 이는 비즈니스에 문제가있을 수 있다는 것을 나타냅니다.하지만 최근에야이 예외를 트리거하는 조건에 도달했습니다.

로그의 간단한 요약 :

1. 이것은 어떤 오류 로그입니까?

8 行 : ### SQL : UPDATE test_table SET money = money +? WHERE user_id =? 9 行 : ### 원인 : com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException : 잠금을 얻으려고 할 때 교착 상태가 발견되었습니다. 트랜잭션을 다시 시작하십시오

8-9 행에서 오류가 데이터베이스 오류이고 교착 상태 오류로 인한 롤백임을 알 수 있습니다. 주요 SQL은 다음과 같습니다. UPDATE test_table SET money = money +? WHERE user_id =?

2. 핵심 오류의 호출 방법, 즉 트랜잭션을 시작하는 데 사용되는 방법은 무엇입니까?

30 行 : org.example.manager.impl.BizManagerImpl.transactionMoney (BizManagerImpl.java:25) ~ [classes / : na] 31 行 : org.example.manager.impl.BizManagerImpl $$ FastClassBySpringCGLIB $$ 824241b9.invoke에서 () ~ [클래스 / : na]

jdk 클래스, spring 클래스, mybatis 클래스를 필터링 한 후 핵심 비즈니스 오류 코드 (30 ~ 31 줄)를 얻습니다 .31 줄은 Spring의 프록시 실행이고 30 줄은 실행될 첫 번째 비즈니스 코드입니다 : BizManagerImpl.transactionMoney

1.2 데이터베이스 교착 상태 로그

그런 다음 라이브러리에 해당하는 데이터베이스 교착 상태 로그로 이동하여 show innodb engine status 명령을 사용하고 다음과 같이 중요하지 않은 로그를 필터링합니다.

------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-07-14 23:34:29 0x7f958f1d5700
*** (1) TRANSACTION:
TRANSACTION 95146580, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 5 row lock(s), undo log entries 2
MySQL thread id 6264489, OS thread handle 140273305761536, query id 837446998 10.10.59.164 root updating
UPDATE test_table SET money = money + 5 WHERE user_id = 5
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table `mall`.`test_table` trx id 95146580 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000000005; asc         ;;
 1: len 8; hex 8000000000000006; asc         ;;

*** (2) TRANSACTION:
TRANSACTION 95146581, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 5 row lock(s), undo log entries 2
MySQL thread id 6264490, OS thread handle 140280327919360, query id 837446999 10.10.59.164 root updating
UPDATE test_table SET money = money + 4 WHERE user_id = 4
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table `mall`.`test_table` trx id 95146581 lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000000005; asc         ;;
 1: len 8; hex 8000000000000006; asc         ;;

Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000000001; asc         ;;
 1: len 8; hex 8000000000000002; asc         ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table `mall`.`test_table` trx id 95146581 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000000004; asc         ;;
 1: len 8; hex 8000000000000005; asc         ;;

*** WE ROLL BACK TRANSACTION (2)

요점은 다음과 같이 요약됩니다.

1. 도서관의 마지막 교착 상태는 언제였습니까?

4 행 : 2020-07-14 23:34:29 0x7f958f1d5700은 2020-07-14 23:34:29에 마지막 교착 상태가 발생했음을 알았습니다.

2. 교착 상태로 인해 발생한 두 트랜잭션에 대한 중요한 정보는 무엇입니까?

12 행 : RECORD LOCKS space id 71816 page no 4n bits 80 index idx_user_id of table mall.test_table trx id 95146580 lock_mode X locks rec but not gap waiting 트랜잭션 1을 기다리는 잠금은 다음과 같습니다. lock_mode X locks rec but not gap waiting

24 행 : RECORD LOCKS space id 71816 page no 4n bits 80 index idx_user_id of table mall.test_table trx id 95146581 lock_mode X locks rec but not gap not learning that the lock hold the transaction 2 is : lock_mode X locks rec but not gap

34 행 : RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146581 lock_mode X locks rec but not gap waiting 트랜잭션 2를 기다리는 잠금은 lock_mode X locks rec but not gap waiting

39 행 : *** WE ROLL BACK TRANSACTION (2) 마지막 롤백이 트랜잭션 1임을 확인했습니다.

12, 24, 34 행 : mall.test_table 테이블의 idx_user_id 인덱스 : 교착 상태를 유발 한 인덱스 : idx_user_id

3. 교착 상태를 일으킨 두 가지 특정 SQL을 알 수 있습니까?

아니요, 다양한 교착 상태 상황이 있습니다. 트랜잭션에 SQL이 두 개 이상있을 수 있습니다. 교착 상태 로그만으로는 구체적인 원인을 알 수 없습니다. 트랜잭션 컨텍스트를 비즈니스 코드와 함께 확인해야합니다.

2. 이론적 지식

조사 과정에서 모든 대규모 온라인 사용자에게 영향을 미치는 기능이있는 것으로 확인되었습니다 . 교착 상태에 대한 이론적 지식을 오랫동안 읽지 않았기 때문에 먼저 관련 교착 상태에 대한 기본 지식을 이해합니다.

2.1 교착 상태 조건

  1. 상호 배타적 조건 : 리소스는 한 번에 하나의 프로세스에서만 사용할 수 있습니다.
  2. 점유 및 대기 : 리소스를 요청하여 프로세스가 차단되면 획득 한 리소스를 계속 보유합니다.
  3. 강제로 점유 할 수 없음 : 프로세스에서 이미 획득 한 자원을 사용하기 전에 강제로 제거 할 수 없습니다.
  4. 순환 대기 조건 : 여러 프로세스간에 일종의 순환 대기 자원 관계가 형성됩니다.

교착 상태를 파괴하는 것도 매우 간단합니다. 네 가지 조건 중 하나만 깨 뜨리면됩니다. (이 사건은 4)

2.2 데이터베이스 잠금 유형

데이터베이스의 교착 상태는 주로 삽입 및 업데이트로 인해 더 복잡해집니다 (실제로 삭제 또는 업데이트 용은 일반적으로 실제 비즈니스 코드에 삭제 또는 업데이트 용 작업이 없기 때문에 개발시 고려되지 않습니다. 삭제는 모두 업데이트는 반드시 사용해야한다는 것을 알지 않는 한 적게 사용하는 것이 좋습니다.) InnoDB 잠금 :

  1. 공유 잠금 및 독점 잠금 (S, X)
  2. 의도 잠금
  3. 레코드 잠금
  4. 갭 잠금
  5. 다음 키 잠금
  6. 의도 잠금 삽입
  7. 자기 증가 잠금
  8. 공간 인덱스 어설 션 잠금

다음은 공식 웹 사이트의 Innodb 잠금 분류에 대한 참조입니다. lock_mode X locks rec에서 교착 상태 로그의 갭이 아니라 X 잠금, 레코드 잠금 및 갭 잠금이 여기에 관련 될 수 있음을 대략적으로 알 수 있습니다 (하지만 관련되지 않음을 나타냄).

3. 교착 상태 로그에서 분석

분석하기 전에 테이블의 테이블 생성 문을 가져옵니다. show create table test_table ;:

CREATE TABLE `test_table` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `money` bigint(20) NOT NULL,
  `user_id` bigint(20) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8

그런 다음 교착 상태 로그, 잠금 유형 및 테이블 작성 문과 함께 다음과 같은 모호한 결론이 도출됩니다.

1. 테이블 인덱스 생성과 결합 된 교착 상태 로그의 10 개 및 12 개 행에서

10 行 : UPDATE test_table SET money = money + 5 WHERE user_id = 512 行 : RECORD LOCKS space id 71816 page no 4n bits 80 index idx_user_id of table mall.test_table trx id 95146580

UPDATE test_table SET money = money + 5 WHERE user_id = 5 statement of transaction 1 is waiting for the lock : it is updated through the normal index idx_user_id, first get the X lock with user_id = 5, then apply for the row of the same row 's primary key (Record Lock) 잠금이 차단되어 (대기 중) 갭 잠금 (갭 아님)을 포함하지 않습니다. 우리는 정확히 어떤 기본 키인지 모릅니다.

2. 테이블 인덱스와 결합 된 교착 상태 로그의 22, 24 줄에서

22 行 : UPDATE test_table SET money = money + 4 WHERE user_id = 424 行 : Record lock, heap no 3 물리적 기록 : n_fields 2; 컴팩트 형식; 정보 비트 0

UPDATE test_table SET money = money + 4 WHERE user_id = 4 statement of transaction 2 is holding the lock : it is updated through the normal index idx_user_id, first get the X lock with user_id = 4, then apply for the primary key (Record Lock) of the 해당 행 갭 잠금 (갭 아님)을 포함하지 않고 행 잠금에 성공했습니다. 어떤 기본 키가 성공적인지 알 수 없습니다.

3. 테이블 인덱스 생성과 결합 된 교착 상태 로그의 22 개 및 34 개 행에서

22 行 : UPDATE test_table SET money = money + 4 WHERE user_id = 434 行 : RECORD LOCKS space id 71816 page no 4n bits 80 index idx_user_id of table mall.test_table trx id 95146581 lock_mode X locks rec but not gap waiting

UPDATE test_table SET money = money + 4 WHERE user_id = 4 트랜잭션 2의 문이 잠금 대기 중 : 일반 인덱스 idx_user_id를 통해 업데이트되고 먼저 user_id = 4로 X 잠금을 획득 한 다음 해당 행의 기본 키 행에 적용됩니다 (Record Lock). 잠금이 차단되어 (대기 중) 갭 잠금 (갭 아님)을 포함하지 않습니다. 우리는 정확히 어떤 기본 키인지 모릅니다.

모호한 결론은 분명히 문제가되는데 가장 큰 문제는 SQL 문이 잘못된 것, 즉 교착 상태의 원인이 실제이지만 어떤 SQL이 교착 상태를 일으켰는지 명확하지 않다는 것입니다. 그런 다음 문제가 될 수있는 다음 표를 정리했습니다.

트랜잭션 1 트랜잭션 2 일부 SQL 일부 SQL 일부 SQL user_id = 5 새 업데이트 작업이 차단됨 일부 SQL user_id = 4 잠금을 얻었지만 일부 SQL을 차단 일부 SQL

사실, user_id가 4, 5 인 두 업데이트 작업은 서로를 차단하지 않기 때문에 교착 상태 로그만으로는 분석이 상대적으로 일방적임을 알 수 있습니다. 다른 SQL이 있어야하므로 비즈니스 로그를 추가해야합니다. 분석은 완전한 장면을 복원 할 수 있습니다.

4. 비즈니스 로그에서 분석

교착 상태 로그에서 주요 SQL 및 장애 사이트의 전체 프로세스를 완전히 알 수 없으므로 비즈니스 로그를 사용하여 장애 사이트의 최종 분석을 완료해야합니다. 이전 비즈니스 로그 분석을 통해 가장 중요한 호출 방법을 알고 있습니다. BizManagerImpl.transactionMoney이며 해당 소스 코드를 확인하십시오.

@Override
@Transactional
public boolean transactionMoney(List<TransactionReqVO> transactionReqVOList) throws Exception {
    for (TransactionReqVO transactionReqVO : transactionReqVOList) {
        // 模拟业务操作
        Thread.sleep(1000);
        int updateCount = testTableService.update(transactionReqVO.getUserId(), transactionReqVO.getMoney());
        if (updateCount == 0) {
            log.error("转账异常:" + transactionReqVO);
        }
    }
    return true;
}

for 루프 트랜잭션 문제 여야하지만 어떤 user_id가 명확하지 않은지 알 수 있습니다. 그런 다음 비즈니스 로그의 컨텍스트를 확인하고 전체 링크 traceId (시뮬레이션)를 검색하여 다음 로그를 얻습니다.

[ConsumerThread2] org.example.controller.TestController    : 全局链路跟踪id:2的日志:[TransactionReqVO(userId=4, money=4), TransactionReqVO(userId=2, money=2), TransactionReqVO(userId=5, money=5)]
[ConsumerThread1] org.example.controller.TestController    : 全局链路跟踪id:1的日志:[TransactionReqVO(userId=5, money=5), TransactionReqVO(userId=1, money=1), TransactionReqVO(userId=4, money=4)]

분석의이 시점에서 이미 교착 상태 시나리오를 복원 할 수 있습니다. 트랜잭션 흐름 차트는 다음과 같습니다.

MySQL 교착 상태 문제가 온라인에서 자주 발생합니다!  자신의 교과서와 같은 조사 및 분석 프로세스 공유

 

5. 비즈니스 로그 및 교착 상태 로그의 결합 분석

교착 상태 로그 분석의 잘못된 테이블을 비즈니스 로그 분석에 추가하여 올바른 테이블을 얻으면 다음을 이해하여 최종 올바른 트랜잭션 테이블을 얻습니다.

MySQL 교착 상태 문제가 온라인에서 자주 발생합니다!  자신의 교과서와 같은 조사 및 분석 프로세스 공유

 

교착 상태 로그의 SQL은 실제로 모호하지만 그 이유는 옳다는 것을 알 수 있으며 교착 상태를 일으킨 특정 SQL은 비즈니스 로그에서 파악해야합니다.

6. 여파

트랜잭션 2의 시나리오를 시뮬레이션 한 후 롤백 된 SQL을 실행하여 영향을받는 사용자 데이터를 수동으로 복구 할 수 있습니다 (고객 우선). 또한이 비즈니스 시나리오에서 각 사용자의 업데이트는 독립적이고 서로 영향을받지 않아야하므로 transactionMoney () 메서드가 트랜잭션을 추가해서는 안된다는 것을 알 수 있습니다. 그러나 업데이트가 실패하면 해당 로그도 인쇄해야합니다.

여기서 우리는 반년 이상 문제가 없었던 이유를 알고 있으며, 최근에 이런 종류의 이상이 자주 발생했습니다. 왜냐하면 두 개의 트랜잭션이 동시에 실행되고 두 트랜잭션이 동일한 두 개 이상의 user_id를 포함 할 때만 트리거가 가능하기 때문입니다. 예외입니다. 그리고 이런 종류의 user_id는 소위 큰 사용자입니다.이 예제의 user_id 1과 2는 영향을받는 소규모 사용자이지만 user_id가 4와 5 인 큰 사용자의 빈도만큼 높지 않습니다.

이는 실제 비즈니스 시나리오에서도 확인할 수 있습니다. 실패 시간은 피크 기간에 집중되어있을뿐만 아니라 실패한 사용자도 자주 그 친숙한 얼굴을 갖게됩니다. 이후 검토 후 이러한 친숙한 얼굴 사용자도 "대규모 사용자"(비즈니스 운영 빈도가 높은 사용자).

7. 시뮬레이션 프로젝트 소스 코드

실제 장면에서 메서드 호출 (메시지 수신 호출 실행)을 시뮬레이션하기 위해 스레드를 사용하여 시뮬레이션합니다. 그리고 스레드 절전을 사용하여 각 트랜잭션이 모든 시뮬레이션 실행을 비정상적으로 만들 수있을만큼 충분히 오래 실행되도록합니다.

MySQL 교착 상태 문제가 온라인에서 자주 발생합니다!  자신의 교과서와 같은 조사 및 분석 프로세스 공유

 

프로젝트 구조는 비교적 간단합니다. Controller-> Manager-> Service-> Mapper-> DB는 curl'localhost : 8080 / test / consumer '를 실행 한 후 명령 줄 출력을 확인하여 비즈니스 예외 로그를 ​​확인합니다.

해당 데이터베이스에서 해당 교착 상태 로그를 실행해야합니다. show engine innodb status를 볼 수 있습니다.

8. 마지막으로

많은 정보를 확인한 결과 모든 교착 상태 로그에 해당하는 가능한 SQL을 요약하는 프로젝트가 있음을 발견했습니다. 구경하다. 다음은 프로젝트의 일부 스크린 샷입니다.

MySQL 교착 상태 문제가 온라인에서 자주 발생합니다!  자신의 교과서와 같은 조사 및 분석 프로세스 공유

 

복잡한 비즈니스 시나리오가 발생할 때 특히 익숙하지 않은 경우 좋은 참조 자료입니다.

비즈니스 로그 기록 및 전체 링크 추적은 매우 중요합니다.

추천

출처blog.csdn.net/weixin_48612224/article/details/109191912