-
SQLServer中如何找到和排除长时间阻塞的任务
- 网站名称:SQLServer中如何找到和排除长时间阻塞的任务
- 网站分类:技术文章
- 收录时间:2025-09-22 16:37
- 网站地址:
“SQLServer中如何找到和排除长时间阻塞的任务” 网站介绍
经常听人说xx很慢,xx卡死,如何找到什么东西被卡死了?如何优化SQL性能。今天就简单分享下自己的经验。
在应用系统反应慢或者卡死,无非下面两种情况。
应用系统自身的问题。
SQL查询读写互相阻塞。
第一种问题的排查方法很简单,即查看SQLServer数据库是否CPU 100%,是否有长时间被阻塞的查询进程,如果没有多半是应用程序的问题,或者网络问题。
第二种问题又分当前正在发生的阻塞,或者曾经发生过阻塞但是当前已工作正常。
查找正在发生的阻塞
可以用SQLServer自带的报表来快速定位,我们先模拟一个阻塞场景。
--在一个查询窗口运行下面的语句begin tranupdate Accountset name ='Test'where ID = 21--在另一个查询窗口运行下面的语句select * from Accountwhere ID = 21
我们可以看到Select的查询正在被阻塞。现在按下图方法打开“所有正在阻塞的事务”报表。
我们可以看到,Session ID = 164的 进程正在被阻塞。阻塞者是 Session ID = 159的进程。
同时我们可以用一下SQL来显示这两个进行的详细信息
WITH cteBL (session_id, blocking_these) AS(SELECT s.session_id, blocking_these = x.blocking_these FROM sys.dm_exec_sessions sCROSS APPLY (SELECT is(convert(varchar(6), er.session_id),'') + ', 'FROM sys.dm_exec_requests as erWHERE er.blocking_session_id = is(s.session_id ,0)AND er.blocking_session_id <> 0FOR XML PATH('') ) AS x (blocking_these))SELECT s.session_id, blocked_by = r.blocking_session_id, bl.blocking_these, batch_text = t.text, input_buffer = ib.event_info, *FROM sys.dm_exec_sessions sLEFT OUTER JOIN sys.dm_exec_requests r on r.session_id = s.session_idINNER JOIN cteBL as bl on s.session_id = bl.session_idOUTER APPLY sys.dm_exec_sql_text (r.sql_handle) tOUTER APPLY sys.dm_exec_input_buffer(s.session_id, ) AS ibWHERE blocking_these is not or r.blocking_session_id > 0ORDER BY len(bl.blocking_these) desc, r.blocking_session_id desc, r.session_id;
我们可以清楚看到,164被159阻塞,159的blocked_by是空,所以他是阻塞的头。input_buffer中我们可以看到阻塞正在执行的SQL语句,找到这个语句在应用程序中的位置,考虑这个语句为什么需要执行这么长时间?解决办法我们后面来讲。
因为大部分生产情况需要立即解决问题,所以现在我们用简单粗暴的情况先解决这个问题,执行Kill命令。(在笔者见到的很多这类情况,程序员无法排查出具体原因直接粗暴的重启数据库的不在少数)
--执行kill先杀死导致阻塞的进程Kill 159
我们可以看到杀掉阻塞的进程后,SELECT语句立即返回了。阻塞问题暂时解决。
查找历史发生的阻塞
笔者见过很多人遇到长时间阻塞导致应用响应缓慢时候,病急乱投医,指望一个SQLServer高手来了,马上就能问题抓出来。
排查是否是阻塞导致的问题,首先要排查下面的问题:
是否是服务器CPU高导致
在服务器上执行一个SQL是否缓慢。
网络正常。
如果以上几个都正常,那多半是阻塞问题导致。
SQLServer默认是不捕获历史的长时间阻塞的问题的,这里需要新建一个SQLServer的扩展事件。第一步命名,第二步选择不使用模板,第三步搜索block,选择blocked_process_report
然后选择几个最重要的指标,sql_text, session_id
下一步,下一步。注意在最后一步选择将数据保存到文件。点击完成。
这时我们在扩展事件里面可以看到block_report已经创建好了。
最后不要忘记设置阻塞捕获的时间的阈值。数据库默认是不会启用阻塞捕获,所以一定要启用设置这个阈值。
下面的示例将 blocked process threshold
设置为20
秒,超过该阈值将为阻塞的每个任务生成阻塞的进程报告。
sp_configure 'show advanced options', 1 ;GORECONFIGURE ;GOsp_configure 'blocked process threshold', 20 ;GORECONFIGURE ;GO
好了,我们再此运行之前的SQL语句(不在赘述),然后再去查看block_report会发生什么。
打开报告,可以看到查询语句被 一个未提交的 插入语句阻塞。(抱歉这里用了别人的图,笔者电脑在写这篇的时候突然这个report无法运行)只要找到这个语句在程序中哪里使用了,优化他就可以解决阻塞问题。
解决阻塞问题具体办法
常见可能导致长时间阻塞的原因无非以下几种
长时间在运行的事务。例如一个长时间Update\Insert大量数据记录的SQL语句。
这类等待会等到事务运行完毕后自动解除。应该优化和避免这类大量Update/insert语句同其他SELECT语句共同执行。
也有可能查询采取了更高的事务隔离级别,例如不可重复读的大量数据提取。导致大量数据行被加锁,阻塞Update/Insert语句执行,客户端呈现超时或卡死的现象。解决办法就是将这些长时间运行的事务错开执行,或者将查询大量数据的查询放入到专用的报表服务器。
事务一直保持打开,但是处于休眠状态。这类情况就是本文所演示的,一个长期未提交的事务,一直保持表上的行的排他锁导致的。
可能是程序未正确处理异常,导致一个未提交事务。例如.net 代码中未正确使用连接池,开启了事务但是出现异常后未显示rollback事务。通过报表,找到发出这个语句的具体位置,检查代码是否未处理异常状态下的回滚。
客户端未提取全部数据就断开连接。
连接开启了隐式事务,出现异常但是未显示关闭,导致孤立的未提交事务,同第二点。
常用的优化手段
将大量的查询(通常是类似报表的操作) 转移到单独的服务器,使用数据库复制技术或者高可用技术,生成一台副本数据库,在副本数据库上执行大数据量查询。
为长时间的查询添加索引。很多复杂查询没有充分利用索引,导致扫描这会导致阻塞。例如一个批量更新的Update语句中包含一个Where条件,但是这个条件没有索引,查询不得不表扫描这些数据,这将阻塞其他进程访问这张表。
正确使用连接池,如果开启事务,要正确处理异常,在异常里面回滚事务。
避免使用隐式事务。
存储过程里面也要捕获异常,并回滚事务。
尽量缩小查询或更新的数据量。例如不要批量查询或更新几十上百万的数据。如果非要这样做,请考虑方案是否合理,或者放在夜间执行。
如果你的系统就是高频率的查询和更新操作并行的。例如上千上万用户高频率增删改,并且发现系统阻塞很严重,那么可以考虑将数据设置为乐观并发模式即READ_COMMITTED_SNAPSHOT 模式,该模式启用后无需更改程序既可显著解决阻塞问题。因为行版本管理,读写不会互相阻塞(依然会写写阻塞),所以并发大大提高。在采用复杂的读写分离模式前,请考虑此模式,可以避免解决方案的复杂性。
- 最近发表
- 标签列表
-
- mydisktest_v298 (35)
- sql 日期比较 (33)
- document.appendchild (35)
- 头像打包下载 (35)
- 梦幻诛仙表情包 (36)
- java面试宝典2019pdf (26)
- disk++ (30)
- 加密与解密第四版pdf (29)
- iteye (26)
- centos7.4下载 (32)
- intouch2014r2sp1永久授权 (33)
- jdk1.8.0_191下载 (27)
- axure9注册码 (30)
- 兔兔工程量计算软件下载 (27)
- ccproxy破解版 (31)
- aida64模板 (28)
- engine=innodb (33)
- shiro jwt (28)
- segoe ui是什么字体 (27)
- head first java电子版 (32)
- clickhouse中文文档 (28)
- jdk-8u181-linux-x64.tar.gz (32)
- 计算机网络自顶向下pdf (34)
- -dfile.encoding=utf-8 (33)
- jdk1.9下载 (32)