1205 = 等太久放弃,1213 = 互相等强滚一个;这两张图是 L-21 锁专项修复留下的“复发哨兵”。覆盖核心盘 🔴 组 2 张面板(c34–c35)· 后端核心仪表盘 siliang-backend。
数据库改数据前要先给那几行上锁(像进占座自习室把包放椅子上)。两个事务抢同一行,就只有两条出路:等太久主动放弃(1205 锁等待超时),或者互相等对方手里的锁、被数据库强行拖走一个(1213 死锁)。两败俱伤,没有赢家。
这两张面板是锁专项修复(L-21)留下的哨兵:根因修净后近 1h 计数应恒为 0;再冒出来,就说明有新的锁窗口在挡路——它回答的问题始终是“锁问题修干净了吗?有没有复发?”
占座的故事。把一行数据想成图书馆的一张桌子。事务 A 坐下来,把包放在椅子上(加锁),开始改桌上的东西。事务 B 想坐,发现被占,只能站在旁边等(锁等待)。从 A 落座到起身离开的这段时间,就叫锁窗口——窗口越长,外面排队的人越多,队伍能排到街口。
两条出路,都是坏结局。出路一:B 等满规定的时限(MySQL 默认约等 50 秒的锁等待超时)还没等到,主动放弃走人——这是 1205,温和失败,没有互害,通常触发重试兜底。出路二:更糟的剧本——A 占了 1 号桌想要 2 号桌,B 占了 2 号桌想要 1 号桌,俩人互相等,谁都动不了。管理员(数据库)发现这个死结,直接把其中一个请走(回滚一个,1213)。被拖走的那个已经干了一半活,回滚有成本,所以 1213 比 1205 更恶劣。
哨兵。L-21 锁专项修复把“删画布输出 / 节点换源”这两类操作的锁窗口缩短了,并且给它们的重试兜底装了计数器:兜底每触发一次就 +1(记在 siliang_db_lock_errors_total)。修复上线后,近 1h 计数恒 0 就是绿灯;哪天它再亮起来,就说明有新的锁窗口在挡路——这就是“防回退哨兵”的思想。
| 类比里的东西 | 系统里对应的东西 |
|---|---|
| 图书馆的桌子 | 一行数据(如画布节点行)。事务改它之前必须先拿行锁,保证数据不写坏。 |
| 占座的包 | 行锁。从拿到锁到放锁的时间 = 锁窗口;窗口越长,别的事务等得越久。 |
| 站着等的人 | 被阻塞的其他事务。它们占着数据库连接干等,排队的队伍越排越长。 |
| 等满时限走人 | 1205 锁等待超时——温和失败,这一个操作放弃,通常触发重试兜底。 |
| 互相等被管理员拖走 | 1213 死锁——数据库发现死结,强制回滚其中一个事务,让另一个通过。 |
| 吵架登记本 | siliang_db_lock_errors_total 计数器:重试兜底每触发一次 +1,近 1h 次数就是哨兵读数。 |
# 锁窗口 = 从拿到锁到放锁的时间 # 窗口里混入慢查询/外部调用 → 排队雪崩
ERROR 1205 (HY000): Lock wait timeout exceeded;
try restarting transaction
# 等满时限放弃,事务本身没互害ERROR 1213 (HY000): Deadlock found when trying
to get lock; try restarting transaction
# 预防:大家按同样顺序拿锁sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h])) # 修复后恒 0;≥1 黄 / ≥10 红
sum(increase(siliang_db_lock_errors_total[1h])) # → 近 1h 总共几次 sum(rate(siliang_db_lock_errors_total[5m])) by (operation, code) # → 速率细看
sum(rate(siliang_db_lock_errors_total[5m])) by (operation, code) # 哪类操作 × 哪种死法,一眼定位
# 链路: 锁等待 → DB 池 checked_out 堆满 → 请求排队 → 5xx # 互证: m115 池使用率 + c17 5xx 错误率
# 反面教材: 事务中间去请求 LLM → 锁窗口拉长几百倍 # 正解: 事务里只碰 DB,外部调用挪到事务外
它是什么:过去 1 小时数据库“抢座位吵架”了几次。这是锁专项修复(L-21)留下的哨兵:删除节点/换源的重试兜底每触发一次计一次。它不是业务量指标——业务量再大,修干净的锁也不该让它非零。
回答什么问题:锁问题修干净了吗?有没有新的锁窗口冒出来?
✅ 根因修净后恒为 0(<1 绿)——这条曲线最健康的样子就是一条贴地的直线。
🚨 ≥1 黄 / ≥10 红;持续 > 0 = 仍有锁窗口未收敛——立即去 c35 速率图按 operation × code 定位是哪类操作、哪种死法,并对照发布记录找第一出现时间。
# 面板 PromQL(照抄主图谱口径) sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h]))
联动:主图谱 c34 ↗ · 锁冲突率 c35(定位入口) · 相关课程:increase() 与 changes() / HTTP 5xx(连带现场)
它是什么:吵架的细节——operation(删画布输出 / 节点换源)× code 两条维度拆开的速率线。1205 = 锁等待超时(等太久放弃),1213 = 死锁(俩事务互相等,数据库强制一个滚)。大数字只告诉你吵了几架,这张图告诉你谁跟谁、怎么吵的。
回答什么问题:哪类操作在跟谁抢锁?是超时还是死锁?修复上线后有没有归零并保持?
✅ 全部线贴地;被 L-21 修过的 operation 长期为 0。
🚨 1205 主导 = 有长锁窗口在挡路(查长事务/慢查询/事务内外部调用);1213 主导 = 加锁顺序不一致(查事务的拿锁顺序)——两种死法处置方向不同,别混着修。
# 面板 PromQL(照抄主图谱口径) sum(rate(siliang_db_lock_errors_total[5m])) by (operation, code)
联动:主图谱 c35 ↗ · 哨兵大数字 c34 · 相关课程:DB 连接池(排队连坐) / increase() vs rate()
用户炸群、接口报 500,排查速查表里锁冲突是必查站。三步:哨兵确认 → 定位操作与死法 → 确认连带伤害。
# 1) 哨兵确认:近 1h 几次 sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h])) # ≥1 黄 / ≥10 红;恒 0 则此路不通,转头查其他依赖 # 2) 定位:哪类操作 × 哪种死法 sum(rate(siliang_db_lock_errors_total[5m])) by (operation, code) # workspace_output_delete / workspace_node_edge_replace × 1205/1213 # 3) 连带伤害确认:池是不是被排队事务占满(内存盘) max_over_time(siliang_db_pool_connections{state="checked_out"}[1m]) # 贴上限 = 锁等待连坐连接池,接口在排队(对照 m115 使用率)
判读:计数归零、池使用率回落、5xx 停止,才算这波过去;事后按 operation 去修对应的锁窗口。
两种死法两种病:1205 找“谁抱着锁不撒手”,1213 找“谁们的拿锁顺序打架”。先按 code 拆开再分别下钻。
# 1205 主导:找“长锁窗口”——谁抱着锁不撒手 sum(rate(siliang_db_lock_errors_total{code="1205"}[5m])) by (operation) # 处置:缩短事务,把慢查询/外部调用挪出事务 # 1213 主导:找“加锁顺序不一致” sum(rate(siliang_db_lock_errors_total{code="1213"}[5m])) by (operation) # 处置:统一拿锁顺序(大家先拿同一把再拿下一把),死结就系不上 # 时间维度:第一出现时间对照发布记录 sum(increase(siliang_db_lock_errors_total[1h])) by (operation) # 非零起点贴着某次发布 → 新代码引入的新锁窗口
修复不是合完代码就结束——上线后 24h 内哨兵必须恒 0,被修的那类 operation 要归零并保持。
# 修复上线后 24h:哨兵应恒 0 sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h])) # 任何非零 → 回滚评估或继续收敛,别让“先观察几天”拖成复发 # 对照操作维度确认修的就是那一类 sum(increase(siliang_db_lock_errors_total[1h])) by (operation) # 被修的 operation 应归零; # 别的 operation 冒头 = 新的锁窗口,另开专项
哨兵的价值在复发第一时间知道。口径与面板一致,非零即报,不要加“再等等看”的缓冲。
# 哨兵告警:非零即报(口径与面板一致) - alert: SiliangDbLockErrors expr: increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h]) > 0 for: 0m labels: severity: warning annotations: summary: "近 1h 出现数据库锁冲突(1205/1213)" description: "L-21 修复疑似回退或新锁窗口,按 c35 by (operation, code) 定位"
监控告诉你“吵了几架、谁跟谁”,要抓“抱着锁不撒手”的现行,得去数据库里看长事务(通用排查命令)。
# 出事时在 MySQL 侧看谁在持锁(通用排查命令) SELECT * FROM information_schema.INNODB_TRX ORDER BY trx_started; # trx_started 很早还在跑的 = 长事务嫌疑人(锁窗口元凶) SHOW ENGINE INNODB STATUS; # LATEST DETECTED DEADLOCK 段: # 最近一次 1213 的双方各持什么锁、各等什么锁 # 拿到“互等的两张桌子”,就能定加锁顺序该怎么统一
# 错: # 1205 告警 → 重启数据库 → 所有连接断开,事故扩大 # 对: # 查 INNODB_TRX 长事务 + 按 operation 定位锁窗口来源
# 错: # 只看总量 → 统一“优化 SQL” → 1213 照样复发 # 对: rate(siliang_db_lock_errors_total[5m]) by (code) # → 按 code 分流
# 错: # 只盯 c34 的数字来回刷新 # 对: sum(rate(siliang_db_lock_errors_total[5m])) by (operation, code) # → 去 c35
# 错: # “就 1 次而已”/“反正一直有点” → 两个方向都错 # 对: # ≥1 黄 / ≥10 红;目标恒 0,非零即查
# 错: BEGIN; UPDATE …; call_llm(); UPDATE …; COMMIT; # → 锁窗口爆炸 # 对: call_llm(); BEGIN; UPDATE …; UPDATE …; COMMIT; # → 事务只碰 DB
{job="siliang_backend_worker"} 排除它。正解:手工查询同样带上 job 过滤。 # 错: sum(increase(siliang_db_lock_errors_total[1h])) # → 双计 # 对: sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h]))
# 错: # 锁冲突工单 → 直接转 DBA # 对: # DBA 抓长事务现场 → 应用侧缩事务/统一拿锁顺序
# 错: sum(rate(siliang_db_lock_errors_total[5m])) # → 这是速率,不是次数 # 对: sum(increase(siliang_db_lock_errors_total[1h])) # → 近 1h 总共几次
参考答案:数据库的每一行数据像图书馆的桌子,改之前要先占座(加锁)。1205 是“等座的人等满规定时间还没等到,主动走了”——温和失败,回头再来(重试);1213 是“A 占了 1 号桌想要 2 号桌,B 占了 2 号桌想要 1 号桌,俩人互相等”——管理员(数据库)看不下去了,直接请走一个(强制回滚),被请走的那个活干了一半白干。所以 1213 更恶劣。
参考答案:恒 0 = L-21 锁专项修复依然有效、没有新的锁窗口冒出来——这是它最健康的样子。≥1 变黄:出现零星锁冲突,去 c35 按 operation × code 定位;≥10 变红:锁窗口在持续挡路,按事故处理(对照发布记录找第一出现时间,抓长事务现行)。哨兵的意义是复发第一时间知道,不是平时好看。
参考答案:因为等锁的事务占着数据库连接干等——连接池是有限的(backend 是 sync 30 / async 80 / checkpointer 30)。等锁的队伍把池借空后,后续所有请求(哪怕是不碰这些行的简单查询)都借不到连接,只能排队,排队超时就 5xx。所以锁冲突的现场是“一片接口一起变慢”,要在连接池使用率(m115/m116)和 5xx(c17)上互证。
参考答案:L-21 是锁专项修复——把“删画布输出 / 节点换源”这两类操作的锁窗口缩短(这是重试兜底曾经反复触发的原因)。复发长什么样:c34 的近 1h 计数从恒 0 变成非零并持续;c35 上被修过的 operation 线重新抬头。处置顺序:先按 code 分流(1205 查长锁窗口、1213 查拿锁顺序),再对照发布记录找引入点。