2013-10-11 2 views
2

질문 :
업데이트 : 이유는 무엇 table B에 외래 키 제약 조건 table A에 행을 삽입 한 후 table B에 행을 업데이트하는 트랜잭션에 table A 참조에 삽입 된 행 교착 상태가 발생 했나요?

PSQL JDBC 거래

시나리오 :

  • reservation.time_slot_idtime_slot.id에 외래 키 제약 조건이 있습니다. 예약은 다음과 같은 SQL이 실행되어
  • :

    BEGIN TRANSACTION 
    INSERT INTO reservations (..., time_slot_id) VALUES (..., $timeSlotID) 
    UPDATE reservations SET num_reservations = 5 WHERE id = $timeSlotID 
    COMMIT 
    
  • 나는 각각 동일한 시간 슬롯 (각각 같은 $timeSlotID에 대한 예약을, 약 100 명의 동시 사용자와 내 서버 부하 테스트입니다 사용자).

  • 트랜잭션을 사용하지 않으면 (cn.setAutoCommit(false);, cn.commit() 등을 제거하십시오.)이 문제가 발생하지 않습니다.


환경 :

  • 의 PostgreSQL 9.2.4
  • 톰캣 7.0
  • JDK 1.7.0_40
  • 평민 - DBCP-1.4.jar
  • commons-pool-1.6.jar
  • PostgreSQL을-9.2-1002.jdbc4.jar


코드 :

// endpoint start 
// there are some other SELECT ... LEFT JOIN ... WHERE ... queries up here but they don't seem to be related 
... 

// create a reservation in the time slot then increment the count 

cn.setAutoCommit(false); 
try 
{ 
    st = cn.prepareStatement("INSERT INTO reservation (time_slot_id, email, created_timestamp) VALUES (?, ?, ?)"); 
    st.setInt (1, timeSlotID); // timeSlotID is the same for every user 
    st.setString(2, email); 
    st.setInt (3, currentTimestamp); 

    st.executeUpdate(); 
    st.close(); 

    st = cn.prepareStatement("UPDATE time_slot SET num_reservations = 5 WHERE id = ?"); // set to 5 instead of incrementing for testing 
    st.setInt(1, timeSlotID); // timeSlotID is the same for every user 

    st.executeUpdate(); 
    st.close(); 

    cn.commit(); 
} 
catch (SQLException e) 
{ 
    cn.rollback(); 
    ... 
} 
finally 
{ 
    cn.setAutoCommit(true); 
} 

... 
// endpoint end 


PSQL의 오류 :

ERROR: deadlock detected 
DETAIL: Process 27776 waits for ExclusiveLock on tuple (2,179) of relation 49817 of database 49772; blocked by process 27795. 
    Process 27795 waits for ShareLock on transaction 3962; blocked by process 27777. 
    Process 27777 waits for ExclusiveLock on tuple (2,179) of relation 49817 of database 49772; blocked by process 27776. 
    Process 27776: UPDATE time_slot SET num_reservations = 5 WHERE id = $1 
    Process 27795: UPDATE time_slot SET num_reservations = 5 WHERE id = $1 
    Process 27777: UPDATE time_slot SET num_reservations = 5 WHERE id = $1 
HINT: See server log for query details. 
STATEMENT: UPDATE time_slot SET num_reservations = 5 WHERE id = $1 
+0

'timeSlotID'의 값은 무엇입니까? 대부분 동일한 레코드를 업데이트하려고합니다. –

+0

"그들"은 무엇을 말하고 있습니까? try 블록 내의 코드는 완전한 코드입니다. 따라서 'timeSlotID'는 두 쿼리에 대해 동일한 값입니다. "they"가 부하 테스트의 사용자 세션 인 경우 올바른 것입니다. 지금 나의 부하 테스트를 되돌아 본다; 무작위'timeSlotId'는 무작위가 아니며 모든 "user"에 대해 동일한'timeSlotId'를 선택했습니다. 그렇습니다. 모든 "사용자"가 동일한 'time_slot'행을 동시에 업데이트하려고했습니다. 그런데 왜이 문제는 트랜잭션을 사용할 때만 존재합니까? – Mike

+0

나는 그것을 볼 수 있지만, 100 개의 동시 세션으로이 세션을 실행하고 있는데, 그 중 어떤 세션이 교착 상태가 발생할 수있는 'timeSlotID'를 사용한다면 –

답변

4

외래 키로 인해 교착 상태가 발생할 수있는 방법 (PostgreSQL 9.2 이하).

하자 부모 테이블을 참조하는 자식 테이블이 말 :

CREATE TABLE time_slot(
    id int primary key, 
    num_reservations int 
); 

