
GitHub面试题揭示:一条未提交连接如何拖垮整个数据库
来源标题:How One Connection Kills a Database
一个未提交的MySQL事务持有表锁,导致schema变更排队等待,后续所有查询全部阻塞,整个数据库停摆。问题根源是一条普通应用连接,无需高权限即可复现,Postgres同样受影响。解决方案是找到阻塞链顶端的连接并将其杀死。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
Simeon Griggs [@simeonGriggs] | 2026年8月31日 你觉得你能在 GitHub 拿到一份 DBA 的工作吗? 你刚被呼叫了。所有查询都执行失败。找出原因。 多年前在 GitHub,这是 Sam Lambert 在面试中会问的一个问题。 问题的答案涉及一个宕掉的数据库:起因是一次 MySQL schema 变更需要对某张表加排他锁,但它拿不到锁。另一个会话已经碰过那张表,而且一直没有提交。那个未提交的事务阻塞了 schema 变更。 一旦 schema 变更开始等待,同一张表上的新查询就都排在它后面。没有任何死锁,也不会有任何东西自己超时。整个堆积就僵在那里,直到有人找到阻塞链最顶端的那条连接并把它杀掉。 (顺便说一句,在 PlanetScale Vitess 上就不会遇到这个 MySQL 锁问题,因为 ALTER 会作为在线 schema 变更执行,在后台复制数据,只在切换时短暂加锁。) 这种情况并不罕见,也不是 MySQL 独有的。在 Postgres 上同样可以轻易复现同样的堆积。 你不需要高权限,一个普通的应用连接就足够了。 只需要一个未处理的异常,外加一次时机不巧的迁移。 假设你有一个 Postgres 数据库,里面有一张热点表叫 orders。 在一条连接上,一个事务开始执行 SELECT 查询,但应用抛出了异常。 -- Connection A: Ordinary application user BEGIN; SELECT * FROM orders WHERE id = 123; -- ...then the app throws an exception 💀 SELECT 查询已经执行完了,但 Postgres 仍然占着这条连接,等待事务以 COMMIT 或 ROLLBACK 结束——而由于应用抛了异常,这个结束永远不会到来。 Postgres 在等那条连接的同时,另一条连接尝试执行迁移。 -- Connection B: Migration role waiting on lock ALTER TABLE orders ADD COLUMN foo integer; SELECT 查询只需要读锁(ACCESS SHARE)。迁移需要一种会阻塞其他所有访问的锁(ACCESS EXCLUSIVE),所以它只能等待。 Postgres 不会让后来的查询插队。orders 上的每一个新查询都排在已经卡住的迁移后面,而这个迁移本身又卡在最初那条从未关闭的 SELECT 连接后面。 -- Connections C, D, E, etc: Waiting on the lock SELECT * FROM orders WHERE customer_id = ?; 如果应用不能妥善处理错误,又依赖 orders 的快速响应,那你就宕机了。 不是因为黑客攻击,而是因为一个未处理的异常阻止了 COMMIT,让一条连接一直开着,阻塞了一次迁移,而那次迁移又阻塞了其他所有事情。 顺便说一句,这是可以预防的。它之所以发生,是因为 idle_in_transaction_session_timeout 默认是禁用的。给它设一个值,Postgres 就会让那些没有关闭的事务超时。 在 MySQL 和 Postgres 中调试连接状态可能很笨拙。 正如 See what your database is doing right now with Connections 一文所述,你可以在终端里循环执行一条查询来查看活动连接。但这不是一个理想的界面。 它还取决于你的数据库是否有空闲的连接槽位。最坏的情况是数据库被大量未关闭的连接淹没,以至于你连自己都连不上去调试和杀掉它们。 因为这种体验实在太糟糕,我们在 PlanetScale 上为 Postgres 和 MySQL 数据库增加了查看和杀掉连接的能力。 PlanetScale 的连接工具使用一条保留的管理连接,因此即使连接已经耗尽、你的应用连不上,它仍然能向你展示活动连接和进程的列表。 在 PlanetScale 仪表盘中,你现在可以点击 Connections 标签页查看实时连接列表。你会看到许多有用的列,比如 Process ID、State 和 Duration,但最重要的是,你会看到一个 Blocked Queries 列。 从这里很容易看出哪些连接正在阻塞其他连接继续执行。你可以点进任意一条连接查看更详细的信息,例如该连接当前正在执行的查询。 Kill process 给你三个选项,从干扰最小到最大: 回想一下前面宕机的例子,那个空闲的 SELECT 早已执行完毕,所以取消查询并不会释放锁。在这种场景下,你需要终止事务或终止连接。 在 Vitess 上,每个进程有两个选项:取消查询或终止连接。在分片数据库中,你可以按 keyspace 和 shard 查看进程列表。 如果你更愿意使用终端 UI,可以运行: pscale branch connections top <database> <branch> 这会打开一个交互式实时视图,大约每秒刷新一次。最重要的连接会排在顶部。你可以用键盘快捷键在视图中导航,并打开任意连接查看更多细节。 如果你的 agent 更希望以 JSON 获取连接的当前状态,可以用下面这条一次性命令代替实时视图: pscale branch connections show <database> <branch> --format json 该输出包含取消查询或终止连接所需的 ID,你仍然可以要求对这些操作进行确认。配套的 kill 命令请参阅 CLI 参考文档。 如今要拿下你梦想工作的正确答案是:“Use PlanetScale。” 下次你因为到处报错被呼叫时,我希望你已经迁移过来了。面试答案没有变,但 PlanetScale 上的连接管理让这一切比以往任何时候都更容易。
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。