java PreparedStatement参数化触发死锁(JDBC,SQL Server 2019)

wgx48brx  于 2023-06-28  发布在  Java
关注(0)|答案(1)|浏览(138)

后台

我正在开发一个企业软件,我们最近决定从通过字符串连接构建SQL查询转向使用带参数的预处理语句,因此第一个示例:

String query = "SELECT * FROM CARS WHERE MAKE = '" + getMake() 
             + "' AND MODEL = '" + getModel() + "'";
PreparedStatement preparedStatement = connection.prepareStatement(query);

成为第二个:

String query = "SELECT * FROM CARS WHERE MAKE = ? AND MODEL = ?";
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, getMake());
preparedStatement.setString(2, getModel());

对于上下文,我使用SQL Server 2019和JDBC。

问题

由于这个变化是代码中唯一的区别,我现在收到了中断处理的死锁,我不明白为什么。该环境是高度并发的,因为大批量的数据在多个线程中处理,每个线程都有许多insertdeleteselect。我不明白的是,为什么这种并发性只在使用模板语句时才成为问题,而不是使用连接语句。
到目前为止,我已经尝试过:

  • 将隔离级别降低到READ_UNCOMMITTED-只会导致delete语句锁定
  • 创建唯一的PreparedStatement对象而不是重用一个对象-不会导致任何更改
  • 询问ChatGPT,这表明它可能与SQL Server重用模板语句有关,因为模板字符串是相同的,而串联版本是唯一的,因为它们包括字符串中的变量。
  • 使用WITH (ROWLOCK),这似乎导致了本质上相同的情况,只有键锁而不是页面锁导致死锁。

我还能尝试什么?为什么这些方法之间的行为会如此戏剧性,以至于导致死锁?我该怎么解决?
谢谢!

l7wslrjt

l7wslrjt1#

如果你问我为什么会有如此巨大的差异,我至少可以给予这样一个答案:类型转换。
Java类型的SQL参数是Unicode字符串,但在数据库上,您将使用其他东西,如具有特定排序规则的varchar。这种转换可能导致即使为表列定义了索引,也不会使用该索引。
在代码更改之前使用的串联String不需要类型转换。

解决方案

您可以通过在连接url中添加以下内容来告诉驱动程序不要将参数作为Unicode发送:

sendStringParametersAsUnicode=false

希望能帮上忙。

相关问题