CREATE TABLE reservation(
    time_slot_id int, 
    created_timestamp timestamp, 
    CONSTRAINT time_slot_fk FOREIGN KEY (time_slot_id) 
    REFERENCES time_slot(id) 
); 

INSERT INTO time_slot values(1, 0); 
INSERT INTO time_slot values(2, 0); 

일반 INSERT 문을 발사 세션 하나를 수정 자식 테이블의 FK 열 (오픈이 동작을 테스트한다고 가정 하나 개의 SQL 쉘 (psql의)에서 세션 설정 자동 오프 커밋 또는 begin 문 사용하여 트랜잭션을 시작합니다 자식 테이블의 FK 열이 수정되면

BEGIN; 
INSERT INTO reservation VALUES(2, now()); 

은, DBMS는을 보장하기 위해 부모 테이블을 조회 할 것이다 부모의 존재 기록.

삽입 된 값이 참조 된 (상위) 테이블에없는 경우 - DBMS는 트랜잭션을 중단하고 오류를보고합니다.

레코드가 자식 테이블에 삽입되지만 DBMS는 트랜잭션 무결성을 보장해야합니다. 트랜잭션이 끝날 때까지 (부모 테이블에 INSERT 될 때까지 다른 트랜잭션이 부모 테이블의 참조 된 레코드를 삭제하거나 수정할 수 없음) 커밋 됨).

PostgreSQL 9.2 (이하)는 읽기 공유 잠금을 상위 테이블의 레코드에 배치하는 경우 데이터베이스 무결성을 보장합니다. 읽기 공유 잠금은 독자가 테이블에서 잠긴 레코드를 읽지 못하게하지만 작성자가 공유 모드에서 잠긴 레코드를 수정하지 못하게합니다.

OK - 세션 1에서 삽입 된 하위 테이블에 새 레코드가 있습니다 (세션 1에서이 레코드에 쓰기 잠금이 있음). 상위 공유 테이블의 레코드 2에 읽기 공유 잠금이 설정됩니다. 거래가 아직 완료되지 않았습니다.

세션이 부모 테이블에서 동일한 레코드를 참조하는 동일한 트랜잭션을 시작한다고 가정하자 :

BEGIN; 
INSERT INTO reservation VALUES(2, now()); 

쿼리가 오류없이 잘 실행 - 그것은 자식 테이블에 새 레코드를 삽입하고 또한 공유 읽기 잠금을 상위 테이블의 레코드 2에 h 치합니다. 공유 잠금이 충돌하지 않으면 많은 트랜잭션이 공유 읽기 모드에서 레코드를 잠글 수 있고 다른 사용자를 기다릴 필요가 없습니다 (쓰기 잠금 만 충돌 함).

UPDATE time_slot 
SET num_reservations = num_reservations + 1 
WHERE id = 2; 
포스트 그레스 9.2 위의 명령 "중지"에서

과 공유 잠금을 기다리고 배치 : 지금 (몇 밀리 초 이상)이 명령 (같은 트랜잭션의 일부로서) 세션 1 화재가

UPDATE time_slot 
SET num_reservations = num_reservations + 1 
WHERE id = 2; 

는이 명령이 "중지"하고 배치 쓰기 잠금을 기다리는 예상되는 : 세션 2

그리고 지금을하여 동일한 명령이 몇 밀리 초 나중에, 세션 2에서 실행되고 있다고 가정 UPDA에 의한 기록에 세션 1의 TE.

그러나 결과는 다음과 같습니다

BŁĄD: wykryto zakleszczenie 
SZCZEGÓŁY: Proces 5604 oczekuje na ExclusiveLock na krotka (0,2) relacji 41363 bazy danych 16393; zablokowany przez 381 
6. 
Proces 3816 oczekuje na ShareLock na transakcja 1036; zablokowany przez 5604. 
PODPOWIEDŹ: Przejrzyj dziennik serwera by znaleźć szczegóły zapytania. 

세션 2에서

  • 업데이트 명령에 노력하고있다 ("zakleszczenie"가 "교착 상태"를 의미는, "BLAD"는 "ERROR"를 의미) 세션 1에 의해 잠긴 레코드 2에 쓰기 잠금 놓기
  • 세션 1이 세션 2에 의해 잠겨 (공유 모드에서) 같은 레코드에 쓰기 잠금을 설정하려고 시도했습니다.
  • ----> ...... 교착 상태.

    교착 상태는 PostgreSQL의 9.3 (그것을 시도)에서 교착 상태가 발생하지 않습니다 UPDATE위한

    위의 테스트 케이스를 선택하여 부모 테이블에 쓰기 잠금을 배치하여 방지 할 수 - 9.3 그들은 이러한 경우에 잠금 동작을 개선 .



------------ 편집 - 추가 질문 -------------------

