mysql을 피하는 방법 '잠금을 시도 할 때 교착 상태가 발견되었습니다. 거래를 다시 시작하십시오 '


286

온라인 사용자를 기록하는 innoDB 테이블이 있습니다. 사용자가 페이지를 새로 고칠 때마다 업데이트되어 자신이있는 페이지와 사이트에 대한 마지막 액세스 날짜를 추적합니다. 그런 다음 이전 레코드를 삭제하기 위해 15 분마다 실행되는 크론이 있습니다.

잠금을 시도 할 때 'Deadlock이 발견되었습니다. 어젯밤 약 5 분 동안 트랜잭션을 다시 시작하십시오.이 테이블에 INSERT를 실행할 때 나타납니다. 누군가이 오류를 피하는 방법을 제안 할 수 있습니까?

=== 편집 ===

실행중인 쿼리는 다음과 같습니다.

첫 번째 사이트 방문 :

INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3

각 페이지 새로 고침에서 :

UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888

15 분마다 크론 :

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

그런 다음 일부 통계 (예 : 온라인 회원, 온라인 방문자)를 기록하기 위해 일부 계산을 수행합니다.


테이블 구조에 대한 자세한 정보를 제공 할 수 있습니까? 클러스터형 또는 비 클러스터형 인덱스가 있습니까?
Anders Abel

13
dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html- "show engine innodb status"를 실행하면 유용한 진단이 제공됩니다.
Martin

사용자가 페이지를 밟을 때 동기 데이터베이스 쓰기를 수행하는 것은 좋지 않습니다. 올바른 방법은 memcache 또는 일부 빠른 대기열과 같은 메모리에 유지하고 cron을 사용하여 db에 쓰는 것입니다.
Nir 씨

답변:


292

대부분의 교착 상태에 도움이되는 한 가지 쉬운 트릭은 작업을 특정 순서로 정렬하는 것입니다.

두 트랜잭션이 반대 순서로 두 개의 잠금을 잠그려고 할 때 교착 상태가 발생합니다.

  • 연결 1 : 키 (1)를 잠그고 키 (2)를 잠그십시오.
  • 연결 2 : 키 (2)를 잠그고 키 (1)를 잠그십시오.

둘 다 동시에 실행되면 연결 1은 키 (1)를 잠그고 연결 2는 키 (2)를 잠그고 각 연결은 다른 연결이 키-> 교착 상태를 해제 할 때까지 기다립니다.

이제 연결이 동일한 순서로 키를 잠그도록 쿼리를 변경 한 경우 :

  • 연결 1 : 키 (1)를 잠그고 키 (2)를 잠그십시오.
  • 연결 2 : 잠금 키 ( 1 ), 잠금 키 ( 2 );

교착 상태를 얻는 것은 불가능합니다.

이것이 내가 제안하는 것입니다.

  1. delete 문을 제외하고 한 번에 둘 이상의 키에 대한 액세스를 잠그는 다른 쿼리가 없는지 확인하십시오. 당신이하고 (그리고 당신이 생각하는 경우), (k1, k2, .. kn)에 오름차순으로 주문하십시오.

  2. 삭제 문을 오름차순으로 작동하도록 수정하십시오.

변화

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

DELETE FROM onlineusers WHERE id IN (SELECT id FROM onlineusers
    WHERE datetime <= now() - INTERVAL 900 SECOND order by id) u;

명심해야 할 또 다른 사항은 mysql 문서에 교착 상태가 발생하면 클라이언트가 자동으로 다시 시도해야한다고 제안하는 것입니다. 이 논리를 클라이언트 코드에 추가 할 수 있습니다. (이러한 특정 오류를 포기하기 전에 3 번 다시 시도하십시오).


2
Transaction (autocommit = false)이 있으면 교착 상태 예외가 발생합니다. 동일한 statement.executeUpdate ()를 다시 시도하는 것만으로 충분합니까? 아니면 전체 트랜잭션이 김프 처리되어 롤백 + 실행해야합니다.
Whome

5
거래가 가능하다면 모두 또는 아무것도 아닙니다. 어떤 종류의 예외가있는 경우 전체 거래가 효과가 없음을 보장합니다. 이 경우 모든 것을 다시 시작하고 싶을 것입니다.
Omry Yadan

4
거대한 테이블의 선택을 기반으로 한 삭제는 단순한 삭제보다 매우 느립니다
Thermech

