jdbc发送unicode字符数据导致的sqlserver死锁 和性能降低

遇到的问题:

线上业务经常出现死锁,查看日志被锁对象是一个应用高频表。

思路:

一开始怀疑是因为高频表,业务操作比较多造成的死锁,确实也有一些功能也是执行时间较长,开启了长事务,但是发现一些对该表的根据id更新的简单操作也会死锁,于是就怀疑表的设计有问题,查看了下表发现该表也有主键索引,于是还怀疑是不是sqlserver或者表的锁策略有问题,于是查看read_committed_snapshot是否开启

SELECT name, is_read_committed_snapshot_on

FROM sys.databases

WHERE name = Databasename;

结果显示开启了

 

查看主键是否有聚合索引,结果显示也有

SELECT name, type_desc

FROM sys.indexes

WHERE object_id = OBJECT_ID('tablename')

 

到这里再继续怀疑是数据库的问题,已经不容易继续了,也没有思路,于是决定写几个测试用例,复现一下死锁场景

测试:

为了验证死锁出现的最低阈值,整个测试用例场景是从简单到复杂逐渐递加,数据库语句采用了最简单的根据主键id更新一个字段的语句,然后用for循环进行更新

update table set 字段名称=测试数据 where id = ?

然后采用了三种更新的方式

1:java 使用for循环,里面使用UpdateWrapper进行更新

2:java 使用for循环,里面使用xml更新语句进行更新

3:在xml中使用<foreach>循环更新

测试用例选择了五条,每条测试用例包含45条数据,每次调用测试方法,方法内都循环45次更新

一开始选择两个线程,每个线程循环调用5次测试用例,然后采用结1、2的更新方式,结果,如下图,线程死锁的请求都占了30%,首先耗时有点不正常,然后两个线程就死锁了,这还是只用主键更新一条数据

 

 

然后测试了,第三种的更新方法,发现是100%通过 如下图(下图开启了5个线程,2个线程的没有保存),虽然成功了,但这时间明显是不对了,太长了,感觉问题还是没有找清楚

 

按照上面的测试结果,我直接将测试用例减少为一个,只用两个线程,每个线程只调用一次接口,并且这两次调用的测试用例完全不一样,保证每次update更新的主键都不相同,按照行锁的概念,本应该不会发生死锁,但是结果还是发生了死锁,于是我查询了数据库的死锁事件如下图

锁类型是key,说明是走的索引,那为什么感觉像是索引失效了,看到后面的更新语句我发现了华点,主键是varchar类型,更新的时候传的是nvarchar(4000),是不是数据类型不一样导致索引失效了呢,于是我把更新语句加上了cast进行了一次转换,再进行并发测试,结果如下图

 

没有死锁了,执行时间明显减少了,看来是索引生效了了,那就是varchar变为nvarchar的原因

解决方法:

在jdbc连接url上添加sendStringParametersAsUnicode=false

原因:默认情况下,java的字符数据是作为Unicode来处理的,所以jdbc驱动连接数据库传输字符数据默认是unicode编码,在sqlserver中支持unicode编码的数据类型是nvarchar、nchar、ntext而varchar是非unicode,所以之前的条件会转成nvarchar;sendStringParametersAsUnicode=false的意思是指定字符数据的预定义参数作为 ASCII 或多字节字符集 (MBCS) 而不是 Unicode 来发送

参考文献

国际功能 - JDBC Driver for SQL Server | Microsoft Learn

设置连接属性 - JDBC Driver for SQL Server | Microsoft Learn

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值