【问题标题】:SQL/Java concurrent transaction deadlockSQL/Java 并发事务死锁
【发布时间】:2020-07-08 21:09:56
【问题描述】:

我有一个方法 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 添加列 columnName int4

C2: ALTER TABLE tableName 添加列 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 在 Postgres 端等待 C1 提交,C1 在 Java 端等待 C2。瞧,我们陷入了僵局。

由于集群/平衡、ipv4/6 和 dns 问题等问题,我没有 100% 的机会检查连接是否指向同一个数据库系统。

问题

如果执行有两个连接到同一个数据库而不执行任何架构更改,如何确保return false;?

【问题讨论】:

  • 不相关,但是:c.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); 在 Postgres 中没用,因为它不支持脏读。
  • 您知道像 Liquibase 这样的架构迁移工具已经解决了这个问题吗? (还有更多)
  • @a_horse_with_no_name 我不喜欢 liquibase,它的架构很脏。
  • 10 多年来,我在大约 20 个项目中运行良好。
  • @a_horse_with_no_name 我相信它在 20 多个项目中为更多人服务了 10 多年。不会改变它的架构很脏的事实。我的意思是谁实际上进行版本控制?谁是并发版本控制(git,svn,cvs)或changelog-xmls或数据库或所有这些的权威?谁在差异的情况下胜出,是数据库还是变更日志?

标签: java sql postgresql transactions deadlock


【解决方案1】:

使用填充了 GUID 的帮助表 database_instance。

在架构更新查询之前,获取 GUID 并将其锁定在本地字典中。

或者干脆在本地锁定表,不管它位于哪个数据库:

var tablename = new object();
lock(tablename)
{
   <your query>
}

【讨论】:

  • 好主意。不幸的是,我的方法没用,因为我不确定这个表是否存在。如果表不存在,我必须创建它并以这种方式违反without executing any schema-change-requirement。
【解决方案2】:

您可以为此使用advisory locks。

咨询锁使用调用者提供的任意数字并一直存在,直到它被显式释放或连接关闭。您可以convert the tablename to its 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
}

也许您想将该功能移动到它自己的方法中。


或者,您可以在迁移开始时尝试获取一个带有硬编码数字的锁(独立于要修改的表)。

【讨论】:

  • 这是个好主意,但即使问题与 postgres 有关,它也必须在其他数据库系统中工作,甚至是未来的异国系统和系统。我尝试使用普通 SQL 标准的方法。
  • 你很有经验,你能看看我的最新建议吗?谢谢。
  • @PeterRader:那么您将如何处理不支持事务 DDL 的数据库?
  • 他们需要处理事务。那是另一个限制。
【解决方案3】:

嗯,我有另一个想法。

我可以首先通过简单地执行 ROLLBACK 而不是 COMMIT 来衡量每个数据库的预期时间消耗。

让我们说

  1. ? 是一个数据库的BEGIN 和COMMIT 之间的单个时间消耗 和
  2. ?? 是所有时间消耗的数量。
  3. ??是所有测量的时间消耗的最大值,最高值ma​​x(??)。

然后我将Watchdog超时设置为

2 * ??

如果看门狗阻止我们有重复的数据库连接,显然必须返回false。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多