根本原因是事务持有锁时间过长,导致后续查询无法获取所需锁;常见诱因包括全表扫描、缺失索引、事务内混入非DB操作及锁升级。

UPDATE长时间运行导致阻塞其他查询
根本原因是事务持有锁时间过长,让后续的 SELECT、UPDATE 或 DELETE 无法获取所需锁(比如 S 锁或 U 锁)。尤其当 UPDATE 扫描大量行、缺少有效索引、或在事务中混入非数据库操作(如 HTTP 调用、文件读写)时,锁会持续几十秒甚至几分钟。
实操建议:
- 检查执行计划:确认
UPDATE是否走到了预期索引;若出现Clustered Index Scan或Table Scan,优先加 WHERE 条件覆盖索引 - 拆分大更新:用 TOP + 循环分批提交,例如
UPDATE TOP (5000) ... WHERE ...,每批后COMMIT - 避免在事务内做耗时逻辑:把日志记录、消息发送等移出
BEGIN TRAN块 - 确认隔离级别:如果业务允许,可在会话级临时设为
READ COMMITTED(默认),而非REPEATABLE READ或更高
两个UPDATE事务互相等待形成死锁
典型场景是事务 A 先更新行 X 再更新行 Y,而事务 B 恰好相反——先 Y 后 X。SQL Server 检测到循环等待后,会选一个作为“受害者”回滚,并抛出错误 1205(Deadlock victim)。
实操建议:
- 统一访问顺序:所有业务逻辑中,对多表/多行的修改必须按固定字段顺序(如按主键升序)批量处理
- 减少事务粒度:把一个大事务拆成多个小事务,中间不跨业务语义断开即可
- 捕获并重试:应用层需捕获
SqlException.Number == 1205,延迟 50–200ms 后重试(注意幂等性) - 禁用锁升级(谨慎):对高频小更新表,可设
ALTER TABLE t1 SET (LOCK_ESCALATION = DISABLE),防止行锁自动升为页锁或表锁
UPDATE被其他事务的S锁长期阻塞
常见于报表类 SELECT 查询用了 WITH (TABLOCK, HOLDLOCK) 或运行在 SERIALIZABLE 隔离级别下,导致整张表被共享锁长期占用,后续 UPDATE 只能卡在等待 U/X 锁阶段。
实操建议:
- 查阻塞源头:运行
SELECT blocking_session_id, wait_type, wait_resource FROM sys.dm_exec_requests WHERE blocking_session_id 0 - 限制长查询:对报表作业设置
SET LOCK_TIMEOUT 30000(30 秒超时),避免无限持锁 - 改用快照隔离:启用
ALLOW_SNAPSHOT_ISOLATION ON,让读操作不争抢写锁(需应用适配,且 tempdb 压力会上升) - 避免显式锁提示滥用:删掉不必要的
WITH (HOLDLOCK)、WITH (TABLOCK)
如何快速定位正在发生的锁与死锁
靠肉眼观察慢或报错再处理,永远晚于问题发生。必须建立主动发现机制。
实操建议:
- 开启死锁追踪:执行
DBCC TRACEON(1222, -1)(SQL Server 2005+),死锁信息会写入 errorlog,含完整资源和会话堆栈 - 查实时锁状态:用
sys.dm_tran_locks关联sys.dm_exec_sessions和sys.dm_exec_requests,过滤resource_database_id = DB_ID()和request_status = 'WAIT' - 用扩展事件替代 Profiler:创建 xEvent 会话监听
xml_deadlock_report,比老式 SQL Trace 更轻量、更稳定 - 别依赖“锁优化”控制台:RDS 等云平台的死锁页面有地域和版本限制(如不支持 2008 R2),且延迟高,不能替代实时诊断

