3
정말 고마워 친구 '정렬 설명'팁으로 교착 상태 문제가 해결되었습니다.
Miere

4
@OmryYadan 내가 아는 한, MySQL에서는 UPDATE를 수행하는 동일한 테이블에서 하위 쿼리를 선택할 수 없습니다. dev.mysql.com/doc/refman/5.7/en/update.html
artaxerxe

73

교착 상태는 두 트랜잭션이 서로 대기하여 잠금을 획득 할 때 발생합니다. 예:

  • Tx 1 : 잠금 A, B
  • Tx 2 : B를 잠근 다음 A

교착 상태에 대한 수많은 질문과 답변이 있습니다. 행을 삽입 / 업데이트 / 삭제할 때마다 잠금이 획득됩니다. 교착 상태를 피하려면 동시 트랜잭션이 교착 상태를 초래할 수있는 순서로 행을 업데이트하지 않아야합니다. 일반적으로, 다른 트랜잭션에서도 항상 동일한 순서로 잠금을 확보하십시오 (예 : 항상 테이블 A를 먼저 켠 다음 테이블 B).

데이터베이스의 교착 상태에 대한 또 다른 이유는 인덱스누락되었을 수 있습니다 . 행이 삽입 / 업데이트 / 삭제 될 때 데이터베이스는 관계 제약 조건을 확인해야합니다. 즉, 관계가 일관성이 있는지 확인해야합니다. 이를 위해 데이터베이스는 관련 테이블에서 외래 키를 확인해야합니다. 그것은 수정 행보다 획득 한 다른 잠금 존재 초래한다. 그런 다음 항상 외래 키 (및 기본 키)에 대한 색인을 가져야합니다. 그렇지 않으면 행 잠금 대신 테이블 잠금 이 발생할 수 있습니다 . 테이블 잠금이 발생하면 잠금 경합이 높아지고 교착 상태 가능성이 높아집니다.


3
아마 내 문제는 사용자가 페이지를 새로 고침하여 cron이 레코드에서 DELETE를 실행하려고하는 동시에 레코드의 업데이트를 트리거한다는 것입니다. 그러나 INSERTS에서 오류가 발생하여 cron은 방금 생성 된 레코드를 삭제하지 않습니다. 아직 삽입되지 않은 레코드에서 교착 상태가 발생하는 방법은 무엇입니까?
David

테이블과 거래가 정확히 무엇에 관한 정보를 조금 더 제공 할 수 있습니까?
ewernli 2019

트랜잭션 당 하나의 명령문 만있는 경우 교착 상태가 발생하는 방법을 알 수 없습니다. 다른 테이블에서 다른 작업이 없습니까? 특별한 외래 키나 고유 제약 조건이 없습니까? 계단식 삭제 제한 조건이 없습니까?
ewernli

아니, 다른 특별한 것은 없습니다 ... 테이블 사용법의 본질로 생각합니다. 방문자가 페이지를 새로 고칠 때마다 행이 삽입 / 업데이트됩니다. 한 번에 약 1000 명 이상의 방문자가 방문합니다.
David

12

delete 문은 테이블에있는 전체 행의 많은 부분에 영향을 줄 수 있습니다. 결국 이로 인해 삭제시 테이블 잠금이 획득 될 수 있습니다. 잠금 (이 경우 행 또는 페이지 잠금)을 유지하고 더 많은 잠금을 확보하는 것은 항상 교착 상태 위험이 있습니다. 그러나 insert 문으로 인해 잠금 에스컬레이션이 발생하는 이유를 설명 할 수는 없습니다. 페이지 분할 / 추가와 관련이있을 수 있지만 MySQL을 더 잘 알고있는 사람이 채워야합니다.

처음에는 delete 문에 대해 테이블 ​​잠금을 명시 적으로 확보하는 것이 좋습니다. 잠금 테이블테이블 잠금 문제를 참조하십시오 .


6

delete이 의사 코드와 같이 임시 테이블에 삭제할 각 행의 키를 먼저 삽입하여 해당 작업 을 수행 할 수 있습니다

create temporary table deletetemp (userid int);

insert into deletetemp (userid)
  select userid from onlineusers where datetime <= now - interval 900 second;

delete from onlineusers where userid in (select userid from deletetemp);

이와 같이 분리하는 것은 효율성이 떨어지지 만, 키 범위 잠금을 유지할 필요는 없습니다 delete.

