【征文】我把一次锁排查的踩坑交给 WorkBuddy
# 我把一次锁排查的踩坑交给 WorkBuddy,它给我写下了三个"视图陷阱" > 《WorkBuddy 行业应用指南》投稿 · 完整案例 > 行业与岗位:数据库运维(DBA) / 知识沉淀与团队交接 > 工具:WorkBuddy(产出物为自建 Skill `dm-lock-forensics`) ---  ## 〇、自报家门 我是干数据库运维的,圈内人叫我"数据库界华少"。 干了几年 DBA 我发现一个很现实的问题:**锁排查这件事,会的人和不 会的人,效率差十倍。** 会的人看一眼 `sys_locks` 心里有数,不会的人在十个视图里反复横跳,最后靠猜。 更扎心的是:**会的那个人一旦休假,整个团队就卡住。** 锁排查的经验,长期装在少数人的脑子里,散不掉。 这一篇要讲的,就是我如何把**一次锁排查的实战经验,交给 WorkBuddy 固化成了一个团队可复用的操作手册(Skill)**——不是让它替我查锁,而是让它**替我把经验沉淀下来**。 --- ## 一、起因:那次排查,我踩了三个坑 2026-09-24,我在达梦 DM9 上排查一个"会话卡住不返回"的问题。折腾了一圈,事后复盘,我踩了三个坑,而且**三个坑都不是 SQL 语法错,是"你查错地方了"**: **坑一:按 `TABLE_ID` 找行锁,根本找不出来。** 演示表的 `OBJECT_ID` 是 `150994945`,我顺手 `where table_id = 150994945` 去查,返回 3 行——**全是 `OBJECT` 型意向锁(IS / IX),一行行锁都没有**。后来才知道,行锁是以 `LTYPE = 'TID'` 存在的,它的 `TABLE_ID` 记的是**另一个值**(实测是 `1013`),`ROW_IDX` 和 `TID` 两列才是"哪一行"。我盯着对象 ID 翻来翻去,方向从第一步就错了。 **坑二:满屏32 行锁,真正有用的只有 1 行。** 把 `V$LOCK` 全量拉出来 32 行,逐行看才发现构成是这样的: | 锁形态 | 模式 | BLOCKED | 行数 | |---|---|---|---| | OBJECT(意向锁) | IS / IX | 0 | 29 | | TID(行锁) | X | 0 | 2 | | **TID(行锁)** | **X** | **1** | **1** | **29 行是事务在系统对象上登记的"我要碰这个对象",属正常。** 只有那 1 行 `BLOCKED = 1` 立着,才是线索。新手对着这 32 行做数据分析,做到天亮也找不出根因。 **坑三:会话 `STATE = ACTIVE`,不代表它没在等锁。** 挂在锁上的那个会话,`V$SESSIONS.STATE` 依然显示 `ACTIVE`。判断"到底有没有等锁",**要看 `V$TRXWAIT`,不能看 `STATE`**。更隐蔽的是:`SQL_TEXT` 存的是会话"当前正在执行"的语句——如果你把锁链查询跑在那个持锁的会话里,`阻塞方SQL` 那一格会显示成**你这条查询自己**。现场就"失真"了。 **这三个坑的共同点:文档上查不到,全靠踩。** 达梦的官方手册不会告诉你"按 TABLE_ID 找行锁会漏",只会告诉你字段名。 --- ## 二、输入材料:给 WorkBuddy 喂什么 这次喂的不是日志,是**我自己的踩坑记录 + 实测输出**: | 输入材料 | 内容 | 用在哪 | |---|---|---| | **原始报错与困惑** | 按 `TABLE_ID` 查不到行锁的疑问、`sessid()` 函数报错 `[-2207]` | 让它复核我的判断对不对 | | **实测输出样本** | 锁链查询的真实一行输出、`V$LOCK` 的 32 行构成 | 让它基于真实数据归纳规律 | | **参数实测读数** | `V$DM_INI` 里 52 个锁相关参数逐条筛、`V$DYNAMIC_TABLES` 捞出的 24 个锁相关视图 | 让它自己确认"到底有没有行锁等待超时这个参数" | | **探字典习惯** | 我常用的两条"不背名字、让库自报清单"的查询 | 作为方法论骨架 | **为什么要喂这些?** 因为这三个坑的本质是"**知识不在文档里,在现场**"。如果我只丢一句"帮我整理下达梦锁排查方法",它会给我一篇教科书——字段列表、语法说明,看起来很全,但**一个坑都不会提**。 **喂实测输出,它才能归纳出陷阱。** --- ## 三、WorkBuddy 配置:让沉淀有章法 我把这次沉淀当成一个**有验收标准的工程任务**来配,而不是随口聊: | 能力层级 | 用法 | 目的 | |---|---|---| | **专家** | 数据库优化专家 | 定分析框架与知识组织方式 | | **角色设定** | 资深 DBA,知识沉淀负责人 | 视角从"解一道题"转成"让别人也能解" | | **硬性约束** | 只写实测过的、**报错就标注不确定**、给 SQL 必须能照抄 | 防止它编造版本差异 | | **产出形态** | 结构化 Skill(四视图分工 → 陷阱 → SQL → 实测数字 → 纪律) | 团队可检索、可复用 | | **可照抄要求** | 关联键、join 条件、函数名一律标注"实测确认,别猜" | 新人复制就能跑 | **关键的一条约束是"报错就换一个,不要背"。** 我让它写"取会话 ID"的函数时明确要求:版本不同函数名可能不同,**报错就标注不确定**。结果它把三种写法和我实测的成功/失败都列了出来: ``` 取当前会话 ID 的写法(实测): select sessid(); ✅ 能跑 select sf_get_session_id(); ❌ [-2207] select sf_get_autocommit(); ❌ [-2207] → 这类函数在不同版本里名字可能不一样,报错就换一个,不要背。 ``` **这种"把自己踩过的错也如实标出来"的做法,是沉淀材料里最值钱的部分**——比一串永远正确的文档更可信。 --- ## 四、操作步骤:四步,把经验变成 Skill ### 第 1 步|先投喂,再让它复述 我把上面的实测输出、参数读数、三个困惑一次性喂进去,第一句话不是"帮我写文档",而是: ``` 你是资深 DBA。下面是我在达梦 DM9 上实测的锁排查过程和踩到的困惑。 请先复述:你认为我踩了哪几个坑,每个坑的判断依据是什么。 先别急着给方案,先确认我们看到的是不是同一件事。 ``` **为什么要让它先复述?** 因为判断错误的根源往往不是它不懂,是**它以为我懂了**。让它先用自己的话把三个坑说出来,我一眼就能看出它有没有真的读懂。 ### 第 2 步|把"经验"强制成"可执行" 复述无误后,第二句要求变了: ``` 把上面的内容整理成一个可复用的 Skill,结构固定为: ① 四视图分工表(管什么 + 关键字段)② 核心可照抄 SQL(含实测关联键) ③ 视图陷阱(每条写清"错在哪 + 正确姿势")④ 实测数字表 ⑤ 安全造锁手法 ⑥ 探字典习惯 ⑦ 读数纪律。 凡是你没有实测过的,一律标注"未核实",不许推测。 ``` **"不许推测"这条约束是灵魂。** DBA 最怕的不是踩坑,是**被一篇写得漂亮但字段名是编的文档坑**。 ### 第 3 步|造锁验证:安全地做一次并发实验 光有输出还不够,怎么证明这套方法在新会话里真能跑?让它给一个**不改数据的造锁实验**方案。它给的思路很关键: **首选 `select ... for update`,不要用 `update`。** 理由很实在:只加行锁、不改任何一行、一直不提交也不产生脏数据,`rollback` 一把就净。而 `update` 改了还得改回来,副作用带出去了。 并附了三条铁律,其中一条是新手必踩的: > **必须两个独立连接。** 同一会话自己拿自己的锁,**永远不会阻塞**。 > 实测反例:三条语句贴进同一窗口,`for update` **0.687 毫秒**就返回,执行号 69216–69220 **连号** → 锁在,但没人等。 > **图形客户端的多个"查询页"常共用一条连接**——看着两个页签,其实是同一个会话。 它还提醒:**验证两个窗口真是两个会话**,两边各跑一次 `select sessid();`,数字不一样才算。 **为什么这条值得写进去?** 因为"造锁造了半天发现没造出来",是每个 DBA 都干过的蠢事——而且**当时你是不会怀疑的**,因为语句确实返回了。 ### 第 4 步|立一条"读数纪律" 最后我要求补一节读数纪律,它自己加了一条我原文没写的: > **引用旧笔记里的统计数字前,必须逐行重数一遍。** > 实测教训:笔记里写"32 行中 30 行属同一事务",逐行核对实为 **27 行(7285)+ 5 行(7289)**。类别计数是高风险数字,默认会错。 **这一条让我服气。** 我笔记里那个"30 行"是当初顺手写的,它不仅没顺着抄,还把错纠正了。这就是我前面反复说的那句:**AI 交活,我签字。但它确实能替我复核。** --- ## 五、产出物:一个团队可复用的 Skill 产出物是一个结构化 Skill `dm-lock-forensics`,**5 大块,全部实测可直接引用**: ### 1. 四视图分工表(少一个都拼不出完整现场) | 视图 | 管什么 | 关键字段 | |---|---|---| | `V$LOCKED_OBJECT` | 会话 → 被锁**对象**(对象粒度) | `SESSION_ID` / `OBJECT_ID` / `LOCKED_MODE` | | `V$LOCK` | **锁本身** | `ADDR` / `TRX_ID` / `LTYPE` / `BLOCKED` / `ROW_IDX` | | `V$TRXWAIT` | **等待关系**(谁等谁、等多久) | `ID` / `WAIT_FOR_ID` / `WAIT_TIME` / `WAIT_FOR_LOCK` | | `V$SESSIONS` | **人头**(事务 → 会话 → SQL → IP) | `SESS_ID` / `TRX_ID` / `STATE` / `SQL_TEXT` | | `V$DEADLOCK_HISTORY` | 死锁案卷 | `TRX_ID` / `HAPPEN_TIME` / **`DEADLOCK_CYCLE`** | ### 2. 核心交付物:锁链查询(关联键已实测,照抄能跑) ```sql select w.ID as 等待事务, ws.SESS_ID as 等待会话, ws.CLNT_IP as 等待来源, ws.SQL_TEXT as 等待的SQL, w.WAIT_FOR_ID as 阻塞事务, bs.SQL_TEXT as 阻塞方SQL, w.WAIT_TIME as 已等待, l.LTYPE as 被等锁类型, l.LMODE as 被等锁模式, l.BLOCKED as 阻塞标记 from v$trxwait w left join v$sessions ws on ws.TRX_ID = w.ID left join v$sessions bs on bs.TRX_ID = w.WAIT_FOR_ID left join v$lock l on l.ADDR = w.WAIT_FOR_LOCK; ``` > ★ **关联键(实测确认,别猜)**:`v$trxwait.LOCK` / `WAIT_FOR_LOCK` = `v$lock.ADDR`;`v$trxwait.ID` / `WAIT_FOR_ID` = `v$sessions.TRX_ID`。 实测输出示例(一行): ``` 等待事务 7289 / 阻塞事务 7285 / 已等待 448347 被等锁类型 TID / 被等锁模式 X / 阻塞标记 0 ``` ### 3. 实测数字表(可直接写进报告,每条都有出处) | 事实 | 实测值 | |---|---| | **锁等待默认无超时** | 挂满 **9 分 34 秒**(已用时间 `00:09:34.766` = 574.766 秒),零报错、零告警 | | **`WAIT_TIME` 单位** | **毫秒**。两点采样 476079 → 503265,差 27186,对应墙上约 27 秒 | | **死锁检测耗时** | `[-6403]:死锁.` 已用时间 **566.928 毫秒** | | **死锁检测参数** | `DEADLOCK_CHECK_INTERVAL = 1000`(毫秒)→ **周期扫描,不是即时判决** | | **52 个锁相关参数里** | **没有"行锁等待超时"这一项** | | 死锁环读法 | `DEADLOCK_CYCLE` = `self -> (7291, 会话ID) -> self`,**一行字符串就是死锁环的全部结构** | ### 4. 探字典习惯(排查第一步,先让库自报清单) ```sql -- 不背视图名,让库自报清单 select name from v$dynamic_tables where name like '%LOCK%' or name like '%TRX%' or name like '%WAIT%' or name like '%DEADLOCK%' order by name; -- 实测 24 行 -- 不背参数名 select para_name, para_value, para_type from v$dm_ini where para_name like '%LOCK%' or para_name like '%TRX%' or para_name like '%WAIT%' order by para_name; -- 实测 52 行 ``` ### 5. 安全造锁手法(不改数据、可回滚、跨环境通用) `select … for update` 优于 `update`;靶子用新建 3 行小表,不要在千万行大表上做;两个独立连接 + `select sessid();` 验证;`set autocommit off;` 关自动提交。 --- ## 六、成果:经验从"个人"变成"团队" 这件事的价值,不在于我又学会了一个技巧,而在于: | 沉淀前 | 沉淀后 | |---|---| | 三个陷阱只在我脑子里 | 任何人接手排锁,**先看陷阱表**,少走三条弯路 | | 锁链 SQL 每次现写,现写现错 | **关联键已实测,照抄能跑** | | 参数是"听说",不确定 | 实测数字表,**每条带出处和单位** | | 新人靠人带 | Skill 可检索、可复用,**可交接** | **我个人的原则是:可以让 AI 干体力活,但知识资产必须带出处。** 所以这份 Skill 里,每一条结论都标注了是"实测"还是"未核实",每一条参数都写了单位和口径。这不是洁癖——**知识资产一旦混入编造的内容,信任就没了,比没有文档更糟。** 顺带一个对照:这套"采集与诊断分离、现场先于结论、数字必须标口径"的打法,和我处理数据库事故的底层习惯是一致的——**先还原现场,再下判断;先给数字,再给结论。** --- ## 七、这套方法能复用到哪 **沉淀方法论,比沉淀某条命令更值钱。** 这套"投喂实测 → 让它复述 → 强制可执行 → 造实验验证 → 立读数纪律"的五步法,我后来复用到: - **达梦并行查询(PARALLEL_POLICY)实测核实**——同样用"不背参数名、让库自报清单"起手,逐条核实"参数改了为什么没生效"; - **国产数据库锁与死锁排查**——把新库当新战场,先跑一遍"探字典 + 造锁 + 读数字",10 分钟就知道它的脾气; - **团队的交接文档**——把"我只说过一遍但没人记住"的东西,变成能检索、能照做的操作手册。 **一句话总结:AI 让我一个人的经验,变成整个团队的工具。**