머리말
우리의 응용 프로그램은 DELETE
병렬로 쿼리를 실행하는 여러 스레드를 실행 합니다. 쿼리는 격리 된 데이터에 영향을줍니다. 즉, DELETE
별도의 스레드에서 동일한 행에 동시 가 발생할 가능성이 없어야 합니다. 그러나 문서 당 MySQL은 DELETE
명령문에 대해 소위 '다음 키'잠금을 사용 하여 일치하는 키와 일부 간격을 모두 잠급니다. 이로 인해 교착 상태가 발생하고 우리가 찾은 유일한 솔루션은 READ COMMITTED
격리 수준 을 사용하는 것입니다.
문제
s의 거대한 테이블로 복잡한 DELETE
명령문을 실행할 때 문제가 발생합니다 JOIN
. 특별한 경우에는 행이 두 개 뿐인 경고가있는 테이블이 있지만 쿼리는 두 개의 개별 INNER JOIN
ed 테이블 에서 특정 엔티티에 속하는 모든 경고를 삭제해야합니다 . 쿼리는 다음과 같습니다.
DELETE pw
FROM proc_warnings pw
INNER JOIN day_position dp
ON dp.transaction_id = pw.transaction_id
INNER JOIN ivehicle_days vd
ON vd.id = dp.ivehicle_day_id
WHERE vd.ivehicle_id=? AND dp.dirty_data=1
day_position 테이블이 충분히 클 때 (내 테스트의 경우 1448 행이 있음) READ COMMITTED
격리 모드 를 사용하는 모든 트랜잭션 은 전체 proc_warnings
테이블을 차단 합니다.
이 문제는 MySQL 5.1 (5.1.59에서 확인)과 MySQL 5.5 (MySQL 5.5.24에서 확인) 모두 에서이 샘플 데이터 ( http://yadi.sk/d/QDuwBtpW1BxB9) 에서 항상 재현됩니다 .
편집 : 링크 된 샘플 데이터에는 쿼리 테이블에 대한 스키마 및 인덱스가 포함되어 있으며 편의를 위해 여기에 재현되었습니다.
CREATE TABLE `proc_warnings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned NOT NULL,
`warning` varchar(2048) NOT NULL,
PRIMARY KEY (`id`),
KEY `proc_warnings__transaction` (`transaction_id`)
);
CREATE TABLE `day_position` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned DEFAULT NULL,
`sort_index` int(11) DEFAULT NULL,
`ivehicle_day_id` int(10) unsigned DEFAULT NULL,
`dirty_data` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `day_position__trans` (`transaction_id`),
KEY `day_position__is` (`ivehicle_day_id`,`sort_index`),
KEY `day_position__id` (`ivehicle_day_id`,`dirty_data`)
) ;
CREATE TABLE `ivehicle_days` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`d` date DEFAULT NULL,
`sort_index` int(11) DEFAULT NULL,
`ivehicle_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `ivehicle_days__is` (`ivehicle_id`,`sort_index`),
KEY `ivehicle_days__d` (`d`)
);
트랜잭션 당 쿼리는 다음과 같습니다.
거래 1
set transaction isolation level read committed; set autocommit=0; begin; DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;
거래 2
set transaction isolation level read committed; set autocommit=0; begin; DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1;
그중 하나는 항상 '잠금 대기 시간 초과 초과 ...'오류와 함께 실패합니다. information_schema.innodb_trx
다음 행 이 포함됩니다.
| trx_id | trx_state | trx_started | trx_requested_lock_id | trx_wait_started | trx_wait | trx_mysql_thread_id | trx_query |
| '1A2973A4' | 'LOCK WAIT' | '2012-12-12 20:03:25' | '1A2973A4:0:3172298:2' | '2012-12-12 20:03:25' | '2' | '3089' | 'DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1' |
| '1A296F67' | 'RUNNING' | '2012-12-12 19:58:02' | NULL | NULL | '7' | '3087' | NULL |
information_schema.innodb_locks
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4' | 'X' | 'RECORD' | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67' | 'X' | 'RECORD' | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
보시다시피 두 쿼리 모두 X
기본 키 = 53 인 행에 독점 잠금을 원합니다 . 그러나 두 쿼리 모두 proc_warnings
테이블 에서 행을 삭제하지 않아야 합니다. 색인이 잠겨있는 이유를 이해하지 못합니다. 또한, proc_warnings
테이블이 비어 있거나 day_position
테이블에 적은 수의 행 (예 : 100 개의 행)이 포함되어 있으면 인덱스가 잠기지 않습니다 .
추가 조사는 EXPLAIN
유사한 SELECT
쿼리 를 실행 하는 것이 었습니다 . 쿼리 최적화 프로그램이 인덱스를 사용하여 proc_warnings
테이블 을 쿼리하지 않으며 이것이 전체 기본 키 인덱스를 차단하는 이유를 상상할 수있는 유일한 이유입니다.
단순화 된 사례
두 개의 레코드가있는 테이블이 두 개만 있지만 자식 테이블의 부모 테이블 참조 열에 인덱스가없는 경우 문제를 더 간단한 경우에 재현 할 수도 있습니다.
parent
테이블 만들기
CREATE TABLE `parent` (
`id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
child
테이블 만들기
CREATE TABLE `child` (
`id` int(10) unsigned NOT NULL,
`parent_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
테이블 채우기
INSERT INTO `parent` (id) VALUES (1), (2);
INSERT INTO `child` (id, parent_id) VALUES (1, NULL), (2, NULL);
두 개의 병렬 트랜잭션에서 테스트하십시오.
거래 1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET AUTOCOMMIT=0; BEGIN; DELETE c FROM child c INNER JOIN parent p ON p.id = c.parent_id WHERE p.id = 1;
거래 2
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET AUTOCOMMIT=0; BEGIN; DELETE c FROM child c INNER JOIN parent p ON p.id = c.parent_id WHERE p.id = 2;
두 경우 모두 공통적 인 부분은 MySQL이 인덱스를 사용하지 않는다는 것입니다. 나는 그것이 전체 테이블을 잠그는 이유라고 생각합니다.
우리의 솔루션
현재 볼 수있는 유일한 해결책은 기본 잠금 대기 시간 제한을 50 초에서 500 초로 늘려서 스레드 정리를 완료하는 것입니다. 그런 다음 손가락을 교차시킵니다.
도움을 주셔서 감사합니다.
day_position
테이블이 너무 느리게 실행될 때 테이블에 일반적으로 포함 되는 행 수는 500 초로 제한 시간을 초과해야합니까? 2) 샘플 데이터 만 있으면 얼마나 오래 걸립니까?