또한 select쿼리를 수정하여 where900 초보다 오래된 행을 제외 하는 절 을 추가하십시오 . 이것은 cron 작업에 대한 종속성을 피하고 덜 자주 실행되도록 스케줄을 조정할 수있게합니다.

교착 상태에 대한 이론 : MySQL에는 많은 배경 지식이 없지만 여기에 간다 ... delete데이트 시간 동안 키 범위 잠금을 유지하여 해당 where절과 일치하는 행 이 트랜잭션 중간에 추가 되지 않도록 합니다. 삭제하려는 행을 찾으면 수정중인 각 페이지에서 잠금을 획득하려고 시도합니다. 는 insert이에 삽입 된 페이지에 잠금을 획득하려고하고, 다음 키 잠금을 획득하려고 시도. 일반적으로는 insert해당 키 잠금이 열릴 때까지 참을성있게 기다리지 만 페이지 잠금이 필요하고 해당 키 잠금 이 필요 하기 때문에 사용 delete중인 동일한 페이지를 잠그려고 하면 교착 상태가됩니다 . 이것은 바로 삽입을 위해하지 않는 것, 그래도 및insertdeleteinsertdeleteinsert 겹치지 않는 날짜 시간 범위를 사용하고 있으므로 다른 일이 일어나고 있습니다.

http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html


4

누군가이 문제로 여전히 어려움을 겪고있는 경우 :

두 요청이 동시에 서버에 도달하는 비슷한 문제에 직면했습니다. 아래와 같은 상황은 없었습니다.

T1:
    BEGIN TRANSACTION
    INSERT TABLE A
    INSERT TABLE B
    END TRANSACTION

T2:
    BEGIN TRANSACTION
    INSERT TABLE B
    INSERT TABLE A
    END TRANSACTION

그래서 교착 상태가 발생하는 이유에 의아해했습니다.

그런 다음 외래 키로 인해 두 테이블 사이에 부모 자식 관계 선박이 있음을 발견했습니다. 자식 테이블에 레코드를 삽입 할 때 트랜잭션이 부모 테이블의 행에 대한 잠금을 획득했습니다. 그 직후 나는 잠금 상승을 트리거하는 부모 행을 독점 행으로 업데이트하려고했습니다. 두 번째 동시 트랜잭션이 이미 SHARED 잠금을 보유하고 있었기 때문에 교착 상태가 발생했습니다.

참조 : https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html


내 경우에도 문제는 외래 키 관계 인 것처럼 보입니다. Thanks1
Chris Prince

3

Spring을 사용하는 Java 프로그래머의 경우 일시적 교착 상태에 빠지는 트랜잭션을 자동으로 다시 시도하는 AOP 측면을 사용 하여이 문제를 피했습니다.

자세한 정보는 @RetryTransaction Javadoc을 참조하십시오.


0

내부가 MySqlTransaction에 싸여있는 메소드가 있습니다.

교착 상태 문제는 동일한 방법을 자체와 동시에 실행했을 때 나에게 나타났습니다.

메소드의 단일 인스턴스를 실행하는 데 문제가 없었습니다.

MySqlTransaction을 제거했을 때 아무런 문제없이 메소드를 병렬로 실행할 수있었습니다.

내 경험을 공유하는 것만으로는 옹호하지 않습니다.


0

cron위험합니다. cron의 한 인스턴스가 다음 인스턴스가 완료되기 전에 완료되지 않으면 서로 싸울 것입니다.

일부 행을 삭제하고 일부를 자고 반복하는 작업을 지속적으로 실행하는 것이 좋습니다.

또한 INDEX(datetime)교착 상태를 피하는 데 매우 중요합니다.

그러나 날짜 시간 테스트에 테이블의 20 % 이상이 포함되어 있으면 DELETE테이블 스캔이 수행됩니다. 더 자주 삭제되는 작은 청크는 해결 방법입니다.

더 작은 청크를 사용하는 또 다른 이유는 더 적은 행을 잠그는 것입니다.

결론 :

  • INDEX(datetime)
  • 지속적으로 실행되는 작업-삭제, 잠자기, 반복
  • 위 작업이 종료되지 않았는지 확인하려면 실패시 작업을 다시 시작하는 크론 작업을 수행하십시오.

다른 삭제 기술 : http://mysql.rjweb.org/doc.php/deletebig

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.