[英]SQL/Java concurrent transaction deadlock
我有一个方法boolean addIntegerColumnToDatabases(String tableName, String columnName, Connection ... conns)
在那里我有一个 SQL 连接的集合。
对于每个 SQL 连接,我执行 schema-update-query
BEGIN
ALTER TABLE <b>tableName</b> ADD COLUMN <b>columnName</b> int4
COMMIT
由于此方法必须是 ACID,因此我喜欢并行执行此操作。
例如,我有两个连接(C1、C2):
C1:开始
C2:开始
C1: ALTER TABLE tableName ADD COLUMN columnName int4
C2: ALTER TABLE tableName ADD COLUMN columnName int4
C1:提交
C2:提交
这是代码:
Statement[] s = new Statement[conns.length];
int i =0;
for(Connection c:conns) {
c.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
c.setAutoCommit(false);
s[i++]=c.createStatement();
}
for(Statement st:s) {
st.executeUpdate("ALTER TABLE "+tableName+" ADD COLUMN "+columnName+" int4;");
}
for(Connection c:conns) {
c.commit();
}
return true;
只要连接在不同的数据库上,这就会起作用。
如果 C1 和 C2 连接到同一个数据库,C2 会等待 C1 在 Postgres 端提交,C1 会在 Java 端等待 C2。 瞧,我们陷入了僵局。
由于集群/平衡、ipv4/6 和 dns 问题等问题,我没有 100% 的机会检查连接是否到同一个数据库系统。
如何确保return false;
如果执行有两个连接到同一个数据库而不执行任何模式更改?
使用填充了 GUID 的帮助表database_instance
。
在 schema-update-query 之前,获取 GUID 并将其锁定在本地字典中。
或者简单地在本地锁定表,无论它位于哪个数据库上:
var tablename = new object();
lock(tablename)
{
<your query>
}
您可以为此使用咨询锁。
咨询锁使用调用者提供的任意数字并存在,直到它被显式释放或连接关闭。 您可以将表名转换为其 oid并将该数字用作锁标识符。
类似的东西(没有错误处理或清理!)
ResultSet st.executeQuery("select pg_try_advisory_lock('" + tableName + "'::regclass::oid::bigint)");
rs.next();
boolean locked = rs.getBoolean(1);
if (locked) {
// do your migration
.....
// once you are done with the table, release the lock immediately
// alternatively keep it open and it will be released automatically
// when the connection is closed
st.execute("select pg_advisory_unlock('" + tableName + "'::regclass::oid::bigint)");
} else {
// handle the conflict
}
也许您想将该功能移动到它自己的方法中。
或者,您可以尝试在迁移开始时使用一些硬编码数字获取一个锁(独立于要修改的表)。
嗯,我还有一个想法。
我可以首先通过简单地执行ROLLBACK
而不是COMMIT
来衡量每个数据库的预期时间消耗。
可以说
BEGIN
和COMMIT
之间的单次消耗,并且然后我将看门狗超时设置为
2 * 𝗧𝗺
如果看门狗阻止我们有重复的数据库连接,并且显然必须返回false
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.