MySQL 인덱스 병합으로 인한 데이터베이스 교착상태 분석 및 해결방안 | JD Cloud 기술팀

배경

DBS-Cluster List-More-Connection Query-Deadlock에서 9월 22일 데이터베이스 교착상태 로그를 보았는데, 조사 결과 mysql 최적화-인덱스 병합으로 인해 데이터베이스 교착상태가 발생한 것으로 확인되었습니다.

정의

인덱스 병합: MySQL 5.1 이후에 도입된 데이터베이스 쿼리 최적화 기술로, 여러 인덱스에 대해 쿼리하고 결과를 다시 병합할 수 있습니다.

MySQL 데이터베이스 잠금 메커니즘

문제를 해결하기 전에 먼저 mysql 데이터베이스의 잠금 메커니즘에 대해 이야기하십시오.

1 잠금의 기본 단위는 다음 키 잠금(레코드 잠금 + 갭 잠금)이며 레코드 잠금이나 갭 잠금으로 유령 읽기 문제를 해결할 수 있는 경우 레코드 잠금(행 잠금)과 갭 잠금으로 퇴화됩니다.

2 잠금은 데이터가 아닌 인덱스에 잠금을 추가합니다.

3 현재 읽기의 경우 인덱스가 잠겨 있습니다. 현재 읽기 문에는 (select... from. ... 업데이트용, select... from ..... 공유 모드에서 잠금, 업데이트..., 삭제가 포함됩니다. . ...).

4. 잠금은 고유 인덱스와 비고유 인덱스로 구분되며 쿼리 조건에 따라 등가 쿼리와 범위 쿼리로 구분되며, 데이터를 찾을 수 있는지 여부에 따라 레코드 유무로 구분됩니다. 존재.

이번 교착상태 문제에 사용된 인덱스는 non-unique index의 등가 쿼리에 레코드가 존재하는 것이므로 본 글에서는 이 상황에 대해서만 자세히 소개하고 있으며, 그 외의 상황에 대해서는 하단의 참고문서 1을 확인하시면 됩니다.

잠금 상황은 다음과 같습니다: 순서대로 스캔합니다. 먼저 조건과 일치하는 데이터를 스캔하고 다음 키 잠금을 추가합니다. 그런 다음 데이터와 일치하지 않는 첫 번째 레코드를 스캔하고 간격 잠금을 추가합니다. 마지막으로 레코드의 기본 키를 찾고 레코드 잠금을 추가합니다.

위의 상황을 위해 세 가지 유형의 잠금이 추가되었으며, 잠금의 목적은 팬텀 읽기(phantom read)가 발생하는 것을 방지하는 것입니다.

보조 인덱스의 잠금을 분석합니다.

테이블 구조:

CREATE TABLE `jdi_roster_apply_detail` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `apply_id` varchar(100) NOT NULL COMMENT '申请单号',
  `status` tinyint(10) NOT NULL COMMENT '状态',
  PRIMARY KEY (`id`),
  KEY `idx_status` (`status`),
  KEY `idx_apply_id` (`apply_id`)
) ENGINE=InnoDB AUTO_INCREMENT=984483 DEFAULT CHARSET=utf8 COMMENT='黑白名单申请单明细'

테이블 데이터:

ID 적용_ID 상태
959651 1695369220522068998 1
960738 1695369227576173690 1
961319 1695373047673903326 1
961365 1695373122447865228 1

idx_apply_id를 통해 설정된 b+ 트리:

인덱스는 보조 인덱스이므로 리프 노드에 저장된 데이터가 기본 키 값입니다.

SQL을 실행합니다:

select * from jdi_roster_apply_detail where apply_id='1695369227576173690' for update

데이터 스캔 프로세스 수행

1 조건에 맞는 레코드를 찾아 다음키 잠금을 추가하면 잠금은 (1695369220522068998,1695369227576173690)이 됩니다.

2 기록과 일치하지 않는 첫 번째 데이터를 찾아 gap lock을 높여서 lock은 (1695369227576173690, 1695373047673903326)이 된다.

3 조건에 맞는 기본키 인덱스에 레코드 잠금을 추가하므로 id=960738에 레코드 잠금을 추가합니다.

세 가지 유형의 잠금 솔루션에 대한 팬텀 판독:

1 첫 번째 항목에 대해 다음 키 잠금이 없고 다른 트랜잭션이 apply_id=1695369227576173690 및 id<960738을 추가하는 경우 쿼리 시 트랜잭션에 레코드가 하나 더 생기므로 팬텀 읽기가 발생합니다.

2 두 번째 간격 잠금이 없고 다른 트랜잭션이 apply_id=1695369227576173690, id>960738을 추가하는 경우 쿼리할 때 트랜잭션에 레코드가 하나 더 생기므로 팬텀 읽기가 발생합니다.

3. 세 번째 레코드 잠금이 없고 다른 트랜잭션이 ID=960738인 레코드를 삭제하는 경우 트랜잭션 쿼리 시 데이터 조각이 하나 줄어들게 되어 팬텀 읽기가 발생합니다.

실무적인 문제 분석

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

위 로그의 두 트랜잭션은 각각 업데이트 문을 실행했습니다.

#事务1
update jdi_roster_apply_detail set `status` = 10 where `status` = 1 and apply_id = '1695369220522068998'
#事务2
update jdi_roster_apply_detail set `status` = 10 where `status` = 1 and apply_id = '1695369227576173690' 

