跨团队共享服务被锁死?长事务与锁等待排查实战指南 一天下午负责订单域的洪世贤正在核对发布单上的操作项马上就要点下确认。突然他接到另一个团队的技术负责人文彦打来的电话两边共同维护的库存服务“品如珊”的写接口开始大面积超时。十分钟后两个人在应急群里的动作变成一条直线——先暂停发布再查数据库锁等待确认根因后统一恢复写入。这种处理过程才是跨团队故障里最需要的“统一战线”不是几十个人在群里各说各话而是一套所有人都看得懂的启动流程和决策次序。这篇博客会围绕一个典型场景展开多个团队共同维护的核心服务或核心表在运行过程中被长事务、锁等待或连接池占满“卡住”。我会把它拆成六个部分故障为什么难查、响应基础如何提前准备、最小复现怎么搭、关键参数怎么调、常见误判怎么规避、最后怎么变成一套预防规范。1. 先理解“品如珊”这类核心依赖为什么会变成事故中心1.1 多团队共享资源天然存在“定位盲区”这里把“品如珊”当一个内部项目代号。它可以是一张订单表、一个库存服务、一个 Redis 锁也可以是一段所有业务都依赖的消息队列。它的特点很明确不是某个团队的私有资源而是多个团队都在写入和读取的公共依赖。单团队维护私有服务时出了问题只要查自己的日志、自己的发布记录通常很快能定位。多团队共享资源时问题就复杂了A 团队认为自己只是发了一个普通批量任务不会影响线上。B 团队发现自己调用“品如珊”超时但看不到 A 团队会话在做什么。DBA 能看到数据库锁等待但不知道哪个业务动作触发了长事务。值班工程师互相拉群确认“是不是你们那边在压测”这类问题比状态排查本身花的时间还多。“品如珊”这类依赖一旦被锁住影响是扇形的调用方超时超时触发重试重试加重连接池压力最终多个团队的系统会同时出现异常。到这个时候再去分辨“谁先开始的”已经没有意义需要做的是快速形成统一动作。1.2 “被锁住”和“进程挂掉”是两种完全不同的故障很多团队在响应初期会走弯路是因为把资源被锁当成进程崩溃来处理。两者表现相似但底层逻辑不同。进程崩溃的典型特征端口长时间无响应。应用日志中大量连接拒绝、线程池拒绝。重启或重新发布后进程恢复问题通常消失。资源被锁的典型特征进程还活着端口也通着CPU 可能不高。部分接口超时部分请求能成功响应极不稳定。重启应用后短期看似恢复大量重试流量一进来又超时。真正的问题在数据库层或中间件层不在应用进程本身。这两种情况经常被混为一谈。遇到“品如珊”大面积超时第一反应应该是确认它处于什么状态而不是盲目扩缩容或重启。下面这张表可以帮助核心依赖负责人快速区分当前故障属于哪一种判断维度进程崩溃或机器异常资源被锁或连接被占进程状态进程退出端口不可用进程存活端口可通资源使用率CPU、内存、磁盘可能飙升或掉零通常不突出连接数可能打满应用日志连接拒绝、初始化失败超时、锁等待、连接池耗尽重启效果通常能恢复短暂恢复后可能再次超时恢复手段重启、扩容、回滚发布需要定位持有方解除锁和连接占用遇到第二种情况时不能简单重启。1.3 为什么跨团队故障必须“统一战线”跨团队故障最怕的不是技术复杂而是动作互相冲突。一个团队判断应该扩容另一个团队判断应该回滚第三个团队正准备执行定时清理任务。如果没有统一决策任何操作都可能把现场“二次破坏”。“统一战线”在技术上的表现是三件事统一目标先把影响面控制住而不是先追究谁的责任。统一动作所有团队暂停发布、停止批量任务只允许一个人下达恢复指令。统一状态源所有人员看同一个告警台、同一个状态页、同一份锁等待视图。这套机制不是等到电话打完才临时拼出来的而是要提前定义好响应角色和通知路径。否则紧急情况下来来回回确认“现在到底谁负责”会浪费掉最宝贵的几分钟。2. 想在紧急来电中找到方向先建好统一的响应基础2.1 告警分级不把每个信号都当成最高级别很多团队在接入监控时把所有文件都配成“电话加急”。结果是值班人员一天收到几十个电话时间久了形成告警疲劳真正严重的“品如珊被锁死”反而被忽略。建议将告警按影响范围分成三级并把每级的触发条件、通知方式和响应时限提前写清楚。级别典型触发条件通知方式响应时限负责人L1 重大故障核心服务不可用、核心数据不可写、用户交易失败率明显上升电话 群内 所有人5 分钟内建立应急群值班主管L2 功能受损部分接口超时、连接池水位持续上升、次要模块异常群内通知 值班人确认15 分钟内完成评估模块负责人L3 资源预警CPU、内存、磁盘、队列堆积超过阈值但未影响业务群内通知当天处理对应团队按这个分级“品如珊”出现大面积写超时至少是 L2 级别。出现库存扣减失败、订单状态无法更新则直接升级为 L1。2.2 共享状态页比微信群排着队发消息更可靠微信群里很容易出现信息互相覆盖。A 团队发了一条推断B 团队转发一条日志C 团队贴了个截图后续的人要花很多时间才能拼出完整时间线。推荐的做法是使用一个共享状态页不管是维基页面、在线表格还是专门的事件管理工具。状态页中必须固定包含以下字段。字段填写说明示例发生时间首个异常告警的时间2025-01-06 14:03当前状态排查中 / 已恢复 / 待复盘排查中影响范围哪个服务、哪个接口、影响哪些业务库存写接口超时订单扣减失败责任团队负责人名字而不是团队名值班负责人洪世贤根因进度当前发现了什么、正在查什么正在查长事务和锁等待恢复时间实际恢复时间点待填写状态页的更新频率要固定比如每 10 分钟更新一次。即使在最紧张的时候也要留出一个人专门维护这个页面让后来参与的人能快速了解现场。2.3 一个最简单的统一通知脚本模板如果你们的监控系统已经能发群通知可以直接跳过这一步。如果团队刚开始建设可以先用一个很小的脚本把监控事件推到群机器人把第一版“电话链”改成机器通知。#!/usr/bin/env bash # 统一告警转发示例接收监控系统传入的事件文本 MESSAGE${1} curl -sS -X POST https://your-webhook.example.com/hook/xx \ -H Content-Type: application/json \ -d {\msgtype\:\text\,\text\:{\content\:\${MESSAGE}\}}调用方式可以非常简单./notify.sh [L1] 品如珊写接口超时率 60%请立即进应急群也可以使用 Python 生成更结构化的负载方便后续接状态页import requests payload { title: 品如珊, severity: critical, summary: 共享表锁等待超过 5 秒连接池水位 90%, start_time: 2025-01-06T14:03:0008:00, responsible: [团队A-值班, 团队B-值班], page: https://state.example.com/incident/12, } requests.post( https://your-webhook.example.com/hook/xx, jsonpayload, timeout3, )这段代码的重点不是发送本身而是它要求监控系统在告警时就把影响范围、开始时间和负责人一起带出来而不是只甩一句“CPU 超过 90%”。收到这种结构化通知的人不需要再去翻监控平台才能判断是否进入紧急状态。3. 最小复现共享核心表被长事务锁死后的排查与恢复3.1 场景定义和表结构假设“品如珊”对外的核心数据存储是 MySQL 中的一张库存表。两个团队在操作它A 团队正在跑一个批量盘点任务逐条更新库存数量。B 团队负责订单扣减每次下单都会更新同一行库存。表结构可以简化成这样CREATE TABLE t_stock ( id bigint NOT NULL AUTO_INCREMENT, item_no varchar(32) NOT NULL COMMENT 商品编码, available_qty int NOT NULL DEFAULT 0 COMMENT 可用库存, version int NOT NULL DEFAULT 0 COMMENT 版本号用于幂等或乐观锁, updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_item_no (item_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存表;这个结构里item_no是唯一的商品编码。多个团队如果同时更新同一商品InnoDB 会在行锁层面发生等待。3.2 用两个会话复现“被锁住”在测试环境开两个连接模拟 A 团队的长事务。第一个连接代表 A 团队先开启事务并更新一行然后故意保持不提交-- 会话 A模拟批量任务中的长事务 BEGIN; UPDATE t_stock SET available_qty available_qty - 10 WHERE item_no ITEM001; -- 这里模拟长事务先不提交 -- 实际生产环境里可能是出现了一个异常路径事务没提交就被挂住了 SELECT SLEEP(20); COMMIT;第二个连接代表 B 团队更新同一行库存-- 会话 B订单扣减 UPDATE t_stock SET available_qty available_qty - 1 WHERE item_no ITEM001;此时第二个连接会一直卡住不会立即返回。原因是它请求的这行数据上的排他锁仍被第一个连接持有InnoDB 默认会等待锁释放直到innodb_lock_wait_timeout到期。3.3 检查链路发现阻塞源在哪第一个动作是查看当前进程列表。登录数据库执行SHOW FULL PROCESSLIST;正常情况下会看到第二个连接状态处于“Updating”或“Waiting for table metadata lock”附近Info 字段是那条卡住的 UPDATE。接下来查看当前运行中的事务SELECT * FROM information_schema.INNODB_TRX\G;这条查询会返回trx_started、trx_query、trx_state、trx_mysql_thread_id、trx_rows_locked等关键信息。如果发现某个事务启动时间很早而且一直显示RUNNING大概率就是阻塞源头。如果 MySQL 开启了 sys 库可以用专门视图直接查看锁等待关系SELECT * FROM sys.innodb_lock_waits\G;这个视图会把等待事务和阻塞事务一起展示出来字段包括waiting_trx_id、waiting_pid、blocking_trx_id、blocking_pid。在高并发场景下它比人工对比INNODB_TRX快得多。3.4 统一恢复动作先于技术手段在真正执行 KILL 之前团队要先统一一个动作由同一个人决定是终止等待方还是终止持有长事务的阻塞方。一种常见做法是先终止一直卡住的等待方让应用不再堆积请求-- 注意生产环境执行前要确认当前 session 信息 KILL waiting_thread_id;如果阻塞方是一个真正失控的长事务则可能需要终止它-- 由 DBA 确认后执行 KILL blocking_thread_id;这里有一个非常重要的细节不要一开始就把进程列表里看起来“异常”的连接全部杀掉。如果杀掉的不是真实阻塞源只是另一个团队正在执行正常任务的连接反而会让故障范围扩大。正确顺序是先用sys.innodb_lock_waits确认等待方和阻塞方。确认阻塞方对应的业务动作是什么。由事件负责人决定终止哪一侧。执行 KILL 后观察连接池水位是否回落。确认恢复后再通知调用方放量。4. 锁死、超时、连接池这些参数怎么调才能稳4.1 关键参数速查表下面这些参数和“品如珊被锁住”的场景直接相关也是一线值班人员最容易接触到的配置。参数默认值含义调大的影响调小的影响innodb_lock_wait_timeout50 秒InnoDB 事务等待行锁的超时时间长事务等待更久接口长时间挂起快速报锁超时误伤正常等待lock_wait_timeout86400 秒元数据锁等待超时等待释放时间更长更容易提前失败避免长时间卡住max_connections151MySQL 最大连接数可容纳更多会话但占用内存连接不足应用直接报拒绝wait_timeout28800 秒非交互连接的空闲超时闲置会话保留更久会话更容易被断开Hikari 最大连接数10应用侧连接池最大连接数能并发更多数据库会话高并发时等待连接Hikari 连接超时30000 毫秒从连接池获取连接的等待时间应用层等待更久快速失败触发重试这些参数单独看都容易理解但真正排查时它们会组合成一个现象链连接池占满是因为 SQL 长时间不返回SQL 长时间不返回是因为行锁被长事务持有而长事务一直没结束可能是因为某个团队写了一个未提交的异常分支。4.2 把超时时间调大不等于解除故障很多团队在第一次遇到锁等待时会立刻修改innodb_lock_wait_timeout希望让 SQL 多等一会儿。这个动作只能缓解症状不能解决根因。如果锁本来会被释放调大超时时间确实能让业务平滑一些。如果锁永远不释放比如事务因为程序 bug 没提交调大超时时间只会让大量请求全部排队等待最终把连接池耗尽造成整个应用不可用。推荐的做法是保持锁等待超时在一个合理范围比如 5 到 30 秒根据业务特性调整。遇到持续超时第一时间查锁等待关系而不是先调参数。应用侧获取连接超时不能设置得过长否则调用方线程全部阻塞。4.3 学习环境与生产环境差别学习环境可以直接用命令观察锁等待现象生产环境则需要更谨慎。配置项学习环境生产环境锁等待超时可临时调小为 5 秒方便复现不随意修改需走配置评审长事务模拟可用SELECT SLEEP模拟禁止模拟用只读快照观察连接池大小随意调整测试根据压测和慢日志评估KILL 操作可以随意执行需要身份确认和变更记录监控指标可有可无锁等待、慢查询、连接数必须覆盖如果现场已经发生故障生产环境不要先去改一堆参数。最稳妥的路径是先找到阻塞源并解除再复盘参数是否需要调整。否则改参数本身也会变成一次未知变更给故障恢复增加新的不确定性。5. 常见误判核心资源被占别急着重启扩缩容5.1 最常见的三条错误动作第一个误判是看到应用日志超时就认为是慢 SQL 导致 CPU 飙高。实际上锁等待时 CPU 可能很低。尤其是在 InnoDB 行锁等待期间事务只是被动等锁并不消耗大量 CPU。此时盯着 CPU 看什么问题也看不出来。第二个误判是重启应用。重启后应用连接池被重建很多之前积压的请求会丢失表面看起来“恢复了”。但数据库层的长事务可能还在或者锁还没释放。调用方一旦继续重试新建立的连接又会全部排进锁等待队列超时现象马上重现。第三个误判是盲目加大连接池。连接池变大之后同一时间会有更多会话去打锁等待数据库连接数会被迅速占满。更大的连接数还可能挤压 DBA 用于诊断的连接导致排查工具都连不上库。5.2 按通路排查不要跳步遇到“品如珊”这类核心依赖异常排查顺序优先级如下确认资源是否被占用SHOW FULL PROCESSLIST看 Session 状态。确认是否有长事务查information_schema.INNODB_TRX。确认锁等待链路查sys.innodb_lock_waits或performance_schema。确认连接池水位看应用指标确认线程是阻塞在等待连接还是等待锁。确认业务源头长事务到底来自哪个应用节点、哪个接口、哪个批量任务。确认变动历史最近 10 分钟内是否有发布、批处理任务或慢日志激增。其中最容易被忽略的是第 5 步。数据库只能看到连接来自哪个 IP不一定能直接看到接口名。需要应用团队提供运行时线程堆栈才能把数据库信息和业务代码对应起来。5.3 排错速查表现象可能原因检查方式处理建议接口超时但 CPU 很低应用线程阻塞在锁等待或连接池等待看线程堆栈、Hikari 活跃连接、SHOW FULL PROCESSLIST定位锁等待链终止阻塞事务某条 UPDATE 长时间不返回行锁被长事务持有INNODB_TRXsys.innodb_lock_waits确认阻塞事务业务归属后再处理重启应用后马上又堵数据库层长事务未结束重启前查锁等待状态先解除数据库阻塞再重启应用连接池数持续上涨请求排队等待锁线程池与连接池同时占满看应用线程状态和 DB 活跃会话先限流或摘节点再查阻塞源大量连接被拒绝max_connections打满show status like Threads_connected找到占用连接最多的会话避免直接调大这张表的核心思路是一致的出现大面积超时先判断请求是“没到数据库”还是“到了数据库但等着锁”。这两种情况的处理方式完全不同。6. 从应急协作变成预防规范避免每次都靠“临时统一战线”6.1 跨团队应急角色和责任要提前定义“两边马上统一动作”很理想但如果没有提前定义角色遇到紧急情况时就会出现多人同时发指令的局面。建议在每个核心依赖中都提前定义一个最小应急角色表。角色负责内容建议人选事件负责人下达统一动作、决定是否暂停发布、对外同步状态值班主管或技术负责人数据操作人执行只读查询、确认锁等待、在授权范围内执行 KILLDBA 或有库权限的工程师应用定位人回溯应用日志、提供线程堆栈、确认业务动作各调用团队负责人状态记录人维护状态页和事件时间线值班助理或后加入的工程师这个角色表不需要写成公司制度那么重但要在应急文档里写清楚谁有一锤定音的权力。跨团队协作中最怕两个团队各派一个负责人两个人意见不一致现场执行的人不知道该听谁的。6.2 发布前检查清单把故障挡在变更之前大部分“品如珊”被锁死的事故都源于变更批量任务上线、数据订正、依赖版本升级、缓存预热脚本。建议每次发布前都跑一遍下面这个清单。本次变更是否会影响共享表或共享服务是否有团队成员同时在操作同一份数据。是否有长时间批量更新任务任务是否会长时间持有事务。是否存在循环调用或重试机制重试上限是否设置合理。关键 SQL 是否走索引是否可能产生全表扫描或大范围锁。是否有回滚方案数据订正脚本是否先备份了原始数据。是否确认了监控告警责任人异常时能联系到人。是否需要提前申请数据库查询权限或紧急变更权限。这份清单不是流于形式的打钩表。它的真正价值是让发布人意识到你不是在改一段自己的代码而是在动一个所有团队都依赖的共享底座。6.3 复盘文档要有时间轴和 Action Item故障恢复后复盘文档不是用来追责的。建议采用下面的结构把时间和动作精确记录下来。时间事件负责人影响14:03品如珊写接口超时率上升监控告警订单扣减开始失败14:05进入应急群状态页建立洪世贤确定统一动作14:10确认存在长事务锁等待链路可见DBA暂不执行 KILL14:16确认长事务来自批量盘点任务文彦暂停批量任务14:20终止阻塞会话连接池开始回落DBA部分请求恢复14:26链路验证通过恢复正常写入洪世贤影响消除复盘正文至少回答四个问题根因是什么是代码逻辑问题、人工操作问题还是依赖导致。为什么没有提前发现监控盲区在哪。恢复过程中有没有新的误判或延迟决策。后续要补齐哪个监控指标、哪段自动化恢复脚本。6.4 工程层面的彻底规避除了应急协调和复盘工程上也有一些硬手段可以降低“品如珊”被锁死的概率。第一拆分核心表或分库。如果一张表同时支撑多个团队的多个业务场景拆成按业务域隔离的表能从根源上减少锁竞争。第二批量任务低峰执行并且分批提交。不要在业务高峰用一条大事务更新核心表尽量按主键分批每次只提交一小批。第三所有数据库 UPDATE 都走短事务。事务里不要夹杂远程调用、文件读写、外部接口同步等慢操作。锁的持有时间越短锁等待概率越低。第四有限重试而不是无限重试。调用方遇到超时要退避重试并设最大重试次数。无限重试会把一次锁等待放大成连接池雪崩。第五使用 Online DDL 或低峰期变更。需要变更大表结构时不要直接ALTER TABLE长时间持有元数据锁尽量使用在线变更工具。第六定期做故障演练。在测试环境故意开一个长事务让值班团队用标准 SQL 查锁、定位、恢复把“看到锁等待不紧张”变成一种基本技能。真正让团队放心的不是一套完美的应急预案而是在每一次核心依赖告警时大家都能马上知道该看什么、不该做什么。跨团队协作里的“统一战线”不应该等到事发后才开始谈而应该提前固化成一章简短、清晰、可执行的应急文档。