SQL Server MERGE 语句在并发场景下的锁升级问题

发布时间 - 2026-01-31 00:00:00    点击率:
MERGE会触发锁升级而非仅行锁,因其默认对目标表扫描范围加意向锁(IX),当预估或实际处理行数超阈值(约5000行)时,SQL Server自动将行锁升级为页锁(PAG)或表锁(TAB);即使只更新一行,若扫描6000行才定位到,仍可能升级。

为什么 MERGE 会触发锁升级而不是行锁

MERGE 在执行时默认对目标表(target_table)的扫描范围加意向锁(IX),如果优化器预估要处理的行数较多,或实际执行中触发了锁数量阈值(通常是 5000 行左右),SQL Server 会自动将大量行锁升级为页锁(PAG)甚至表锁(TAB)。这不是语句写法问题,而是锁管理器的主动行为——哪怕你只 UPDATE 一行,只要扫描了 6000 行才找到它,就可能被升级。

常见现象包括:其他会话对同一表的 INSERTUPDATE 被长时间阻塞,sys.dm_tran_locks 中看到大量 PAGTAB 类型锁,且 resource_description 显示为整页或全表。

  • 确保 ON 条件列有高效索引(最好是唯一索引或主键),避免全表扫描
  • 避免在 USING 子句中使用未索引的视图、函数表达式或计算列作为匹配依据
  • OPTION (LOOP JOIN) 强制嵌套循环连接,可限制扫描范围(但需确认驱动表足够小)

MERGE 的 WHEN MATCHED 和 WHEN NOT MATCHED 实际加锁差异

WHEN MATCHED 执行 UPDATE 时,SQL Server 会对匹配到的**每一行先加 U 锁(更新锁)再升级为 X 锁**;而 WHEN NOT MATCHED 执行 INSERT 时,会在插入位置加 IX + X 锁,但若聚集索引不连续(如 GUID 主键),可能引发页分裂并扩大锁影响范围。

关键点在于:MERGE 不是原子性地“先查再定操作”,而是在连接阶段就锁定所有潜在参与匹配的行。这意味着即使某行最终走的是 INSERT 分支,它在 ON 匹配扫描过程中已被锁住。

  • 不要在 WHEN MATCHED 中更新非索引列以外的大字段(如 varchar(max)),会延长 X 锁持有时间
  • 若业务允许,把 INSERT 拆出来单独做(用 NOT EXISTS + INSERT),可绕过 MERGE 的批量扫描锁
  • 测试时用 DBCC TRACEO

    N(1200, -1)
    查看实际锁申请序列(生产环境慎用)

如何验证当前 MERGE 是否已发生锁升级

直接查动态管理视图比看执行计划更可靠。执行 MERGE 后立即运行:

SELECT resource_type, resource_description, 
       request_mode, request_status, COUNT(*) cnt
FROM sys.dm_tran_locks 
WHERE resource_database_id = DB_ID()
  AND resource_associated_entity_id = OBJECT_ID('your_target_table')
GROUP BY resource_type, resource_description, request_mode, request_status;

如果结果中 resource_type 出现大量 PAGTAB,且 request_modeXU,基本确认发生了锁升级。注意:KEY 类型表示行锁(正常),OBJECT 表示整个堆/索引被锁(危险信号)。

  • 检查 sys.dm_exec_requests 中该会话的 wait_type,常见如 LCK_M_X 长时间等待说明锁冲突严重
  • 对比加索引前后 resource_description 中的页号(如 (1:123456))是否从分散变集中
  • 避免依赖 SSMS 的“包含实际执行计划”——它不显示锁升级过程,只反映优化器预期

替代方案:用独立 UPDATE + INSERT 替代 MERGE 的真实代价

拆成两步确实能规避 MERGE 的扫描锁,但引入新问题:两次访问目标表、丢失原子性、需手动处理竞态(比如两次查询之间数据被删又插)。不过在高并发写场景下,这往往是更可控的选择。

典型模式是:

BEGIN TRY
  UPDATE t SET ... 
  FROM target_table t 
  INNER JOIN #staging s ON t.id = s.id;
  
  INSERT INTO target_table (...) 
  SELECT ... FROM #staging s 
  WHERE NOT EXISTS (SELECT 1 FROM target_table t WHERE t.id = s.id);
