|
昨天凌晨两点,杀死锁我盯着监控屏上那串不断跳动的语句数字——数据库阻塞率飙到87%。生产库挂了,杀死锁客服电话被打爆。语句查了锁等待,杀死锁罪魁祸首是语句一条跑了40分钟的报表查询,它把订单表锁得死死的杀死锁,所有写入全堵在门口。语句那时候我脑子里只有一个念头:杀。杀死锁杀掉那个会话,语句让业务先活过来。杀死锁 SQL Server里杀锁的语句指令其实就一条:KILL。但怎么找到那个该杀的杀死锁SPID,才是语句真正的技术活。你不能瞎杀,杀死锁把正常的后台任务也误伤了,那比锁死更惨。 先看锁。执行这条:SELECT * FROM sys.dm_exec_requests WHERE blocking_session_id > 0。它会告诉你哪个会话被哪个会话堵住了。重点看blocking_session_id那一列,非零值就是祸根。比如你看到SPID 62堵了SPID 73、74、81,那62就是你要找的目标。 别急着动手。先看清楚62到底在跑什么。用DBCC INPUTBUFFER(62),能显示这个会话最后提交的那条SQL。如果是一条UPDATE忘了提交事务,或者一条全表扫描的SELECT,那基本可以确定是它了。我见过最离谱的一次,是有人开了个查询窗口,跑了条DELETE,然后人下班走了,事务一直开着没提交。锁就那样挂了一整夜。 确认无误后,执行KILL 62。就这么简单。但注意,如果那个会话正在执行大型事务,KILL之后它可能不会立刻消失,而是进入“回滚中”状态。这时候你去查sys.dm_exec_requests,会看到它的状态是ROLLBACK。别慌,这是它在清理自己搞出来的烂摊子。回滚时间取决于事务大小,有时候几分钟,有时候半小时。你可以用KILL 62 WITH STATUSONLY查看回滚进度百分比。 再说个坑。你以为杀掉了就完事了?不。如果锁是因为索引缺失导致的,杀掉之后过五分钟又堵上了。那种情况你得去查执行计划,看看是不是全表扫描把行锁升级成了表锁。行锁升表锁这事,SQL Server干得很隐蔽,你不看lock_escalation相关事件根本发现不了。我处理过一个案例,明明就更新了200行数据,结果整个表被锁了,就是因为那张表没有主键,锁升级阈值被触发。杀锁只是治标,补索引才是治本。 有时候你连KILL都执行不了。为什么?因为那个阻塞会话本身正在等待某种资源,比如它在等一个磁盘IO超时。这时候KILL命令会卡住,一直处于“已取消”状态。办法是找到那个会话的session_id,然后去sys.dm_exec_sessions里看它的status。如果是sleeping,但last_request_end_time很久没更新,那可能是个孤儿连接。这种只能通过杀掉对应的spid对应的进程来强制回收,比如在操作系统层面用taskkill /F /PID干掉那个sqlservr子进程——但这招风险极高,非必要别用,搞不好整个实例会崩。 最后给你个习惯建议。别等到锁死了才去杀。写个监控脚本,每30秒跑一次sys.dm_exec_requests,发现阻塞超过5秒就自动发告警。我现在的生产环境就是这套逻辑,阻塞超过10秒直接自动KILL掉那个阻塞源(白名单里的报表账号除外)。当然,自动杀锁要谨慎,得加条件判断,只杀那些wait_type为LCK_M_*且运行时间超过阈值的会话。这样既保住了业务连续性,又不会误伤正在跑长事务的合法操作。 杀锁不是目的,让系统活着才是。工具就那几行代码,但什么时候杀、杀完怎么防,这才是DBA真正的功夫。 |