线上数据库死锁问题排查

今天记录一下在项目上遇到的一个问题,和排查解决过程。

数据库死锁

我们这个项目是 springboot + hibernate + SQL server 基础架构的。

发现问题

在我们的一个地图配置功能页面,修改配置信息点击保存后,接口一直是处理中,被阻塞一直不能完成。
image.png

解决问题

  1. 首先查看服务是不是挂了,首先进服务器看服务状态和负载,发现都正常。所以排查不是服务整体挂了。
  2. 然后点点其他功能,发现也正常,发现只有跟这个配置相关的页面都阻塞打不开了。这就很明显了,不是代码的问题,可能是数据库的问题,于是连接数据库打开这个页面设计的表,发现数据库这个表也打不开,阻塞。ok这就确定了是数据库的问题,数据库锁表了。
  3. 赶快查chatgpt,搜索一下怎么查询数据库锁,然后就开始查询如下图:
    image.png截图很明显,pid 286 给表SESGIS_BASIC_INFO_SETS加了LX(排他锁),pid 273 给一共10张表加了LS(共享锁)其中就有SESGIS_BASIC_INFO_SETS这张表。
  4. 这原因已经找到了,是会话273一开始加了共享锁,然后会话286要加排他锁,一个表加了排他锁就不能加共享锁,加了共享锁就不能加排他锁,所以他们永远阻塞到这了。
  5. 行,到这里问题定位了,先解决在排查。很简单直接 kill 286 ;
  6. 回去查看发现,页面确实不阻塞了,爆出了错误,但是至少,其他关联这个表的页面都能正常展示了。

排查问题

先保证线上服务能正常使用了,才能后续再来排查问题。

  1. 这就根据这个保存接口看代码,可以测试环境断点调试。很快定位到第2430行代码被阻塞。下面截取这个方法的两个关键位置代码。
    image.png
    image.png
    第一个截图是这个方法开始 先对SESGIS_BASIC_INFO_SETS表做了数据更新,第二个截图是在这个方法的后半部分,又调用了公司封装的一个工具类JSONUtil.generateObjectFromJson方法,传入了数据库的连接session工厂,所以这里面一定也有数据库操作。
  2. 在看这个方法里面image.png
    这里面调用了我们的session工厂的openSession,这是再创建一个新的会话,这就是关键,这整个大方法本来就在一个事务里的, 这是在一个事务里开了两个session访问数据库,刚刚说的第一张截图中方法开始就对这个表做了更新操作,这里就是要加排他锁的,然后在下面又要加共享锁去读取,那肯定要等待排他锁释放才能加共享锁,但是你们在一个事务里, 你读取数据不完成我事务也没法提交啊。所以这就形成了最终的死锁。

这就发现了死锁的原因是这个底层的JSONUtil工具类导致的。

根本解决问题

这就彻底解决这个问题吧。
这个JSONUtil工具类中openSession就开辟了新的会话,我们只要它不开启新的,我已经传递了工厂给你,你就用当前的就好了,直接调用getCurrentSession()就可以了,但是这是底层封装好的,而且是别的部门封装的,咱们也不能改,也不能改,后来询问了地图开发的同事,它们遇到过这个问题没?
结果它们其实已经又了解决办法,要把数据库的事务隔离级别升级成:**READ COMMITTED SNAPSHOT(读已提交快照)**这个事务隔离级别的查询语句不会加锁。

然后我查看原本数据库配置:image.png
原来事务隔离级别isolation level:RC,而且还有一个重要配置lock_timeout:-1,这个是锁超时等待时间,-1就是一直等,导致我们页面一直阻塞,这个参数要设置一个合理时间,当发生死锁时,数据库也会在这个时间后自动报错释放锁。

最后关闭数据库服务器的防火墙,断开所有连接后,把事务隔离级别调整成READ COMMITTED SNAPSHOT 再打开防火墙,就都可以恢复正常了。

总结

最后有三种解决方案:

  1. 在使用sessionFactory时,不要新建session,使用当前的session就可以了。
  2. 其实可以把方法开始的更新方法放到最后,这样就会先加共享锁,等查询都完成后,最后再加排他锁更新,就不会死锁了。
  3. 修改事务隔离级别到READ COMMITTED SNAPSHOT ,这样查询就不会加锁了。

欢迎关注我的博客:http://he-bi.cn/#/

### SQL Server 中死锁的诊断与解决 #### 死锁的原因分析 在 SQL Server 中,死锁通常是由多个会话相互持有对方所需的资源而引发的一种阻塞状态。常见的死锁诱因包括索引的并发创建或重建、长时间运行的事务和锁升级、以及不一致的锁定顺序[^1]。 #### 死锁的诊断方法 为了有效诊断死锁问题,可以采用以下几种技术: 1. **启用跟踪标志** 启用 `trace flag 1222` 可以捕获详细的死锁信息并将其记录到 SQL Server 的错误日志中。这些信息有助于识别涉及哪些对象、锁类型以及参与的进程 ID。 ```sql DBCC TRACEON(1222, -1); ``` 2. **使用扩展事件 (Extended Events)** 扩展事件是一种轻量级的性能监控工具,能够捕捉死锁图和其他相关信息。通过配置扩展事件会话,可以选择监听 `xml_deadlock_report` 事件来获取完整的死锁报告。 ```sql CREATE EVENT SESSION deadlock_capture ON SERVER ADD EVENT sqlserver.xml_deadlock_report; GO ALTER EVENT SESSION deadlock_capture ON SERVER STATE = START; ``` 3. **动态管理视图 (DMVs)** 动态管理视图提供了实时数据访问能力,用于查看当前系统的内部状态。特别是 `sys.dm_exec_requests` 和 `sys.dm_tran_locks` 这两个 DMV 能够帮助定位正在发生死锁的具体请求及其持有的锁资源[^3]。 ```sql SELECT r.session_id, r.blocking_session_id, t.resource_type, t.request_mode, t.request_status FROM sys.dm_exec_requests AS r INNER JOIN sys.dm_tran_locks AS t ON r.session_id = t.request_session_id WHERE r.blocking_session_id IS NOT NULL; ``` 4. **SQL Profiler 或 Extended Events 报告** 如果需要更直观的方式观察死锁行为,则可以通过 SQL Profiler 工具或者基于扩展事件生成的报表文件进行可视化分析。这种方法特别适合于开发环境中的调试工作。 #### 解决死锁的方法 针对已发现的死锁现象,可以从以下几个方面入手解决问题: - **优化查询逻辑** 确保所有事务遵循相同的访问路径,减少不同执行计划带来的潜在冲突风险;同时尽量缩短事务持续时间以降低加锁窗口期。 - **调整隔离级别** 使用较低级别的隔离机制(如读取提交而非可重复读),从而允许更多并发操作而不至于频繁争抢相同的数据页[^2]。 - **重新设计表结构或索引策略** 创建覆盖索引来满足常见查询需求,避免全表扫描过程中产生的大量行级锁竞争情况;另外考虑分区表技术分摊热点区域压力。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值