why does the insert statement not release the lock after it is done? Or does it remain for the entire transaction which is why not using a transaction does not cause a deadlock?

트랜잭션 (삽입, 업데이트, 삭제) 내의 데이터를 수정하는 모든 명령문은 수정 된 레코드에 잠금을 설정합니다. 이러한 잠금은 트랜잭션이 종료 될 때까지 - 확약 또는 롤백을 발행하여 활성 상태를 유지합니다. autocommit가 JDBC 연결에 꺼져

때문에, 연속적인 SQL 명령이보기 자동 하나의 트랜잭션

설명으로 그룹화하는 것은 여기에 있습니다 :
http://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#setAutoCommit%28boolean%29

If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either the method commit or the method rollback.



How does the SELECT FOR UPDATE prevent the deadlock?


SELECT FOR UPDATE는 레코드에 쓰기 잠금을 설정합니다. 이것은 전체 트랜잭션에서 첫 번째 명령이며 잠금은 처음에 배치됩니다. 다른 트랜잭션이 (다른 세션에서) 시작할 때 SELECT FOR UPDATE을 실행하여 동일한 레코드를 잠그려고합니다. 쓰기 잠금 충돌 - 두 트랜잭션이 동시에 같은 레코드를 잠글 수 없으므로 두 번째 트랜잭션의 SELECT FOR UPDATE이 보류 상태이고 첫 번째 트랜잭션이 잠금을 해제 할 때까지 대기합니다 (커밋 또는 롤백을 실행하여). 실제로 두 번째 트랜잭션이 대기 중입니다. 첫 번째 거래가 끝날 때까지 - 예약 테이블
에 삽입 된 기록에 쓰기 잠금 -와 외래 키 제약 조건에 의해 참조되는 time_slot 테이블의 기록에 읽기 공유 잠금
: 첫 번째 시나리오에서

는 INSERT 문이 자물쇠를 배치

읽기 공유 잠금이 충돌하지 않습니다. 둘 이상의 트랜잭션이 공유 모드에서 동일한 레코드를 잠글 수 있고 실행을 계속할 수 있습니다. 그런 다음 서로 기다릴 필요가 없습니다. 그러나 나중에 동일한 트랜잭션 내에서 UPDATE가 발행 될 때 이미 공유 모드로 잠긴 동일한 레코드에 쓰기 잠금을 설정하려고하면 교착 상태가 발생합니다.

Would placing the increment first also prevent the deadlock?





예, 당신은 맞다. 이렇게하면 교착 상태가 방지됩니다. 트랜잭션 시작시 레코드 잠금이 설정되기 때문입니다. 또 다른 트랜잭션은 동일한 레코드 at the beginning을 업데이트하려고 시도하기 때문에 레코드가 이미 다른 세션에서 (쓰기 모드로) 잠겨 있기 때문에이 시점에서 기다려야합니다.

+0

이것은 내가 찾고 있던 것이고 위대하다. 여전히 몇 가지 질문 : 비록 insert 문이 완료된 후에 잠금을 해제하지 않는 이유는 무엇입니까? 아니면 트랜잭션을 사용하지 않는 것이 교착 상태를 일으키지 않는 전체 트랜잭션에 대해 남아 있습니까? SELECT FOR UPDATE가 교착 상태를 어떻게 막을 수 있습니까? 증분을 먼저 배치하면 교착 상태가 방지됩니까? – Mike

+0

추가 질문에 대한 답변을 일부 추가했습니다. – krokodilko

+0

추가 정보를 보내 주셔서 감사합니다. 다시 한 번, 당신의 시간과 우아한 대답에 감사드립니다. – Mike

0

난 아직도 이해하지 않지만 그것, 나는 더했다 :

SELECT * FROM time_slot WHERE id = ? FOR UPDATE 

을 트랜잭션의 첫 번째 명령문으로 사용하십시오. 이것은 더 이상 교착 상태가 발생하지 않아서 내 문제를 해결 한 것 같습니다.

나는 여전히 누군가에게 적절한 대답을주고 나에게 이것을 설명하는 것을 좋아할 것이다.

+0

'time_slot' 테이블을 참조하는'reservation' 테이블 컬럼에 외래 키 제약 조건이있는 것 같습니다. 그렇다면 교착 상태가 발생합니다. – krokodilko

+0

맞습니다. 나는 그걸 생각하지 않았다. 나는'reservation' 테이블에 삽입이 다른 테이블 이었기 때문에 문제와 관련이 없다고 잘못 가정하고 있었지만 외래 키 제약에 대해서는 잊어 버렸습니다. 아무도 외래 키 제약 조건이 교착 상태를 일으키는 방법을 말해 줄 수 있습니까? – Mike