배경
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
오픈 소스 프레임워크 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 출시 계획저자: JD Industrial Li Xiaohui
출처 : JD Cloud 개발자 커뮤니티 전재시 출처를 밝혀주세요