이 SQL은 특정 애플리케이션 ID의 승인 보류 중인 데이터를 승인됨으로 변경하는 데 사용됩니다.

Taishan에서는 update 문을 실행할 수 없기 때문에 select 문을 실행하여 인덱스 상태를 확인합니다.

explain select * from  jdi_roster_apply_detail  where `status` = 1 and apply_id = '1695369220522068998'

실행 결과:

결과를 보면 두 업데이트 문 모두 idx_status와 idx_apply_id라는 두 개의 인덱스를 사용하고 있으며, 검색된 결과는 병합되어 시뮬레이션 과정에서 두 개의 쿼리 문으로 분할될 수 있습니다.

교착상태 시뮬레이션

거래 1 거래 2 잠금 범위
시작하다 시작하다  
업데이트를 위해 jdi_roster_apply_detail에서 *를 선택하세요. 여기서 apply_id = '1695369220522068998'입니다.   idx_apply_id가 잠겨 있습니다. (-, 1695369220522068998], (1695369220522068998, 1695369227576173690) 기본 키 ID 인덱스가 잠겨 있습니다. id=959651
  업데이트를 위해 apply_id = '1695369227576173690'인 jdi_roster_apply_detail에서 *를 선택하세요. idx_apply_id가 잠겨 있습니다 (1695369220522068998, 1695369227576173690], (1695369227576173690, 1695373047673903326) 기본 키 ID 인덱스가 잠겨 있습니다 id=960738
업데이트를 위해 상태 = 1인 jdi_roster_apply_detail에서 *를 선택하세요.   idx_status에는 다음 키 잠금과 갭 잠금이 추가되지만, 기본 키 959651, 960738, 961319, 961365에 레코드 잠금이 추가되면 트랜잭션 2는 이미 960738에 레코드 잠금을 추가했기 때문에 트랜잭션 1이 차단됩니다.
  업데이트를 위해 상태 = 1인 jdi_roster_apply_detail에서 *를 선택하세요. idx_status에는 다음 키 잠금과 갭 잠금이 추가되지만, 기본 키 959651, 960738, 961319, 961365에 레코드 잠금이 추가되면 트랜잭션 1이 이미 959651에 레코드 잠금을 추가했기 때문에 트랜잭션 2가 차단됩니다.
  이중 자물쇠  

두 트랜잭션은 각각 두 개의 기본 키 ID에 대한 레코드 잠금을 원하므로 서로 기다리며 교착 상태가 발생합니다.

위는 idx_apply_id의 인덱스 쿼리를 먼저 수행한 후 idx_status의 인덱스 쿼리를 수행하는 방법인데, idx_status의 인덱스 쿼리를 먼저 수행한 후 idx_apply_id의 인덱스 쿼리를 수행하면 역시 Primary의 레코드 잠금으로 인해 교착상태가 발생하게 된다. 열쇠.

해결책

1 InnoDB가 인덱스 병합을 무시하고 여러 인덱스가 동시에 잠기지 않도록 강제 인덱스(idx_apply_id)를 사용하여 인덱스를 강제 실행합니다.

2 인덱스 병합 비활성화 인덱스 병합을 비활성화하려면 다음 명령을 사용하십시오: SET GLOBAL Optimizer_switch='index_merge=off,index_merge_union=off,index_merge_sort_union=off,index_merge_intersection=off';

3 Index Merge는 두 개의 독립적인 인덱스를 동시에 사용하므로 이 두 인덱스의 모든 필드를 포함하는 새로운 조인트 인덱스를 생성하여 InnoDB가 이 별도의 조인트 인덱스만 사용하도록 합니다.

세 번째 솔루션은 첫 번째 솔루션보다 쿼리 성능이 우수하고, 두 번째 솔루션에 비해 테이블에만 영향을 미치고 영향 범위가 작아서 이번에도 이 솔루션을 채택하게 되었습니다.

요약하다

교착상태 문제는 옵티마이저가 병합된 인덱스를 사용함으로써 발생했는데, 이는 결국 새로운 조인트 인덱스를 생성함으로써 해결되었습니다.

참조 문서:

1 https://www.xiaolincoding.com/mysql/lock/how_to_lock.html

저자: JD Industrial Li Xiaohui

출처 : JD Cloud 개발자 커뮤니티 전재시 출처를 밝혀주세요

오픈 소스 프레임워크 NanUI의 작성자가 철강 판매로 전환하여 프로젝트가 중단되었습니다. Apple App Store의 무료 목록 1위는 포르노 소프트웨어인 TypeScript입니다. 이제 막 인기를 얻었는데 왜 대기업들은 이를 포기하기 시작합니까? ? TIOBE 10월 목록: Java가 가장 큰 감소세를 보이고 C#은 Java Rust 1.73.0 출시 에 가까워지고 있습니다. 한 남자가 AI 여자친구의 부추김을 받아 영국 여왕을 암살했으며 9년 징역형을 선고받았습니다. Qt 6.6 공식 출시 Reuters: RISC-V 기술이 중미 기술 전쟁의 핵심이 된다 새로운 전장 RISC-V: 단일 기업이나 국가에 의해 통제되지 않는 Lenovo, Android PC 출시 계획
{{o.이름}}
{{이름}}

추천

출처my.oschina.net/u/4090830/blog/10117355