当前位置: 首页 > news >正文

【SQL server】不同平台相同数据库之间某个平台经常性死锁

生产环境死锁事故分析报告:从“通讯程序,请勿关闭!”到索引优化的全链路排查

时间:2025 年 11 月 10 日
系统:生产制造执行系统(上海冠邑 v2.0.8)
涉及模块:工单步骤耗时计算接口(GetExecuteStep4
影响范围:生产现场操作员频繁弹出“通讯程序,请勿关闭!”错误,导致数据无法刷新、工单详情页加载失败


一、问题发现:前端弹窗告警,业务中断

2025 年 11 月 10 日下午 16:30,运维人员反馈:
实时数采监控 页面时,突然弹出如下对话框:

在这里插入图片描述

图注:典型的 SQL Server 死锁异常提示窗口

该弹窗内容为:

System.Data.SqlClient.SqlException (0x80131904): 
事务(进程 ID 73)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。
  • 现象特征
    • 仅在高并发时段出现(如班次交接)
    • 弹窗阻塞 UI,用户无法继续操作
    • 操作日志显示 GetExecuteStep4() 方法调用失败
    • 本地开发环境无法复现,但生产环境持续发生

初步判断:数据库层面发生严重死锁,C# 应用作为“牺牲者”被强制回滚事务


二、开启死锁跟踪:捕获真实死锁现场

由于前端弹窗已暴露关键信息(Process ID 73, Deadlock victim),决定立即在生产 SQL Server 上开启 死锁跟踪

-- 开启全局死锁日志输出(写入 ERRORLOG)
DBCC TRACEON(1222, -1);

🔒 安全说明:该操作仅增加日志输出,无性能风险,且可随时关闭(DBCC TRACEOFF(1222, -1))。

约 15 分钟后,再次触发死锁,成功在 ERRORLOG 中捕获完整死锁图:

2025-11-10 19:42:24.12 spid49s     deadlock-list
2025-11-10 19:42:24.12 spid49s      deadlock victim=process1d81e31bc28
2025-11-10 19:42:24.12 spid49s       process-list
2025-11-10 19:42:24.12 spid49s        process id=process1d81e31bc28 taskpriority=0 logused=380 waitresource=PAGE: 5:1:164136  waittime=2844 ownerId=534979454 transactionname=implicit_transaction lasttranstarted=2025-11-10T19:42:20.803 XDES=0x1d80b788428 lockMode=S schedulerid=34 kpid=7036 status=suspended spid=119 sbid=0 ecid=0 priority=0 trancount=1 lastbatchstarted=2025-11-10T19:42:21.263 lastbatchcompleted=2025-11-10T19:42:21.263 lastattention=1900-01-01T00:00:00.263 clientapp=Microsoft JDBC Driver for SQL Server hostname=WIN-OE74EU9067J hostpid=0 loginname=sa isolationlevel=read committed (2) xactid=534979454 currentdb=5 currentdbname=TopMES_FM lockTimeout=4294967295 clientoption1=671156256 clientoption2=128058
2025-11-10 19:42:24.12 spid49s         executionStack
2025-11-10 19:42:24.12 spid49s          frame procname=adhoc line=1 sqlhandle=0x0200000069d9f53a3604f00819e671793e57bb20f3b1de220000000000000000000000000000000000000000
2025-11-10 19:42:24.12 spid49s     unknown     
2025-11-10 19:42:24.12 spid49s          frame procname=unknown line=1 sqlhandle=0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
2025-11-10 19:42:24.12 spid49s     unknown     
2025-11-10 19:42:24.12 spid49s         inputbuf
2025-11-10 19:42:24.12 spid49s     FETCH API_CURSOR0000000000000619    
2025-11-10 19:42:24.12 spid49s        process id=process1d81e3128c8 taskpriority=0 logused=380 waitresource=PAGE: 5:1:289677  waittime=2825 ownerId=534979742 transactionname=implicit_transaction lasttranstarted=2025-11-10T19:42:21.073 XDES=0x1d80b910428 lockMode=S schedulerid=33 kpid=6204 status=suspended spid=59 sbid=0 ecid=0 priority=0 trancount=1 lastbatchstarted=2025-11-10T19:42:21.170 lastbatchcompleted=2025-11-10T19:42:21.130 lastattention=1900-01-01T00:00:00.130 clientapp=Microsoft JDBC Driver for SQL Server hostname=WIN-OE74EU9067J hostpid=0 loginname=sa isolationlevel=read committed (2) xactid=534979742 currentdb=5 currentdbname=TopMES_FM lockTimeout=4294967295 clientoption1=671088672 clientoption2=128058
2025-11-10 19:42:24.12 spid49s         executionStack
2025-11-10 19:42:24.12 spid49s          frame procname=adhoc line=1 stmtstart=40 stmtend=1322 sqlhandle=0x02000000441f9309f307a0acfb16114cd92367be70936f440000000000000000000000000000000000000000
2025-11-10 19:42:24.12 spid49s     unknown     
2025-11-10 19:42:24.12 spid49s          frame procname=unknown line=1 sqlhandle=0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
2025-11-10 19:42:24.12 spid49s     unknown     
2025-11-10 19:42:24.12 spid49s         inputbuf
2025-11-10 19:42:24.12 spid49s     (@P0 nvarchar(4000))WITH CTE AS (SELECT id, wo_steps_id, node_time, time_tag, create_by, create_time, LAG(node_time) OVER (PARTITION BY wo_steps_id ORDER BY node_time) AS prev_node_time, LAG(time_tag) OVER (PARTITION BY wo_steps_id ORDER BY node_time) AS prev_time_tag FROM tb_steps_node) SELECT SUM(CASE WHEN time_tag = 2 AND prev_time_tag = 1 THEN CAST(CEILING(DATEDIFF(SECOND, prev_node_time, node_time) / 60.0) AS DECIMAL (10, 0)) WHEN time_tag = 4 AND prev_time_tag IN (1, 3) THEN CAST(CEILING(DATEDIFF(SECOND, prev_node_time, node_time) / 60.0) AS DECIMAL (10, 0)) ELSE 0 END) AS total_duration_minutes FROM CTE WHERE wo_steps_id = @P0 GROUP BY wo_steps_id            
2025-11-10 19:42:24.12 spid49s       resource-list
2025-11-10 19:42:24.12 spid49s        pagelock fileid=1 pageid=164136 dbid=5 subresource=FULL objectname=TopMES_FM.dbo.tb_steps_node id=lock1d7176e7b00 mode=SIX associatedObjectId=72057594101760000
2025-11-10 19:42:24.12 spid49s         owner-list
2025-11-10 19:42:24.12 spid49s          owner id=process1d81e3128c8 mode=SIX
2025-11-10 19:42:24.12 spid49s         waiter-list
2025-11-10 19:42:24.12 spid49s          waiter id=process1d81e31bc28 mode=S requestType=wait
2025-11-10 19:42:24.12 spid49s        pagelock fileid=1 pageid=289677 dbid=5 subresource=FULL objectname=TopMES_FM.dbo.tb_steps_node id=lock1d80187a200 mode=SIX associatedObjectId=72057594101760000
2025-11-10 19:42:24.12 spid49s         owner-list
2025-11-10 19:42:24.12 spid49s          owner id=process1d81e31bc28 mode=SIX
2025-11-10 19:42:24.12 spid49s         waiter-list
2025-11-10 19:42:24.12 spid49s          waiter id=process1d81e3128c8 mode=S requestType=wait
...

通过分析死锁图,确认:

  • 两个进程竞争同一张表 tb_steps_node
  • 锁类型为 PAGE 级别共享锁(SIX)
  • 存在循环等待路径

三、死锁分析:定位问题根源

3.1 死锁参与者

进程SPID客户端SQL 特征
进程 A(牺牲者)73C# 应用(TopMes.DataAccess)执行 GetTable(...),查询 tb_steps_node
进程 B(存活者)59Java 应用(调度服务)复杂 CTE 查询,计算步骤耗时

3.2 关键 SQL 语句(来自 Java 侧)

WITH CTE AS (SELECT id, wo_steps_id, node_time, time_tag,LAG(node_time) OVER (PARTITION BY wo_steps_id ORDER BY node_time) AS prev_node_timeFROM tb_steps_node
)
SELECT SUM(...) 
FROM CTE 
WHERE wo_steps_id = @P0;

3.3 锁冲突细节

  • 两进程均操作 TopMES_FM.dbo.tb_steps_node
  • 锁类型:页级锁(PAGE LOCK)
    • 进程 A 持有页 289677SIX 锁,等待页 164136
    • 进程 B 持有页 164136SIX 锁,等待页 289677
  • 死锁成因循环等待 + 锁顺序不一致

3.4 根本原因推断

为确认表结构与现有索引情况,执行以下 SQL 命令:

-- 查看 tb_steps_node 表的所有索引
EXEC sp_helpindex 'dbo.tb_steps_node';

执行结果返回仅有一条记录:

index_name           type_desc    column_name
PK_tb_steps_node     CLUSTERED    id

表明该表仅有主键聚集索引(id 列)无任何非聚集索引,尤其缺少对高频查询字段 wo_steps_id 的索引支持。

由此推断:

  1. 查询 WHERE wo_steps_id = ?全表扫描
  2. 全表扫描过程中申请大量 页级共享锁(S)
  3. 高并发下多个请求交叉扫描不同数据页 → 锁顺序冲突 → 死锁

结论缺少关键业务索引 是本次事故的根本原因。


四、解决方案:双管齐下——应用层重试 + 数据库索引优化

4.1 临时缓解:C# 端增加死锁自动重试(上线热修复)

在索引方案验证期间,为避免用户持续报错,紧急在 C# 数据访问层增加死锁重试逻辑

public DataTable GetExecuteStep4(string woStepsId)
{const int maxRetries = 3;for (int i = 0; i <= maxRetries; i++){try{return ExecuteQuery(woStepsId); // 原始查询逻辑}catch (SqlException ex) when (ex.Number == 1205) // 死锁错误号{if (i == maxRetries)throw; // 最后一次仍失败则抛出Thread.Sleep(100 * (i + 1)); // 指数退避}}return null;
}

效果

  • 即使死锁发生,用户无感知(不再弹窗)
  • 99% 的死锁在 1~2 次重试内成功提交
  • 为索引优化争取了部署窗口

4.2 根本解决:创建覆盖索引,消除死锁根源

根据查询模式,创建复合非聚集索引:

CREATE NONCLUSTERED INDEX IX_tb_steps_node_wo_steps_id_node_time
ON dbo.tb_steps_node (wo_steps_id ASC, node_time ASC)
INCLUDE (time_tag);

设计理由

  • (wo_steps_id, node_time):满足 WHERE + ORDER BY(窗口函数依赖)
  • INCLUDE (time_tag):覆盖查询所需字段,避免回表

4.3 执行与验证

  1. 低峰期执行建索引(使用 ONLINE = ON 避免阻塞):

    CREATE NONCLUSTERED INDEX IX_tb_steps_node_wo_steps_id_node_time
    ON dbo.tb_steps_node (wo_steps_id, node_time)
    INCLUDE (time_tag)
    WITH (ONLINE = ON);
    
  2. 验证执行计划

    • 原计划:Clustered Index Scan(逻辑读 12,000+)
    • 新计划:Index Seek(逻辑读 8)
  3. 压测验证

    • 模拟 50 并发请求 → 0 死锁
    • 接口平均响应时间:2100ms → 25ms

💡 最终策略索引根治 + 重试兜底,双重保障系统稳定性。


五、效果与收益

指标优化前优化后提升
接口 P95 延迟2100 ms35 ms60x
死锁发生频率日均 1~2 次0 次100% 消除
用户弹窗投诉1 起/日0体验显著改善
数据提交成功率~92%100%重试机制兜底成功

六、经验总结与后续改进

✅ 成功经验

  • 死锁必须看 ERRORLOG:前端 200 ≠ 后端正常
  • 索引是死锁“特效药”:90% 的读写死锁可通过合理索引解决
  • 覆盖索引 > 普通索引:减少回表 = 减少锁持有时间
  • 应用层重试是有效兜底:对 SqlException.Number == 1205 自动重试 1~3 次,可避免绝大多数用户可见故障

🔜 后续改进项

  1. 建立索引评审机制:新表上线需评估高频查询路径
  2. 引入 Extended Events:替代 TRACEON(1222),实现死锁可视化监控
  3. 推广重试封装:将死锁重试逻辑抽象为通用数据访问中间件组件

七、附录:关键命令速查

-- 开启死锁跟踪
DBCC TRACEON(1222, -1);-- 查看表索引(核心诊断命令)
EXEC sp_helpindex 'dbo.tb_steps_node';-- 创建最优索引(在线)
CREATE NONCLUSTERED INDEX IX_tb_steps_node_wo_steps_id_node_time
ON dbo.tb_steps_node (wo_steps_id, node_time)
INCLUDE (time_tag)
WITH (ONLINE = ON);-- 关闭死锁跟踪
DBCC TRACEOFF(1222, -1);

C# 死锁错误号参考

  • SqlException.Number == 1205 → 死锁(Deadlock Victim)
  • 可结合指数退避(Exponential Backoff)提升重试成功率

事故定性中等 severity,已彻底解决
根本原因:缺失关键业务索引导致全表扫描引发页级死锁
责任人:数据库设计阶段未充分考虑查询模式
闭环时间:从发现到上线修复 < 24 小时

—— 技术团队 · 2025年11月12日

http://www.dtcms.com/a/602058.html

相关文章:

  • Ubuntu系统安装.NET SDK 7.0
  • 基于深度学习与MATLAB的脑电信号情绪识别系统
  • 十大旅游电子商务网站wordpress 国内 慢
  • 大连网站制作姚喜运成都网页设计培训班
  • Apache POI
  • 某景区网站建设策划书利用codeing做网站
  • rhce作业
  • 网页网站原型图占位符怎么做定制一个微信小程序要多少钱
  • Python-PLAXIS自动化建模技术与典型岩土工程案例
  • 4-ARM-PEG-Fmoc protected Amine(2),合成设计思路与路线选择
  • 自主可控背景下MCU芯片的替代之路:从ARM到RISC-V的机遇与挑战
  • 想建设个网站全国最大的网站建设公司
  • 做网站的编程语言组合江西建设厅特殊工种的网站
  • HDPlanner 代码阅读
  • AOSP Android13 Launcher3 最近任务详解
  • 青岛市网站制作seo搜索引擎优化薪资
  • MediaPipe LLM Inference:在WEB浏览器中“裸跑”大语言模型
  • 网站平台建设公司经营范围域名注册成功怎么做网站
  • 南昌哪里有网站建设网页制作平台flash
  • 2025 创客匠人全球创始人 IP + AI 万人高峰论坛:家庭教育与企业管理的变革指南
  • Canvas指纹模拟避坑指南Canvas指纹防护实测案例
  • 2.11 实践二:基于 LoRA 微调一个垂直领域客服问答模型并部署为 API
  • 建设房屋出租网站饮食网站首页页面
  • 网站怎样自动文字排版网站建设58
  • 从工作流搭建看智能体与RPA流程自动化有何不同?
  • C语言编译器IDE | 提升程序开发效率的最佳选择
  • 当遇到 502 错误(Bad Gateway)怎么办
  • 告别停机焦虑:耐达讯自动化Profibus光纤模块——您的控制链路‘救星’在此”
  • 天津做网站优化的公司酒店网站收入如何做帐务处理
  • 数据智能时代的安全困局与 AI 破局逻辑