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将在记录上放置写锁。这是整个事务中的第一个命令,锁放在开头。当另一个事务启动(在另一个会话中)时,也会