END TRY

这里的关键不是“更快”,而是**锁行为可预测**:UPDATE 只锁匹配行,INSERT 只锁插入页,且两者可分别加索引优化。代价是应用层需容忍部分失败后重试,以及多一次索引查找。

  • 务必给 #staging 表建临时索引(尤其 ON 列),否则 NOT EXISTS 可能退化为嵌套循环+全表扫描
  • 如果业务要求强一致性,可用 UPDLOCK, HOLDLOCK 提示在 UPDATE 里提前锁定范围,但会降低并发度
  • 千万避免在事务里先 SELECTIF EXISTS ... UPDATE/INSERT —— 这种写法在并发下必然出现幻读和丢失更新
锁升级不是 MERGE 的 bug,而是 SQL Server 在“锁内存开销”和“并发吞吐”之间做的权衡。真正难处理的,是那些看似只改一行、却因缺少索引导致扫描上万行的隐式锁膨胀——它不会报错,只会让整个表慢慢变慢。


# ai  # 为什么  # sql  # Object  # if  # select  # 循环  #   # using  # 并发  # bug  # 升级为  # 长时间  # 两次  # 它不  # 的是  # 主键  # 行数  # 是在  # 已被  # 会在 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 网站制作软件有哪些,制图软件有哪些?  百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧  Java类加载基本过程详细介绍  Laravel Octane如何提升性能_使用Laravel Octane加速你的应用  Laravel怎么创建控制器Controller_Laravel路由绑定与控制器逻辑编写【指南】  Python数据仓库与ETL构建实战_Airflow调度流程详解  文字头像制作网站推荐软件,醒图能自动配文字吗?  JavaScript如何实现路由_前端路由原理是什么  韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐  购物网站制作费用多少,开办网上购物网站,需要办理哪些手续?  谷歌浏览器下载文件时中断怎么办 Google Chrome下载管理修复  Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能  Laravel如何生成API文档?(Swagger/OpenAPI教程)  网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?  大连网站制作公司哪家好一点,大连买房网站哪个好?  昵图网官方站入口 昵图网素材图库官网入口  免费视频制作网站,更新又快又好的免费电影网站?  浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】  怎么用AI帮你设计一套个性化的手机App图标?  Win11摄像头无法使用怎么办_Win11相机隐私权限开启教程【详解】  Laravel怎么进行浏览器测试_Laravel Dusk自动化浏览器测试入门  Laravel怎么生成URL_Laravel路由命名与URL生成函数详解  Laravel如何使用Eloquent进行子查询  Linux系统命令中screen命令详解  Laravel如何使用模型观察者?(Observer代码示例)  JavaScript如何操作视频_媒体API怎么控制播放  Laravel如何实现一对一模型关联?(Eloquent示例)  *服务器网站为何频现安全漏洞?  如何快速搭建高效可靠的建站解决方案?  Laravel怎么处理异常_Laravel自定义异常处理与错误页面教程  海南网站制作公司有哪些,海口网是哪家的?  打造顶配客厅影院,这份100寸电视推荐名单请查收  uc浏览器二维码扫描入口_uc浏览器扫码功能使用地址  高端网站建设与定制开发一站式解决方案 中企动力  微信小程序 wx.uploadFile无法上传解决办法  Android利用动画实现背景逐渐变暗  Internet Explorer官网直接进入 IE浏览器在线体验版网址  如何续费美橙建站之星域名及服务?  Laravel如何实现全文搜索_Laravel Scout集成Algolia或Meilisearch教程  如何在企业微信快速生成手机电脑官网?  三星网站视频制作教程下载,三星w23网页如何全屏?  BootStrap整体框架之基础布局组件  如何在IIS服务器上快速部署高效网站?  如何确保西部建站助手FTP传输的安全性?  北京专业网站制作设计师招聘,北京白云观官方网站?  Laravel如何发送系统通知?(Notification渠道示例)  如何快速上传建站程序避免常见错误?  Laravel路由Route怎么设置_Laravel基础路由定义与参数传递规则【详解】  怎样使用JSON进行数据交换_它有什么限制  如何用狗爹虚拟主机快速搭建网站?