新闻详情

新闻详情

首页 / 资讯中心 / 详情

SQL Server死锁分析实战:捕获、定位与修复“奇怪死锁”

发布时间:2026/10/2 15:03:40来源:尧图网络
SQL Server死锁分析实战:捕获、定位与修复“奇怪死锁”
简介一份面向 SQL Server 数据库管理员与开发者的死锁分析案例资料围绕 Deadlock 标签系统讲解了一个由索引和锁机制引发的奇怪死锁。文档从问题重现步骤入手先给出创建含聚集索引与非聚集索引的测试表、插入10000条记录、并发执行update操作等具体场景再逐步分析non-clustered index中包含include(d)或使用varchar(max)字段时行锁升级和锁冲突的变化直击死锁难以复现的要点。文档还对比了删除include选项或缩短字段类型后死锁消失的实验结果能加深对不同索引设计影响锁行为的理解。分析方法上重点介绍了开启1222跟踪标志、使用SQL Trace按SPID过滤Locks与TSQL事件、通过sp_readerrorlog读取死锁链等典型手段可帮助读者掌握从现象到根因的排查思路。资源为单个docx文档压缩包约694KB内容为完整技术分析文章结构清晰便于阅读保存已有326人学习下载对希望深入理解索引锁与死锁关系的DBA和开发人员尤具实用参考价值。1. 一个锁等待为何会让业务全部卡死先说结论SQL Server 上的死锁Deadlock不一定是两个连接互相等锁这么简单。真正让人头疼的是那种“看起来完全没有交集”的会话却在同一个时间点互相阻塞最后被锁监视器Lock Monitor随机杀掉一个受害者。这个问题几乎每个 DBA 都遇到过但很多人拿到死锁图之后看半天也看不出所以然最后只能重启服务或清缓存过几天又复现。文章要讲的就是一套从捕获到定位再到验证的分析方法能把“奇怪的 Deadlock”变成一个可解释、可复现、可修复的具体问题适合开发、运维以及所有被“锁”折磨过的人。我在生产环境处理过不少锁等待案例印象最深的是某次半夜告警所有业务库的连接数瞬间打满SQL Server 错误日志里刷出一大片死锁记录。用户端的现象是查询卡住、超时、偶尔报错。但打开死锁图之后发现参与死锁的两个会话分别访问的是两张完全不同的表谁也不挨着谁。这种案例用常规的“看两个资源互相占有”的思路根本解释不通。后来我花了两个晚上把 Extended Events 的捕获数据完整拉下来配合执行计划、索引使用情况和应用端的调用链才定位到问题的真正根源——一个被忽略的外键锁升级。这篇文章会把这种“非典型死锁”的完整分析路径拆开讲每一步都带着可复现的操作命令和参数说明力争让你下次遇到类似问题时不用再靠玄学去猜。2. 从“杀掉一个”到“定位根因”死锁分析的基础设施2.1 死锁不是 bug是锁调度的一种极端结果在 SQL Server 里死锁的本质是“两个或多个会话各自持有对方需要的资源并且都不释放导致无法继续推进”。锁监视器Lock Monitor会周期性检测这种循环等待一旦发现就选择一个代价最小的会话作为牺牲者Victim回滚它的整个事务并释放所有锁让其他会话继续运行。整个过程通常只需要几秒钟但受害者的事务会被回滚应用端就会看到类似 1205 的错误码。这听起来像一个标准机制但为什么有些死锁让人觉得“奇怪”呢因为默认的死锁报告只展示“锁资源、锁模式、会话状态”这些内核数据它根本不关心你业务上是怎么调用的。于是经常出现一个场景死锁图里明明只有两条 SQL但它们对应的存储过程、触发器、外键约束、索引维护任务甚至服务层的 ORM 缓存策略全都不会出现在报告里。你看到的只是冰山一角真正导致死锁的是水面下面的那些行为。另一个关键点是死锁图会优先展示“进程持有锁”和“进程请求锁”的直接关系但对于部分特殊场景比如同一事务内多次获取同一范围内的锁、键范围锁Key-Range Lock、自旋锁Spinlock、甚至外部代码里长时间占有连接导致的事务超长死锁报告只会给一个非常简化的结果。要想把问题看清楚必须把捕获层建好让数据“说话”的粒度足够细。这也是整篇文章反复强调的一件事没有捕捉能力就不要谈死锁分析。2.2 建一个“黑匣子”用 Extended Events 捕获死锁会话SQL Server 2008 以上版本自带 Extended Events扩展事件这是捕获死锁会话的首选工具。它比旧的 SQL Profiler 更轻量对生产环境性能的影响更小而且能精确记录死锁发生时的 XML 死锁图Deadlock Graph。常见的做法是建一个专门收集死锁的会话放到系统里长期运行相当于给数据库加了一个黑匣子。下面的脚本可以创建一个名为“DeadlockMonitor”的扩展事件会话捕获所有死锁事件并把数据写到本地文件里-- 创建一个扩展事件会话用于捕获死锁图 CREATE EVENT SESSION [DeadlockMonitor] ON SERVER ADD EVENT sqlserver.xml_deadlock_report( ACTION (sqlserver.database_name, sqlserver.session_id, sqlserver.sql_text, sqlserver.tsql_stack, sqlserver.client_app_name) ), ADD EVENT sqlserver.lock_deadlock( ACTION (sqlserver.database_name, sqlserver.session_id, sqlserver.sql_text, sqlserver.tsql_stack, sqlserver.client_app_name) ) ADD TARGET package0.event_file( SET filename ND:\XE_Deadlock\DeadlockMonitor.xel, max_file_size 50, -- 单个文件最大 50 MB max_rollover_files 10, -- 最多保留 10 个文件超出自动轮转 metadatafile ND:\XE_Deadlock\DeadlockMonitor.xem ) WITH ( MAX_MEMORY 4 MB, EVENT_RETENTION_MODE ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY 30 SECONDS, MEMORY_PARTITION_MODE NONE, STARTUP_STATE ON -- 设为开机自动启动防止漏捕获 ); GO -- 启动会话 ALTER EVENT SESSION [DeadlockMonitor] ON SERVER STATE START; GO脚本的逻辑分三层先定义“要监听什么事件”再定义“事件发生时额外抓取哪些上下文”最后定义“把数据写到哪”。这里我加了sqlserver.lock_deadlock和sqlserver.xml_deadlock_report两个事件前者在发生死锁时第一时间触发后者会把完整的 XML 死锁图写下来。注意STARTUP_STATE ON这个参数如果忘了开SQL Server 服务重启后会话不会自动拉起来死锁就漏掉了。写文件的路径也需要先建好SQL Server 服务账户必须有对应目录的写权限。max_file_size和max_rollover_files用于控制磁盘占用50 MB 一个、轮转 10 个对绝大多数环境足够。如果生产环境的死锁频率特别高可以调大MAX_DISPATCH_LATENCY分发延迟来减少磁盘写频率但也要承担“临时丢失事件”的风险。2.3 死锁图怎么读从 XML 里提取会话、资源和调用栈死锁图是一个 XML 结构里面包含了deadlock根节点、victim-list受害者列表、process-list进程列表和resource-list资源列表。传统做法是用 SSMS 直接打开 .xel 文件在图形化界面里看到两个进程节点互连的箭头图。但生产环境里真正有用的是从 XML 里把关键字段提取成表格然后用时间线去对比应用日志。下面这段脚本从扩展事件文件中读取死锁图并解析出每个进程的关键信息-- 读取扩展事件文件并解析死锁图的进程列表 SELECT n.value((//process/id)[1], varchar(20)) AS ProcessId, n.value((//process/spid)[1], int) AS SPID, n.value((//process/waitresource)[1], varchar(200)) AS WaitResource, n.value((//process/lockmode)[1], varchar(50)) AS LockMode, n.value((//process/executionStack/frame/sqlhandle)[1], varchar(200)) AS SqlHandle FROM ( SELECT CAST(event_data AS XML) AS event_data FROM sys.fn_xe_file_target_read_file( ND:\XE_Deadlock\DeadlockMonitor*.xel, NULL, NULL, NULL ) ) AS raw_data CROSS APPLY raw_data.event_data.nodes(//deadlock/process-list) AS p(n);sys.fn_xe_file_target_read_file可以按通配符读取所有轮转出来的 .xel 文件nodes()函数把 XML 拆成行集然后取每个进程节点的 ID、SPID、等待资源和锁模式。实际排查时我会重点看WaitResource列它能直接告诉你这个会话正在等哪把锁格式通常是KEY: 数据库ID: schema_id: object_id: index_id: hash_value。拿到这些参数后再用dm_exec_sql_text去反查当时的 SQL 文本基本就能定位到是哪条语句参与了死锁。死锁图里最容易忽略的是executionStack节点它记录了进程当前的调用栈。如果死锁来自存储过程嵌套或者链式触发器调用栈里的sqlhandle能帮你还原整条执行链路。经验不足的时候我只看 SPID 和资源结果把责任推给了错误的 SQL后来才意识到死锁图里的进程往往只是链条上最后被卡住的那个环节。3. 那个“奇怪”的死锁一场由外键和索引“合力”造成的意外3.1 两个毫不相关的表为何会互相锁回到开头提到的那个案例死锁图里会话 A 在更新订单表Order的一行会话 B 在更新客户表Customer的一行两个表只有业务逻辑上的关联没有直接的 SQL 互相引用。按常理说不同表之间的更新操作根本不会产生锁竞争但死锁确实发生了。真正的凶手是外键约束。订单表上有一个CustomerId外键指向客户表而应用代码在新增订单时会先更新客户表的某列比如累计积分再插入订单表。从数据库视角看这两个操作在一个事务里顺序是先持有客户表行的排他锁再尝试在订单表上插入新行。问题是SQL Server 在插入订单表时需要检查外键约束这时它要在客户表的对应客户行上加一个共享锁S 锁来验证父记录存在。如果此时另一个事务正好持有该客户行的排他锁X 锁并反过来需要订单表上的锁就形成了一个典型的“等待循环”。这就是“奇怪”的来源两个事务表面上操作的是不同表但因为外键约束的存在SQL Server 在内部额外请求了另一张表上的锁。这种锁不在应用代码的预期范围内所以死锁图上看不太出来。要想确认外键是不是元凶可以查系统视图里有多少外键没有配套索引因为外键列上缺少索引会让“检查父表存在性”这种操作变成全表扫描导致锁范围和锁持有时间被放大死锁概率直接上一个台阶。3.2 锁模式与锁升级为什么“S 和 X”会打架分析这个案例时还需要把锁模式Lock Mode弄清楚。SQL Server 的锁分为共享锁S、排他锁X、更新锁U、意向锁IX 等死锁图里最常见的组合是 S 锁和 X 锁互等。但还有一种容易被忽略的情况同一张表上更新锁U与排他锁IX在特定隔离级别下也会形成等待。比如在 READ COMMITTED 隔离级别下UPDATE 先读取目标行加的是 U 锁然后再更新为 X 锁如果两个事务同时对同一行执行“先读后写”的操作就会互相持有 U 锁并等待对方的 X 锁释放。下表的对比能帮你快速判断死锁图里的锁模式组合是否合理锁模式兼容性同粒度常见来源典型死锁场景S共享锁与 S、U 兼容与 X 不兼容SELECT 默认读读操作与写操作互等U更新锁与 S 兼容与 U、X 不兼容UPDATE 的前置读取两个 UPDATE 互相等待X排他锁与任何锁都不兼容INSERT/UPDATE/DELETE写入竞争IX意向排他与 S 兼容与 X 不兼容较高粒度的写操作外键检查触发 S 锁键范围锁RR与 S/U 部分兼容可重复读隔离级别间隙锁互锁在死锁图里看到“S 锁与 X 锁冲突”时不能只针对 SQL 本身去优化而要往上层找这条 SELECT 是在哪个事务里是不是被 ORM 自动包到了一个更大的事务里或者是触发器或外键隐式引入了额外的锁请求这些信息在死锁图里不一定全但至少能引导你去查调用链。3.3 定位方法锁定索引、确认执行计划和隔离级别遇到死锁我一般按这个顺序操作先查死锁里每个进程的WaitResource解析出 object_id 和 index_id然后去确认这个索引的外键关联情况。命令很简单-- 根据死锁图中的 object_id 和 index_id 反查表与索引信息 SELECT OBJECT_NAME(i.object_id) AS TableName, i.name AS IndexName, i.index_id, i.type_desc, c.name AS ColumnName, ic.key_ordinal FROM sys.indexes i JOIN sys.index_columns ic ON i.object_id ic.object_id AND i.index_id ic.index_id JOIN sys.columns c ON ic.object_id c.object_id AND ic.column_id c.column_id WHERE i.object_id OBJECT_ID(NOrder) -- 替换为死锁图里的表名 AND i.index_id 5 -- 替换为死锁图里的 index_id ORDER BY ic.key_ordinal;如果查出来外键列上没有索引或者返回的key_ordinal里根本没有外键列那基本就能确认问题所在外键列缺索引导致子表插入时父表的查找效率极差锁持有时间拉长。此时解决方案有两个层面逻辑层要调整事务内语句顺序物理层给外键列补一个索引。多数场景下补索引是最快、最稳的方法它能让外键检查直接走窄索引的 seek而不是扫父表的主键或堆锁的粒度和时间都会明显下降。隔离级别也是必查项。如果应用连接串里把隔离级别设置成了SNAPSHOT或SERIALIZABLE死锁的触发条件会和默认的 READ COMMITTED 完全不同。SERIALIZABLE会引入范围锁对范围查询造成大范围阻塞SNAPSHOT虽然以读不阻塞写为目标但它也有自身的更新冲突机制报告的表现形式是“更新冲突”而不是经典死锁。排查死锁前先确认所有相关连接的隔离级别这是避免误判的基本功。4. 用系统视图和脚本“复刻”现场把死锁从玄学变成可解释数据4.1 收集死锁的历史记录sys.dm_exec_requests 与日志扩展事件负责实时捕获但要分析“这次死锁”是否曾经发生过就得翻历史记录。SQL Server 的系统视图sys.dm_exec_requests只能看到当前正在执行的请求对历史死锁无能为力但错误日志里其实会留下死锁的记录取决于错误日志级别设置。另外SQL Server 默认会在sys.dm_tran_locks里保留当前锁的信息虽然不能在死锁发生瞬间抓到现场但可以结合阻塞监控脚本记录“即将发生死锁”前的锁分布。一个常见的做法是建一个带作业的监控表每小时采集一次锁的数量和等待情况。下面是一个简单的抢占脚本它的作用是找出当前有哪些会话在等待锁资源并输出等待数量最多的前 10 条-- 收集当前的锁等待情况按等待资源聚合 SELECT request_session_id AS SessionID, resource_type AS ResourceType, resource_database_id AS DBID, resource_description AS ResourceDesc, request_mode AS LockMode, request_status AS LockStatus, COUNT(*) AS WaitCount FROM sys.dm_tran_locks WHERE request_status WAIT GROUP BY request_session_id, resource_type, resource_database_id, resource_description, request_mode, request_status ORDER BY WaitCount DESC;这个查询的意义在于它能让你在死锁发生前捕捉到锁等待的“前兆”。如果某个资源长期处于 WAIT 状态往往说明两个业务之间有锁竞争死锁只是这种竞争在某个时间点的爆发。生产环境里我一般会把这种监控脚本放到 SQL Agent 作业里每 5 分钟采集一次存到单独的监控库里事后可以用来回放整个死锁形成的过程。注意别用太长的时间间隔否则抓不到 10 秒级的死锁瞬间。4.2 编写一个“死锁前侦察”存储过程自动记录会话状态只靠系统视图在死锁发生后拿不到当时每个会话都在执行什么。所以更成熟的做法是写一个存储过程死锁告警触发时自动把sys.dm_exec_requests、sys.dm_exec_sql_text、sys.dm_exec_plan_attributes这些数据全部落库。这也是我踩过坑之后总结出的习惯没有上下文数据死锁图就是半张地图。下面是一个侦察存储过程的骨架-- 创建一个存储过程用于死锁发生时快速记录现场信息 CREATE PROCEDURE dbo.usp_DumpLockActivity AS BEGIN SET NOCOUNT ON; -- 把当前所有请求的文本、计划、锁信息写入一张表 INSERT INTO dbo.DeadlockDump ( CaptureTime, SPID, SqlText, LockMode, WaitResource, IsVictim, QueryPlanXML ) SELECT GETDATE(), r.session_id, t.text, l.request_mode, l.resource_description, 0, -- 实际标记受害者需要额外判断这里先用 0 CAST(p.query_plan AS XML) FROM sys.dm_exec_requests r JOIN sys.dm_exec_connections c ON r.session_id c.session_id CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t LEFT JOIN sys.dm_exec_query_plan(r.plan_handle) p ON 1 1 LEFT JOIN sys.dm_tran_locks l ON r.session_id l.request_session_id WHERE l.request_status WAIT OR r.session_id IN (SELECT session_id FROM sys.dm_exec_requests WHERE blocking_session_id 0); END; GO这个存储过程的核心是CROSS APPLY它从 SQL Handle 反查出原始 SQL 文本再从 Plan Handle 拿到执行计划 XML最终把所有正在等待的会话和它们要执行的语句关联出来。实际使用时可以在死锁告警邮件触发的地方调用它比如 SQL Agent 作业或监控工具的执行动作里。需要注意的是存储过程本身执行需要时间而死锁过程中的会话很快会被杀掉所以有可能抓不到受害者的完整语句。为了解决这个问题我更愿意配合扩展事件里的sql_textaction 一起记录两边数据交叉对比才能把死锁链完整还原出来。不要觉得重复生产环境的死锁现场重复数据永远比缺数据好一百倍。4.3 死锁复现用并发脚本稳定重现问题要验证一个死锁是不是由特定 SQL 引起的最好的办法不是直接改生产代码而是用一个精简的并发脚本去复现。SQL Server 没有专门“制造死锁”的工具但我们可以用两个会话 延时 锁提示人为制造出一个可重复的比赛条件。下面是我常用的“最小死锁复现法”两个会话分别执行相反的加锁顺序-- 会话 1先锁 Order 表 A 行再锁 Customer 表 B 行 BEGIN TRAN; UPDATE dbo.[Order] SET CreateTime GETDATE() WHERE OrderId 1; WAITFOR DELAY 00:00:05; UPDATE dbo.Customer SET Score Score 1 WHERE CustomerId 2; COMMIT; GO -- 会话 2先锁 Customer 表 B 行再锁 Order 表 A 行 BEGIN TRAN; UPDATE dbo.Customer SET Score Score 1 WHERE CustomerId 2; WAITFOR DELAY 00:00:05; UPDATE dbo.[Order] SET CreateTime GETDATE() WHERE OrderId 1; COMMIT; GO两个会话会互相等对方的锁5 秒后锁监视器触发其中一个被牺牲。这个脚本本身很简单但它能验证一个现象如果两个会话的加锁顺序一致就不会死锁交错加锁就会出现比赛条件。当你在生产环境里没法直接改业务逻辑时用这种方式测试不同顺序对死锁的影响是很高效的验证手段。注意WAITFOR DELAY不要太长否则锁监视器判断的时间窗口会被拉长不容易复现也不要太短否则两个 UPDATE 可能恰好顺序执行完不会形成等待。通过这种复现你可以确认死锁是否真的由“加锁顺序”导致。如果复现不出来但生产环境告警还在继续说明死锁的原因比表面看更复杂可能需要回到第 3 章的锁模式分析去考虑外键、触发器、索引缺失等因素。5. 死锁分析避坑指南这些常见的“坑”可能让你白忙一场5.1 死锁图看不懂先排除“假死锁”现象打开死锁图发现两个进程的WaitResource都指向同一个资源但看不出任何循环等待关系杀死任何一个会话都无法解决。原因有些“死锁”实际上根本不是锁冲突而是外部资源竞争或自旋锁Spinlock导致会话长期无法推进被锁监视器误判为死锁。比如大内存页面交换、IT 部门运维任务抢占 CPU、甚至是文件 I/O 太慢导致事务进展受阻。死锁图只是把当时的等待关系记录下来它不会告诉你“为什么会这样等”。解决遇到这种情况需要把sys.dm_os_wait_stats拉出来看会话的等待类型。如果WRITELOG或PAGEIOLATCH_XX的累计等待时间特别长那问题就出在磁盘子系统而不是锁调度。死锁分析要跳出“锁”本身去看整个等待链。5.2 受害者选错SQL Server 选“最便宜”的不代表最合适现象死锁告警里被回滚的会话往往不是占用资源最多、也不是业务最关键的会话而是那个“代价最小”的会话。应用端经常抱怨明明我只是执行了一个很小的查询为什么把我回滚了原因锁监视器计算死锁代价时会参考sys.dm_exec_requests里的estimated_completion_time、事务日志写入量、锁数量等因素选择代价最低的会话作为牺牲者。它不关心这个会话是不是核心业务。解决如果需要让“更重要的”会话不被牺牲可以在事务里设置SET DEADLOCK_PRIORITY LOW降低代价或SET DEADLOCK_PRIORITY HIGH提高代价。这个参数只能在事务会话里生效要在事务开始之前设置。实际应用中可以把核心写操作调到 HIGH把后台批处理调到 LOW形成明确的牺牲次序。5.3 补了索引死锁还在可能是索引方向写反了现象给外键列补了索引死锁频率不降反升。原因索引不是越宽越好。死锁场景里索引的键顺序和查询的过滤条件顺序必须匹配。比如外键列是CustomerId但你的查询条件是Status CustomerId的组合条件你顺手建了(CustomerId, Status)的索引实际查询要走Status开头才能命中这个索引对死锁分析来说就失效了。更麻烦的是窄索引没有覆盖查询需要的其他列SQL Server 需要回表引入了额外锁请求。解决建索引前先看死锁图里WaitResource指向的具体索引 ID确认 SQL Server 实际使用的是哪个索引。然后对比执行计划里Index Seek还是Index Scan如果还是扫描就要调整索引的键顺序或者考虑加INCLUDE列让它成为一个完全覆盖的索引减少不必要的锁持有。5.4 隔离级别是隐藏变量READ_COMMITTED_SNAPSHOT 和死锁的纠葛现象应用降级部署后死锁突然消失一段时间后又回来。代码没有改动SQL 也没有改动。原因数据库的隔离级别不是单一属性。如果数据库开启了READ_COMMITTED_SNAPSHOTRCSI普通的读语句不会持有 S 锁死锁率会大幅下降但写语句之间的冲突依然存在。一旦 DBA 因为别的原因关掉了 RCSI所有读操作回归传统 READ COMMITTED 行为S 锁死锁又回来了。这种“时有时无”的死锁最容易被误判。解决检查数据库属性里is_read_committed_snapshot_on值。如果是 1说明读操作走快照隔离如果是 0常规读会加 S 锁。另外即使 RSCI 开着可重复读和序列化级别的显式事务仍会使用传统锁机制所以还要检查应用代码里有没有显式的SET TRANSACTION ISOLATION LEVEL语句。5.5 长期运行的“拍照”脚本本身会引入新的锁现象加了死锁监控会话后系统反而出现新的死锁且死锁图里能看到监控脚本自己的会话 ID。原因扩展事件的sql_textaction、session_idaction 本身也会有轻量锁操作如果你再用sys.dm_exec_requests频繁采样也会对系统视图造成额外负载。极少数场景下监控会话自己会成为死锁的一部分。解决扩展事件本身我没遇到过问题但高频轮询系统视图确实容易踩坑。把监控作业的频率降到 30 秒以上避免sys.dm_exec_requests这类视图与业务 SQL 产生干扰。死锁图里如果看到监控会话直接通过client_app_name过滤掉即可不要被它带偏。6. 从分析到预防让死锁成为可控指标而不是黑匣子6.1 用锁等待阈值代替死锁次数告警死锁次数是一条滞后指标等它触发告警时业务已经受影响。更好的做法是把lock_wait_time锁等待时间这个动态指标作为预警。SQL Server 的性能计数器里有Lock Wait Time (ms)和Lock Waits/sec通过sys.dm_os_performance_counters可以查到当前值-- 查询锁等待时间相关的性能计数器 SELECT counter_name, cntr_value, cntr_type FROM sys.dm_os_performance_counters WHERE counter_name LIKE Lock Wait Time% OR counter_name LIKE Lock Waits/sec%;性能计数器是累积值用法是两次采样做差再除以采样间隔。如果单次锁等待时间超过 1000 毫秒或者锁等待速率持续上升就说明系统的资源竞争已经逼近死锁临界点这时候介入要比等死锁发生再清理更从容。配合前面建好的 Extended Events 会话相当于给数据库装了一个“提前哨兵”。6.2 一个我自己的复盘习惯死锁周报与代码走查每次处理完一个死锁案例我会把死锁图、相关 SQL、索引情况、隔离级别按时间归档形成一份“死锁案例分析表”。表格的列很简单时间、数据库、SPID、资源描述、锁模式、根因分类索引缺失/事务过长/加锁顺序/外键、修复方案。积累几十条之后你会发现多数死锁的根因高度重复来来回回就是那几类。这种做法还有一个隐藏价值做代码走查时把历史上出现过的死锁资源作为关注点新需求只要碰到这些表就要特别注意事务边界和加锁顺序。我现在的习惯是任何新上线的写操作先在测试环境用并发脚本模拟 50 个线程同时执行 10 分钟再放生产。死锁分析和预防不是一次性工作而是一条持续反馈链。希望这套方法能帮到你让你下次面对死锁时不用靠“重启大法”来续命。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

聚氨酯发泡成品率为何总卡在瓶颈?拆解组合料落地选型的3大核心痛点》 2026/10/2 17:22:41

聚氨酯发泡成品率为何总卡在瓶颈?拆解组合料落地选型的3大核心痛点》

在聚氨酯材料应用领域,组合料的品质直接决定下游制品的成型效率、物理性能与综合成本。小到冷链保温箱的硬泡发泡成型,大到汽车内饰软泡的一体注塑,哪怕组合料黏度产生 5% 的微小漂移,都可能引发气泡、收缩或脱模缺陷,…

阅读更多 →
给 Coding Agent 加长期记忆——MemoraX Code 先解决哪几个重复问题? 2026/10/2 17:22:40

给 Coding Agent 加长期记忆——MemoraX Code 先解决哪几个重复问题?

在 MemoraX Code 这类编码 Agent 上接长期记忆,最先该解决的通常不是“所有重复提问”,而是跨会话稳定复现的那几类:技术栈与版本约束、目录与分层约定、命令与脚本用法、代码风格与命名。判断优先级的方法很直接——看最近若干次会话里&…

阅读更多 →
OpenShell实战指南:还原高效Windows开始菜单与资源管理器 2026/10/2 17:22:39

OpenShell实战指南:还原高效Windows开始菜单与资源管理器

如果你和我一样,每天要在一台Windows电脑上处理大量文件、软件、系统设置,对新版系统那个自带开始菜单越用越不顺手,那OpenShell这名字你应该不陌生。它最早叫Classic Shell,后来项目源码开源、社区接手后更名为Open-Shell&#x…

阅读更多 →
VSCode C/C++ IntelliSense 在远程 Linux 服务器失效?把 settings.json 改到 TaoToken 的排查路径 2026/10/2 17:22:39

VSCode C/C++ IntelliSense 在远程 Linux 服务器失效?把 settings.json 改到 TaoToken 的排查路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Figma-Context-MCP(figma-developer-mcp)版本演进全解析:从 v0.2 到 v0.13 的核心能力与实现原理 2026/10/2 17:22:38

Figma-Context-MCP(figma-developer-mcp)版本演进全解析:从 v0.2 到 v0.13 的核心能力与实现原理

AI 应用MCP 服务 【免费下载链接】Figma-Context-MCP MCP server to provide Figma layout information to AI coding agents like Cursor 项目地址: https://gitcode.com/gh_mirrors/fi/Figma-Context-MCP 点击查看 免费下载 本文以仓库根目录 CHANGELOG.md 为主线…

阅读更多 →
拆解 vuex-router-sync 构建管线:一份 Rollup 配置如何产出 6 种打包格式 2026/10/2 17:22:32

拆解 vuex-router-sync 构建管线:一份 Rollup 配置如何产出 6 种打包格式

拆解 vuex-router-sync 构建管线:一份 Rollup 配置如何产出 6 种打包格式 【免费下载链接】vuex-router-sync Effortlessly keep vue-router and vuex store in sync. 项目地址: https://gitcode.com/gh_mirrors/vu/vuex-router-sync vuex-router-sync 是一个…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