项目背景:Resin项目 Spring+hibernate框架
起因:
线上项目客户反映项目网址进不去,后台看代码一直在报错,并且是数据库连接池达到了上限
但是最近是没有更新线上代码,只动过数据库插入过几千条数据以及更改状态信息,遂重新打断点看了有关业务的流程,排除了是数据的问题
翻阅报错日志最终定位到是发生锁表的问题但是没有任何的有关哪张表的信息,并且数据库也在客户那边没有权限,每次报错只能重启项目
确认问题:死锁发生以及锁表
解决过程
因为定位不到是哪里的方法出的问题,由于当时数据库权限拿不到sql,刚好框架使用的Druid于是搭建了一个监控面板。通过Spring.xml文件
1.搭建Druid监控面板
Spring.xml文件
aop可以监控方法的调用
然后再在web.xml文件配置路径
<servlet><servlet-name>druidStatViewServlet</servlet-name><servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class><init-param><param-name>loginUsername</param-name><param-value>admin</param-value></init-param><init-param><param-name>loginPassword</param-name><param-value>admin</param-value></init-param><init-param><param-name>urlPattern</param-name><param-value>/druid/*</param-value></init-param><init-param><param-name>resetEnable</param-name><param-value>false</param-value></init-param></servlet><servlet-mapping><servlet-name>druidStatViewServlet</servlet-name><url-pattern>/druid/*</url-pattern></servlet-mapping><!-- 启用Druid的Web监控过滤器 --><filter><filter-name>DruidWebStatFilter</filter-name><filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class><init-param><param-name>exclusions</param-name><param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value></init-param><init-param><param-name>sessionStatEnable</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>DruidWebStatFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
2.持续监控
搭建完成后便能看见sql情况以及调用的方法层情况
3.临时使用
3.1 造成项目问题--接口堵塞,sql堵塞
搭建完成后发现是各个接口一直堵塞导致连接池达到上限,没有放开
接口方法
sql sql这边能看到只有一个插入语句发生堵塞,于是怀疑是涉及到这张表出了问题,因为是线上的项目,用A表代替,而与A表高关联的表有另一张B表,于是去验证了两张表的问题,执行插入语句
3.2 确认锁表
两张表都去执行插入语句,发现时间都很长且插不进去,其他表正常,确认A表B表发生锁表
为了能让客户能正常打开项目而不是一直转圈
3.3 扩大连接池上限
Spring-hibernate.xml文件
线上是直接调整到了100 (这是本地文件并没有做更改,只是示例)
3.4 缩短会话存活时间
存活时间,线上也是直接缩短到了两分钟(这是本地文件并没有做更改,只是示例)
3.5 降低上面方法发生堵塞的事务隔离级别
先将上面堵塞方法的事务隔离级别降到了读未提交
@Transactional(readOnly = false, isolation = Isolation.READ_UNCOMMITTED)
3.6 解决了连接池达到上限问题
降低事务隔离级别后,方法接口并不会再继续堵塞,但是后续Insert语句依旧会挂在sql监控那,就是一直在执行中,说明还是有AB表还是有锁表出现
4.继续排查
4.1 持续观察方法的并发量--排除并发引起的锁表
观察方法的最大并发量,并不像之前那样因为堵塞并发量持续上升,而是很正常的1或者2,并且是隔一段时间发生锁表,且未发生锁表的时候各个方法都正常执行,插入sql也正常执行,于是觉得可能并不是并发引起的锁表甚至不是这个系统引起的问题,感觉像是数据库那边出的问题
4.2 翻阅报错日志以及插入记录--整点锁表
查询了报错日志以及插入记录,发现都会有一个规律,基本上都集中在整点附近发生锁表,于是在整点进行了验证,AB表会在整点进行锁表
4.3 翻阅线上的每小时执行一次的定时任务--排除主系统定时任务的问题
翻阅了线上的定时任务,唯一一个每小时执行一次的定时任务和那两张表没有关联遂排除,到这时候已经基本上排除了锁表和主系统没有关联
4.4 验证主系统和锁表无关
在晚上没有人使用的时候将主系统关闭,再次验证整点锁表的问题,依旧锁表,排除主系统的问题
5.联系云数据库提交工单
5.1 工程师反馈慢sql
因为数据库没有权限,提交了工单进行解决,那边数据库工程师反馈是一个慢sql缺失了索引导致需要进行全表扫描导致锁表
5.2 为慢sql构建索引--解决锁表问题
先直接为这个sql构建了索引,在整点进行了验证,不会锁表
5.3 查找慢sql位置
在系统中通过全局搜索并没有找到有类似的sql,并且之前也排除了锁表问题与主系统无关
5.4 反映系统并没有该sql--要到了数据库权限
有权限之后,SQLserver执行下列语句,可以查到慢sql的历史记录
SELECT qs.sql_handle,qs.execution_count,qs.total_worker_time AS TotalCPU,qs.total_elapsed_time AS TotalElapsedTime,qs.total_logical_reads AS TotalReads,qs.total_logical_writes AS TotalWrites,qs.creation_time,qs.last_execution_time,qt.text AS SQLText
FROM sys.dm_exec_query_stats qsCROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt
WHERE qs.total_elapsed_time > 10000000
ORDER BY qs.creation_time DESC;
可以看到依旧是在整点附近执行
5.5 整点执行全局会话sql--查询sql来源
下列sql可以查看sql的来源
SELECT qs.sql_handle,qs.execution_count,conn.client_net_address, -- 客户端IPses.program_name, -- 程序名称ses.host_name, -- 客户端机器名ses.login_name, -- 登录用户qt.text AS SQLText
FROM sys.dm_exec_query_stats qsCROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qtOUTER APPLY (SELECT TOP 1 *FROM sys.dm_exec_requests req WHERE req.sql_handle = qs.sql_handle ORDER BY req.start_time DESC) reqLEFT JOIN sys.dm_exec_connections conn ON req.connection_id = conn.connection_idLEFT JOIN sys.dm_exec_sessions ses ON conn.session_id = ses.session_id
WHERE qs.total_elapsed_time > 10000000;
执行如下,可以看到整点时发起会话的主机--用G代替
6 确认sql来源服务器
向上询问了主管,确实我们有这个服务器,然后进入到服务器当中,查看各个开启的服务,翻阅其中的定时任务,发现其中有个服务确实是有点像关联其中的sql
6.1 反编译Class文件
Java decompiler online
通过上面网址,反编译了Dao层的sql语句
6.2 找到问题sql
找到了sql所在的文件,询问主管,注释掉了定时任务
7. 问题解决
总结
任何报错都事出有因
处理死锁问题思路
确认发生死锁的方法--查看并发量--并发量大--大概率是同时操作改张表引发的问题--加乐观锁或者其他关键字可解决--必须明确事务的执行顺序
并发量小--大概率是sql引起的问题--找到具体sql--优化sql,重建索引(索引失效引起)