2010-02-25 4 views
192

온라인 사용자를 기록하는 innoDB 테이블이 있습니다. 사용자가 페이지를 새로 고칠 때마다 업데이트되어 사이트의 페이지 및 사이트에 대한 마지막 액세스 날짜를 추적합니다. 그런 다음 오래된 레코드를 삭제하기 위해 매 15 분마다 실행되는 cron이 있습니다.mysql을 피하는 방법 '잠금을 얻으려고 할 때 교착 상태가 발견되었습니다. 트랜잭션을 다시 시도하십시오 '

잠금을 얻으려고 할 때 '교착 상태가 발견되었습니다. 지난 밤에 약 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 

그런 다음 몇 가지 통계 로그인 할 몇 가지 계산을 수행합니다 (예 : 온라인 온라인 회원, 방문자).

+0

테이블 구조에 대해 더 자세히 설명해 주시겠습니까? 클러스터형 또는 비 클러스터형 인덱스가 있습니까? –

+9

http://dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html - "show engine innodb status"를 실행하면 유용한 진단 정보를 얻을 수 있습니다. – Martin

답변

205

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

  • 연결 1 :

    두 트랜잭션이 반대 순서에서 두 개의 잠금 장치, 즉 고정하려고하는 교착 상태를 얻을 수 잠기고 키 (1), 키 잠금 (2);

  • 연결 2 : 잠금 키 (2), 잠금 키 (1);

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

지금, 당신은 연결이 같은 순서의 키, 즉 잠글 것이다하도록 쿼리를 변경 한 경우 :

  • 연결 1 : 키 잠금 (1), 잠금 키 (2);
  • 연결 2 : 잠금 키(), 잠금 키();

교착 상태가 발생하는 것은 불가능합니다.

  1. 당신이 삭제 문을 제외하고 한 번에 하나의 키보다 더 많은 액세스를 잠글 다른 문의 사항이 없는지 확인 :

    그래서 이것은 내가 제안 것입니다. 만약 당신이 (그리고 나는 당신이 의심하는) 그들의 순서를 (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, 포기하기 전에이 특정 오류를 3 번 ​​다시 시도하십시오.)

+1

트랜잭션 (autocommit = false)이 있으면 교착 상태 예외가 발생합니다. 동일한 문장을 다시 시도하는 것만으로 충분합니까? 실행 취소() 또는 전체 트랜잭션이 이제는 느껴지므로 실행되고있는 모든 것을 다시 롤백하고 다시 실행해야합니까? – Whome

+2

거래를 사용하도록 설정 한 경우 모두 또는 전부입니다. 어떤 종류의 예외가있는 경우 전체 거래가 아무 효과가 없다는 것을 보장합니다. 이 경우 을 사용하면 모든 것을 다시 시작하고 싶을 것입니다. –

+1

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

49

두 개의 트랜잭션이 서로를 대기하여 잠금을 획득하면 교착 상태가 발생합니다. 예 :

  • Tx는 1 : 잠금 B, 다음

교착 상태에 대한 수많은 질문과 답변이 있습니다 : 다음

  • 텍사스 (2) B, A를 잠급니다. 행을 삽입/업데이트/삭제할 때마다 잠금이 획득됩니다. 교 x 상태를 f 지하려면 동시 트랜잭션이 교 x 상태가 될 수있는 순서로 행을 갱신하지 않도록해야합니다. 일반적으로 은 다른 트랜잭션 (예 : 항상 테이블 A가 먼저, 그 다음 테이블 B)에서도 항상 동일한 순서로 잠금을 획득하려고 시도합니다 ().

    데이터베이스에 교착 상태가 발생하는 또 다른 이유는 누락 된 인덱스 일 수 있습니다. 행이 삽입/업데이트/삭제 될 때 데이터베이스는 관계형 제약 조건을 확인해야합니다. 즉 관계가 일관성이 있는지 확인해야합니다. 그렇게하기 위해 데이터베이스는 관련 테이블의 외래 키를 점검해야합니다. 이 수정 된 행보다 다른 잠금을 획득하게 될 수 있습니다. 외래 키 (물론 기본 키)에 항상 인덱스가 있어야합니다. 그렇지 않으면 행 잠금 대신 테이블 잠금이 발생할 수 있습니다. 테이블 잠금이 발생하면 잠금 경합이 높아지고 교착 상태가 발생할 확률이 높아집니다.

  • +3

    아마도 내 문제는 사용자가 페이지를 새로 고침하여 cron이 레코드에서 DELETE를 실행하려고 시도하는 동시에 레코드의 UPDATE를 트리거한다는 것입니다. 그러나 INSERT에서 오류가 발생하여 cron이 방금 작성된 레코드를 삭제하지 않을 것입니다. 그렇다면 아직 삽입되지 않은 레코드에서 어떻게 교착 상태가 발생할 수 있습니까? – David

    +0

    테이블에 대한 더 많은 정보와 트랜잭션이 정확하게 수행 할 수있는 정보를 제공 할 수 있습니까? – ewernli

    +0

    나는 초기 게시물에 쿼리 정보를 편집했다. – David

    4

    delete 문은 테이블의 전체 행 중 큰 부분에 영향을 줄 수 있습니다. 결국 이것은 h 제될 때 테이블 잠금을 확보하게됩니다. 잠금 (이 경우 행 또는 페이지 잠금)을 유지하고 더 많은 잠금을 획득하면 항상 교착 상태 위험이 있습니다. 그러나 나는 왜 insert 문이 lock escalation으로 이어지는 지 설명 할 수 없다 - 페이지 분할/추가와 관련이 있을지 모르지만, 누군가는 MySQL을 잘 알고 있으면 거기에 기입해야 할 것이다.

    처음에는 delete 문에 대해 테이블 ​​잠금을 명시 적으로 획득하는 것이 좋습니다. LOCK TABLESTable locking issues을 참조하십시오.

    4

    당신은 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 쿼리를 수정하여 where 절을 추가합니다 (900 초보다 오래된 행 제외). 이렇게하면 cron 작업에 대한 종속성을 피할 수 있으며 실행 빈도를 줄이기 위해 일정을 다시 잡을 수 있습니다.

    교착 상태에 대한 이론 : MySQL에는 많은 배경 지식이 없지만 여기에 있습니다 ... deletewhere 절과 일치하는 행이 추가되지 않도록 datetime에 대한 키 범위 잠금을 보유하려고합니다. 트랜잭션의 중간에서 삭제할 행을 찾으면 수정중인 각 페이지에서 잠금을 획득하려고 시도합니다. insert은 삽입중인 페이지에서 잠금을 획득하려고 시도하고 다음 키 잠금을 획득하려고 시도합니다. 일반적으로 insert은 키 잠금이 열리기를 참을성있게 기다리지 만 deletedelete에 페이지 잠금이 필요하고 키 잠금이 필요하기 때문에 이 사용중인 동일한 페이지를 잠그려고하면 교착 상태가됩니다. 이것은 삽입을 위해 옳은 것처럼 보이지 않지만, deleteinsert은 겹치지 않는 datetime 범위를 사용하고 있으므로 다른 어떤 일이 일어나고 있습니다. 스프링을 사용하여 Java 프로그래머를위한

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

    2

    , 나는 자동으로 일시적인 교착 상태로 실행 트랜잭션을 시도 AOP 측면을 사용하여이 문제를 피할했습니다.

    자세한 내용은 @RetryTransaction Javadoc을 참조하십시오.

    +0

    링크를 업데이트 할 수 있습니까? 죽었어 –

    +0

    업데이트 됨 - 고마워요! – Archie

    관련 문제