前段时间生产上出现了数据库锁太多的告警,其中等待的锁短时间内达到了6000个,好在十分钟后锁很快解除,否则可能会有数据库宕机的风险。此问题的具体表现如下:
- 其中一台应用服务器在凌晨2点钟出现大量交易变慢,导致weblogic线程超时阻塞,进而导致服务重启;
- 其他服务器在2点钟某一个账务交易出现大量长交易,但其他账务交易、查询交易均正常;
- 数据库某个限额统计表在2点多出现大量的行锁,触发数据库告警。
- 数据库在2点10分恢复正常,行锁也跟着消失
根据数据库锁的信息,结合那段时间的交易流水,可以确定是由于某个大客户的同一帐号的转账导致的。进一步梳理了代码,没有发现死锁的问题;另外这个客户日间交易量更大,理论上不应该出现这种问题。
进一步分析出问题的应用服务器怀疑是由于iBatis的锁导致的(原来生产上也出现过由于IBatis导致的锁定问题),但在什么情况下会出现长时间的行锁呢?目前iBatis有三个相关参数:maxRequests、maxSessions、maxTransactions,目前生产上分别设置的是100,60, 30。但根据iBatis的文档看maxRequests的值建议是maxTransactions的是10倍。为了验证是否是由于此参数的问题,我们在非功能环境进行了测试。
1. maxSessions=90,数据库连接数60,只做查询:
为了看出问题我们去掉了索引使查询交易耗时边长。此测试发现当查询并发小于等于60时没问题,但大于60时会出现连接不够的错误。
2. maxSessions=90,数据库连接数100,只做查询:
发现查询并发数增大(超过数据库连接)并不会导致交易失败,但查询时长会边长。这说明ibatis自身有锁机制来进行等待。
3. maxSessions=90,数据库连接数100,同时做付款与查询:
转账交易10个并发,在转账交易稳定后开始发送查询交易,查询交易85个并发。发现在启动查询交易一段时间后转账交易开始出现超时和失败,但查询交易无失败和超时,但交易时间有增加。
转账交易10个并发,在转账交易稳定后开始发送查询交易,查询交易75个并发。发现在启动查询交易一段时间后转账交易正常无失败和超时,查询交易亦无失败和超时。
这个测试我们为了看出对账务交易的影响对账务交易代码进行了修改,伪码如下:
Enquire agreementInfo and other info; (no transaction)
Start transaction #spring逻辑
Get session #ibatis处理逻辑
Get Request #ibatis处理逻辑
Do update account limit
Return Request #ibatis处理逻辑
Return session #ibatis处理逻辑
Sleep 1s #增加的代码,为了便于看出效果
Get session #ibatis处理逻辑;在这里可能出现拿不到session而阻塞,但这时其实线程是有数据库连接的,因为transaction未断开,不应该出现阻塞
Get Request #ibatis处理逻辑
Insert account transaction information #预计账务流水
Return Request #ibatis处理逻辑
Return session #ibatis处理逻辑
End Transaction #spring逻辑
我们可以看出由于iBatis在每次数据库操作开始都会请求session,而在每次数据库执行完成都会释放session。如果拿不到锁就会阻塞等待。这在仅仅是查询时没问题,但是在更新等事务中就会有问题,会导致数据库行锁,而且会影响到这条记录相关的所有交易。
后来我们仔细阅读了iBatis的代码,包括spring对于iBatis处理封装的逻辑,发现其实:maxRequests、maxSessions、maxTransactions这三个参数只有maxSession是有用的,因为maxRequest都是在session中进行获取,所以其取值不会比session大,而transaction是spring负责管理的,所以此参数也无效。这三个参数在iBatis的2.3.1版本中已经去掉了,可能也是因为这个问题。
基于上述分析我们决定使用数据库连接作为限制条件(通过使maxSession值大于数据库连接数),放弃使用session来进行控制。这个事也让我明白了在事务中尽量不要使用锁(特别是无等待时间的锁),不管是显示的还是隐式的(框架的)。关于jdbc的超时设置可以见参考资料 。
参考资料: