정산 배치가 계정 잔액을 여러 건 한 트랜잭션 안에서 UPDATE 하는데, 평소엔 멀쩡하다가 트래픽 몰리는 시간대에만 가끔 아래 에러가 납니다.
ERROR: deadlock detected
DETAIL: Process 18442 waits for ShareLock on transaction 99123;
blocked by process 18455.
Process 18455 waits for ShareLock on transaction 99120;
blocked by process 18442.
HINT: See server log for query hints.문제는 로컬에서 아무리 돌려도 재현이 안 된다는 거예요. 배치가 여러 개 동시에 돌 때만 터지는 것 같은데, 각 배치는 잘못된 것도 없어 보이고 각자 커밋도 잘 됩니다. 데드락이면 뭔가 서로 물고 있는 건 알겠는데 어디서부터 봐야 할지 감이 안 잡힙니다.
댓글 4
원인은 거의 항상 '업데이트 순서'입니다. 배치 A가 계정 100 -> 200 순으로 잠그고, 배치 B가 200 -> 100 순으로 잠그면, A는 100 잡고 200 기다리고 B는 200 잡고 100 기다리면서 서로 영원히 못 풀립니다. 그래서 로컬(동시 실행 안 됨)에선 재현이 안 되는 거예요.
해결은 한 트랜잭션 안에서 여러 행을 건드릴 때 항상 같은 순서로 잠그는 겁니다. 대상 id를 정렬해서 순서대로 UPDATE 하거나, 미리 SELECT ... FOR UPDATE ... ORDER BY id 로 일관된 순서로 락을 잡고 시작하면 순환이 생길 수가 없어요. 어느 배치든 낮은 id부터 잡으니까요.
추가로 트랜잭션은 최대한 짧게 가져가세요. 한 트랜잭션이 오래 여러 행을 붙들고 있을수록 충돌 창이 커집니다.
재시도도 같이 두는 걸 권합니다. 동시성이 높으면 데드락은 '버그'라기보다 가끔 일어날 수 있는 정상 상황에 가까워서, 40P01(deadlock_detected) 에러코드를 잡아서 짧게 백오프 후 한두 번 재시도하는 안전망은 실무에서 흔히 씁니다. 다만 재시도는 어디까지나 보조고, 순서 정렬로 발생 자체를 없애는 게 먼저예요. 재시도만 믿고 순서를 안 맞추면 부하 오를 때 재시도 폭풍이 됩니다.