Dead Lock (feat. foreign key)
계십니까? 저 데드락이라고 합니다.
아주 징그러운 그 녀석 데드락
이 돌아왔다. 오류가 났으니 로그를 얼른 살펴보자!
*** (1) TRANSACTION:
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2701 page no 212637 n bits 0 index PRIMARY of table 데이터베이스.테이블A trx id 9504463467 lock_mode X locks rec but not gap waiting
** (2) TRANSACTION:
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 2701 page no 212637 n bits 0 index PRIMARY of table 데이터베이스.테이블A trx id 9504463410 lock mode S locks rec but not gap
Record lock, heap no 42 PHYSICAL RECORD: n_fields 67; compact format; info bits 0
트랜잭션2가 특정 인덱스 레코드에 S Lock을 획득한 상태이고 트랜잭션1이 같은 인덱스 레코드에 X Lock을 얻기 위해 대기중인 상황이었다. 이상황만 가지고 보면 트랜잭션2가 커밋이 되고나면 트랜잭션1은 대기하다가 X Lock을 획득해야했다. 타임아웃 시간도 50초로 넉넉했는데 아마 트랜잭션2에서 트랜잭션1이 X Lock 을 획득한 이후에 해당 레코드에 update를 걸려고 해서 mysql이 데드락을 탐지하고 트랜잭션1을 롤백시킨것같다.
트랜잭션1 | 트랜잭션2 |
---|---|
S Lock 획득 | |
X Lock 획득 시도 및 대기중 | |
X Lock 시도 (트랜잭션1의 X Lock으로 인해 대기..) |
좋아 차근차근 시작이었던 S Lock 부터 어디서 걸린건지 확인해볼까ㅎㅅㅎ….
어?? 코드 그 어디에도 S Lock을 사용하는 곳이 없네?? JPA의 PESSIMISTIC_READ 락을 사용하는 곳도 없었고 트랜잭션 isolation 레벨을 SERIALIZABLE 로 열렸던 곳도 없다.. 뭐야??? 의아해하고 있다가 공식 문서상에는 언급된 것이 없나 하고 찾아보던 중 아래의 문구를 보게되었다.
외래키 제약이 테이블에 정의되어있으면 제약 조건과 관련있는 삽입, 수정, 삭제 수행시 해당 참조 레코드에 S 잠금을 건다고 한다. 그리고 위의 데드락 로그에 나와있는 테이블A를 참조하고 있는 테이블B를 찾고 로직에서 B테이블에 삽입, 수정, 삭제가 있었는지 찾아보았더니 삽입이 일어나고 있는걸 알아챌 수 었다. 이제 해당 코드에 각각 break point를 걸어놓고 실행해보니 드디어 재현이 된다.
해결자체는 간단하다. 처음에는 데드락 발생시 retry를 걸까 했었다가 외래키 자체를 없애는게 후에 버그도 방지할 수 있을것이라 생각하여 외래키 제약을 삭제하기로 결정하였다.
인생은 실전이야
글만 읽고나서 직접 경험해보지 못한 분을 위해 간단하게 실습을 준비하였습니다. 저와 같이 미숙한 분들은 한번 따라해보시면 좋을 것 같습니다.
먼저 데이터베이스 세션 2개를 열고서 시작하시면 됩니다.
테이블 생성 및 데이터 세팅
두 군데 중 한곳에 아래의 구문을 실행시켜주세요.
create table parent (id int primary key, name varchar(10));
create table child(id int primary key, p_id int, foreign key(p_id) references parent(id));
insert into parent(id, name) value (10, "kim");
데드락 발생시켜보기
이제 각 세션에서 다음의 순서대로 쿼리를 날려보도록 하시죠.
세션1 | 세션2 |
---|---|
begin; | begin; |
insert into child(id, p_id) value(10, 10); | |
update parent set name = ‘kkim’ where id = 10; | |
update parent set name = ‘mo’ where id = 10; | |
Deadlock found when trying to get lock; try restarting transaction |
- 세션1에서 인서트 시 parent 에 S 잠금을 획득.
- 세션2에서 업데이트하면서 X Lock 잠금 시도하였으나 세션1의 S 잠금 때문에 대기상태에 들어감.
- 세션1의 업데이트하면서 X Lock 시도하였으나 세션2의 X Lock으로 인해 데드락이 발생
Reference
https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html