在SQL Server中如何解决大表UPDATE引发的死锁问题?

轻枫同学_9916

轻枫同学_9916

2026-10-23

720人浏览

原创

根本原因是不同事务对相同资源加锁顺序不一致,形成循环等待;需统一in列表顺序、确保索引有效、避免范围查询,并捕获1213错误重试。

在sql server中如何解决大表update引发的死锁问题?

为什么大表UPDATE容易触发死锁

根本原因不是“UPDATE本身危险”,而是它在高并发下极易形成「不同事务按不同顺序访问相同资源」的循环等待。比如两个会话同时执行 UPDATE orders SET status = 1 WHERE customer_id IN (101, 202),但执行计划因统计信息偏差或索引选择不同,一个走 IX_customer_id 扫描,另一个走聚集索引扫描——它们加锁的行顺序就可能完全相反,U锁 → X锁升级过程中互相卡住。

特别注意:死锁常发生在有多个非聚集索引且含 INCLUDE 列的表上。当 UPDATE 修改了 INCLUDE 列(如 varchar(max)),SQL Server 必须同时维护主键页和所有相关非聚集索引页,锁范围扩大、锁获取顺序更难统一,死锁概率陡增。

用 UPDATE TOP + ORDER BY 分批控制锁粒度

这是最直接、无需改应用逻辑的落地方案。关键不在“分批”,而在“可控加锁顺序”:

  • 必须显式包裹 BEGIN TRANSACTION 和 COMMIT,否则每批都是独立事务,锁频繁释放/申请反而加重争抢
  • TOP(n) 后必须跟 ORDER BY(推荐主键或唯一索引列),否则同一批可能重复更新或漏更,且无法保证锁顺序一致
  • 批次大小设为 1000–5000:小于 1000 提交太密,日志写压力大;大于 5000 容易触发锁升级(行锁 → 页锁 → 表锁)
  • 终止条件用 IF @@ROWCOUNT = 0 BREAK,别依赖预估总行数——WHERE 条件可能随更新动态变化(如 status 从 pending 变成 processed)

示例:

WHILE 1 = 1
BEGIN
    BEGIN TRANSACTION
    UPDATE TOP(2000) orders 
    SET status = 'shipped' 
    WHERE status = 'pending' 
    ORDER BY id;
    
    IF @@ROWCOUNT = 0
    BEGIN
        COMMIT;
        BREAK;
    END
    
    COMMIT;
END

查清谁在堵你,而不是只优化那条UPDATE

90% 的“UPDATE死锁”问题,根源不在那条语句本身,而在它前面有个长事务或未提交操作占着锁不放。先定位阻塞源头,比调优SQL更高效:

  • 运行 SELECT blocking_session_id, wait_type, wait_resource FROM sys.dm_exec_requests WHERE blocking_session_id > 0,一眼看出哪个 SPID 是根因
  • 对 blocking_session_id 查 sys.dm_exec_sql_text,确认它执行的是什么语句(常见坑:应用层开了事务但忘了 COMMIT,或 ORM 自动开启的长事务)
  • 如果阻塞链很深,用 sys.dm_os_waiting_tasks 看等待堆栈,重点关注 wait_type 是 LCK_M_U 还是 LCK_M_X,对应更新锁或排他锁争抢

避免索引设计埋下的死锁隐患

很多死锁不是代码问题,是表结构设计导致的。典型陷阱:

  • 非聚集索引带 INCLUDE 大字段(如 varchar(max)):UPDATE 该字段时,必须同时锁聚集索引和所有含该字段的非聚集索引页,锁路径变长、顺序难控
  • 多个非聚集索引覆盖相同查询条件但顺序不同:比如 IX_a_b 和 IX_b_a,不同事务可能分别走这两个索引,加锁顺序天然冲突
  • 缺失覆盖索引导致查询走聚集索引扫描:UPDATE 和 SELECT 同时扫全表,行锁顺序完全不可预测

验证方式:对问题语句跑 SET STATISTICS XML ON,看执行计划是否出现“Key Lookup”或“Clustered Index Scan”——这些是锁范围失控的信号。

真正难处理的从来不是那条报错的 UPDATE,而是它背后那个没被发现的长事务、那个带 INCLUDE (text) 的索引、或者两个会话对同一组数据却按相反顺序加锁的隐式行为。盯住锁的来源和顺序,比反复重写SQL更接近本质。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
sqlserver和mysql区别
sqlserver和mysql区别

SQL Server和MySQL是两种广泛使用的关系型数据库管理系统。它们具有相似的功能和用途,但在某些方面存在一些显著的区别。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.11

5751

4

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

2023.06.29

2785

3

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2023.08.14

3981

10

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

2023.08.31

3091

3

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.05

987

5

vb中怎么连接access数据库
vb中怎么连接access数据库

vb中连接access数据库的步骤包括引用必要的命名空间、创建连接字符串、创建连接对象、打开连接、执行SQL语句和关闭连接。本专题为大家提供连接access数据库相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.09

2707

5

数据库对象名无效怎么解决
数据库对象名无效怎么解决

数据库对象名无效解决办法:1、检查使用的对象名是否正确,确保没有拼写错误;2、检查数据库中是否已存在具有相同名称的对象,如果是,请更改对象名为一个不同的名称,然后重新创建;3、确保在连接数据库时使用了正确的用户名、密码和数据库名称;4、尝试重启数据库服务,然后再次尝试创建或使用对象;5、尝试更新驱动程序,然后再次尝试创建或使用对象。

2023.10.16

2707

4

vb连接access数据库的方法
vb连接access数据库的方法

vb连接access数据库方法:1、使用ADO连接,首先导入System.Data.OleDb模块,然后定义一个连接字符串,接着创建一个OleDbConnection对象并使用Open() 方法打开连接;2、使用DAO连接,首先导入 Microsoft.Jet.OLEDB模块,然后定义一个连接字符串,接着创建一个JetConnection对象并使用Open()方法打开连接即可。

2023.10.16

2893

3

vb连接数据库的方法
vb连接数据库的方法

vb连接数据库的方法有使用ADO对象库、使用OLEDB数据提供程序、使用ODBC数据源等。详细介绍:1、使用ADO对象库方法,ADO是一种用于访问数据库的COM组件,可以通过ADO连接数据库并执行SQL语句。可以使用ADODB.Connection对象来建立与数据库的连接,然后使用ADODB.Recordset对象来执行查询和操作数据;2、使用OLEDB数据提供程序方法等等。

2023.10.19

2521

3

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.9万人学习