Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/postgresql/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java PSQL JDBC事务导致死锁_Java_Postgresql_Jdbc_Transactions_Deadlock - Fatal编程技术网

Java PSQL JDBC事务导致死锁

Java PSQL JDBC事务导致死锁,java,postgresql,jdbc,transactions,deadlock,Java,Postgresql,Jdbc,Transactions,Deadlock,问题: 更新: 为什么在表a中插入一行时带有对表B的外键约束,然后更新表B中的行,使表a中插入的行引用在事务中导致死锁? 场景: reservation.time\u slot\u id对time\u slot.id具有外键约束 进行预订时,将运行以下SQL: BEGIN TRANSACTION INSERT INTO reservations (..., time_slot_id) VALUES (..., $timeSlotID) UPDATE reservations SET n

问题:
更新: 为什么在
表a
中插入一行时带有对
表B
的外键约束,然后更新
表B
中的行,使
表a
中插入的行引用在事务中导致死锁?

场景:

  • reservation.time\u slot\u id
    time\u slot.id
    具有外键约束
  • 进行预订时,将运行以下SQL:

    BEGIN TRANSACTION   
    INSERT INTO reservations (..., time_slot_id) VALUES (..., $timeSlotID)
    UPDATE reservations SET num_reservations = 5 WHERE id = $timeSlotID
    COMMIT
    
  • 我正在用大约100个并发用户对我的服务器进行负载测试,每个用户都为相同的时间段(每个用户都有相同的
    $timeSlotID

  • 如果我不使用事务(删除
    cn.setAutoCommit(false);
    cn.commit()
    ,等等),则不会发生此问题

环境:

  • PostgreSQL 9.2.4
  • Tomcat v7.0
  • JDK 1.7.0_40
  • commons-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
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

PSQL错误:

// 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
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

虽然我还是不明白,但我补充道:

SELECT * FROM time_slot WHERE id = ? FOR UPDATE
作为事务中的第一条语句。这似乎解决了我的问题,因为我不再陷入僵局


我仍然希望有人能给我一个正确的答案,并向我解释这一点

虽然我还是不明白,但我补充道:

SELECT * FROM time_slot WHERE id = ? FOR UPDATE
作为事务中的第一条语句。这似乎解决了我的问题,因为我不再陷入僵局


我仍然希望有人能给我一个正确的答案,并向我解释这一点

外键如何导致死锁(在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 );
BEGIN;
INSERT INTO reservation VALUES( 2, now() );
假设子表中的FK列在会话一中被修改,该会话激发普通insert语句(要测试此行为,请在SQL Shell(psql)中打开一个会话并关闭自动提交,或者使用
begin
语句启动事务:

BEGIN;
INSERT INTO reservation VALUES( 2, now() );
修改子表中的FK列时,DBMS必须查找父表以确保父记录的存在。

如果被引用(父)表中不存在插入的值,DBMS将中断事务并报告错误。

如果该值存在,则将记录插入子表,但DBMS必须确保事务的完整性-在事务结束之前(提交插入子表之前),其他事务不能删除或修改父表中的引用记录。

PostgreSql 9.2(及以下版本)在这种情况下确保数据库完整性,在父表中的记录上放置读取共享锁。读取共享锁不会阻止读卡器从表中读取锁定的记录,但会阻止写入程序在共享模式下修改锁定的记录。

好的-现在在会话1插入的子表中有一条新记录(会话1在该记录上放置了一个写锁),而在父表的记录2上放置了读共享锁。事务尚未提交。

假设会话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 );
BEGIN;
INSERT INTO reservation VALUES( 2, now() );
查询执行良好,没有任何错误-它将一条新记录插入子表,并在父表的记录2上放置一个共享读锁。

现在(几毫秒后),会话1(作为同一事务的一部分)触发以下命令:

UPDATE time_slot
SET num_reservations = num_reservations + 1
WHERE id = 2;
在Postgres 9.2中,上述命令“挂起”并等待会话2放置的共享锁。

现在,假设几毫秒后,相同的命令正在会话2中运行:

UPDATE time_slot
SET num_reservations = num_reservations + 1
WHERE id = 2;
这个命令应该是“挂起”的,并且应该等待会话1的更新在记录上放置写锁。

但结果是:

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.
(“zakleszczenie”表示“死锁”,“BŁĄD”表示“错误”)

  • 来自会话2的update命令正试图在会话1锁定的记录2上放置写锁
  • 会话1试图在同一记录上放置写锁,由会话2锁定(在共享模式下)
  • ---->…死锁。

    通过使用SELECT FOR UPDATE在父表上放置写锁,可以防止死锁

    上面的测试用例不会导致PostgreSQL 9.3中的死锁(试试看)——在9.3中,他们改进了这种情况下的锁定行为


------编辑-其他问题-----------------

为什么insert语句在完成后不释放锁?或者它在整个事务中保持不变,这就是为什么不使用事务不会导致死锁

在事务中修改数据的所有语句(insert、update、delete)都会对修改后的记录设置锁。这些锁在事务结束之前保持活动状态—通过发出commit或rollback。

由于在JDBC连接中关闭了
自动提交
,连续的SQL命令会自动分组到一个事务中

解释如下:

如果连接处于自动提交模式,则其所有SQL语句都将作为单个事务执行和提交。否则,其SQL语句将分组为事务,并通过调用方法提交或方法回滚来终止这些事务



选择更新如何防止死锁


SELECT FOR UPDATE将在记录上放置写锁。这是整个事务中的第一个命令,锁放在开头。当另一个事务启动(在另一个会话中)时,也会