前两天有个朋友半夜给我打电话,声音带着哭腔:“系统卡死了,所有订单都提交不了,后台日志一看,‘锁表’。”他焦虑得不行,老板催着上线,客户等着发货,数据库一旦被锁,简直就是掐住了整条业务的脖子。其实这种情况很常见,尤其是业务量上来以后,MySQL 或 PostgreSQL 这些关系型数据库,动不动就会出现“锁表”。说白了,锁表是数据库为保证数据一致性而不让多个事务同时修改同一行数据。但开发人员写 SQL 时往往没想那么多,一个慢查询卡住,其他请求排队等着,整个系统就全堵死了。

遇到锁表,第一反应千万别是重启数据库。很多新手运维一慌就想着重启,结果事务回滚,数据虽没丢,但业务中断的时间更长。正确的做法是先查清楚是谁在锁、锁在哪。MySQL 可以用 或 ,PostgreSQL 用 视图,一两分钟就能定位到阻塞源头。我见过最夸张的一次,一个同事写了个更新语句没加索引,导致全表扫描锁住了几十万行,后面的所有操作都得等它跑完。所以第一步,永远是诊断,而不是盲目操作。
诊断完后,最直接的解决办法就是杀掉那个阻塞的会话。MySQL 用 ,PostgreSQL 用 ,这一刀下去,被锁住的事务会立即回滚,后面的请求就能继续跑。但这里有个坑:杀掉会话只是治标,需要弄清楚为什么那个事务会跑那么久。如果是死锁,数据库通常会自动检测并终止其中一个;如果是长时间运行的查询锁住了资源,就要检查 SQL 本身是否有问题。比如我那个朋友,后来查出来是一个报表查询忘了加 ,一次捞全表数据,跑了快两分钟,期间其他事务写不了数据。杀掉会话后,他加了索引和分页,问题再没出现。
有时候杀会话也不管用,因为锁可能来自多个会话互相等待,形成环路。这时就要看锁等待的图。MySQL 的 会输出当前锁的信息,包括哪些事务在等哪个锁,哪个事务持有锁。如果发现是多个事务循环等待,就得一个个杀,从最外层的会话开始。我遇到过最复杂的情况,是三个会话互相锁住,每个都在等对方释放资源,杀掉其中一个,另外两个就自动解开了。所以别慌,理清链条,一刀一刀来。
除了手动杀会话,更优雅的做法是调整数据库的锁超时参数。MySQL 有 ,默认 50 秒,你可以调小一点,比如 5 秒。这样一旦某个事务等待锁超过 5 秒,就会报错回滚,不会让整个系统卡死。当然,这个参数不能设得太小,否则频繁回滚也会影响业务。PostgreSQL 有 ,用法类似。我的习惯是,关键业务表设 3 秒,非关键报表表设 10 秒,这样既保证核心流程的响应速度,又不会因为锁问题拖垮全站。
说到底,锁表问题的根源往往在代码层面。比如事务里混进了耗时的外部调用——调用第三方 API、发邮件、写日志,这些操作在事务里执行,锁就一直被持有。正确的做法是,事务里只做数据库操作,其他事情放在事务外。再比如,更新语句没走索引,全表扫描锁住了整张表。加索引是基础,更关键的是,开发人员要养成在 条件中使用主键或唯一索引的习惯。我见过一个团队,所有更新都通过主键进行,结果锁表问题几乎消失。
想说,数据库锁表不是单纯的技术问题,而是工程问题。它暴露的是系统设计里对并发控制的理解深度。很多创业公司初期用户少,怎么搞都没事,一旦流量上来,锁表就像定时炸弹一样炸开。与其等到半夜被电话吵醒去杀会话,不如提前在代码里做好事务拆分、索引优化和超时设置。记住一条铁律:事务要短,锁要小,索引要准。少写一个 ,少用一个长事务,数据库就能多睡几个安稳的晚上。


