深夜崩溃:生产环境 MySQL 锁表引发的大型翻车现场

114 阅读3分钟

那天晚上,刚准备下班的我突然接到一通电话,语气急促:“生产挂了!”
电话那头,运维小哥焦急地喊道:“订单模块超时严重,所有请求都在排队,整个系统快瘫了!”

事件起因:一次不起眼的操作埋下隐患

事情的起因,是一个看似简单的需求:
客户希望在订单列表里增加一个字段“status”,用于显示订单状态。于是,开发同事直接在生产环境执行了一条 SQL:

ALTER TABLE orders ADD COLUMN status VARCHAR(20);

DDL 语句刚下发没多久,订单模块的响应时间开始飙升,从几十毫秒变成几秒,最终所有请求都超时,系统陷入停滞。


灾难升级:锁表问题引发的连锁反应

1. 查询队列爆满

在业务代码中,有大量针对 orders 表的读写操作:

SELECT * FROM orders WHERE status = 'pending';
UPDATE orders SET status = 'processed' WHERE id = 123;

当执行 ALTER TABLE 时,MySQL 会申请一个 MDL(元数据锁),阻止其他线程访问该表。随着业务流量持续涌入,查询队列逐渐堆积。运行 SHOW PROCESSLIST 命令,发现场景异常惨烈:

+----+-------------+-------------------+------+---------+---------+------------------------+------------------+
| Id | User        | Host              | db   | Command | Time    | State                  | Info             |
+----+-------------+-------------------+------+---------+---------+------------------------+------------------+
| 1  | app_user    | 192.168.1.1:12345 | test | Query   | 300     | Waiting for table lock | SELECT * FROM... |
| 2  | root        | localhost         | test | Query   | 290     | Waiting for table lock | UPDATE orders... |
+----+-------------+-------------------+------+---------+---------+------------------------+------------------+

2. 用户投诉不断

由于超时,订单无法正常处理,用户投诉如潮水般涌入,客服热线几乎被打爆。技术团队焦头烂额,运营部门更是压力巨大。


真相揭晓:锁表的幕后黑手

这次事故的根源是 ALTER TABLE 触发了 MySQL 的锁机制。执行 DDL 操作时,MySQL 会:

  • 获取 MDL 写锁
    这种锁会阻止所有线程的读写操作,直到 DDL 完成。
  • 引发高并发场景下的连锁阻塞
    业务的频繁查询和更新操作全部被挂起,导致锁队列快速膨胀。

避免锁表的解决方案

1. 使用 Online DDL 技术

现代 MySQL 提供了 ALGORITHM=INPLACE 的方式,允许在不锁表的情况下执行 DDL 操作:

ALTER TABLE orders ADD COLUMN status VARCHAR(20) ALGORITHM=INPLACE;

这种方式会在后台完成表结构变更,避免阻塞业务流量。

2. 分批次处理大表结构变更

对于数据量较大的表,可以采用以下方式:

  • 创建一个新表,例如 orders_new,并设计新的结构:
    CREATE TABLE orders_new LIKE orders;
    
  • 分批次迁移数据:
    INSERT INTO orders_new SELECT * FROM orders WHERE id BETWEEN 1 AND 10000;
    
  • 使用 RENAME 快速替换:
    RENAME TABLE orders TO orders_backup, orders_new TO orders;
    

3. 设置合理的锁等待超时时间

通过调整 MySQL 的超时时间参数,避免长时间锁等待引发更大的阻塞问题:

SET innodb_lock_wait_timeout = 10;

4. 实时监控锁方法状态

通过以下命令,监控锁状态,快速定位问题:

  • 使用 SHOW ENGINE INNODB STATUS 查看锁相关信息。
  • 使用 performance_schema:
    SELECT * FROM performance_schema.data_locks WHERE object_name = 'orders';
    

真实场景总结:一次小操作引发的教训

通过这次事故,我们学到以下教训:

  1. 千万不要在高并发的生产环境直接执行 DDL 操作。
  2. 尽量提前规划表结构,避免大规模变更。
  3. 借助 Online DDL 工具或分批迁移方式,降低锁表风险。
  4. 建立完善的锁监控与报警机制,及时发现和解决问题。