那天凌晨2点17分,我被值班电话叫醒,监控页面上一片深红:订单库的活跃连接数已经超过上限,应用服务开始批量报出 Too many connections。赶到工位后的第一件事不是重启MySQL,而是打开 SHOW PROCESSLIST,发现大量查询都卡在同一个状态上——一条平时根本没人注意的对账SQL,因为一次活动的数据量波动,把一个本应毫秒级的关联查询拖成了几十秒的全表扫描。
这个场景是 MySQL 性能优化在企业应用里最典型的缩影:问题从“连接数打满”这种外观开始,真正的病灶却藏得更深,可能是SQL写错了,可能是索引没建对,也可能是事务把锁拽得太久。我后来总结过一句话:企业库的“慢”,90%不会只有一个原因,它永远是SQL、索引、参数、事务、架构五层问题叠在一起的结果。这篇内容就按我实际排障的顺序展开,适合正在维护线上数据库的后端开发、DBA,也适合准备数据库面试时想建立系统知识框架的朋友。
1. 先说说那次被“连接数打满”逼出来的故障定位
1.1 凌晨报警的真相,往往不是连接数不够
那次的故障链路其实很经典:促销活动结束后,运营后台触发了一大批统计任务,每条统计SQL都要去关联一张几千万行的订单明细表。刚开始只是几条SQL变慢,后来请求越积越多,应用层的连接池先被打满,新的数据库连接请求进不来,最终数据库端抛出了 ERROR 1040: Too many connections。
很多团队到这一步的第一反应是把 max_connections 从 200 调到 2000,我见过不止一次这么做的。调完之后呢?雪崩从十分钟延迟到二十分钟,最后照样炸。为什么?因为 max_connections 只是一个门槛,它控制的是“能同时进多少个请求”,但它回答不了“这些请求来了之后能不能快速走”。真正的问题是每一条连接占用的时间太长,长查询像堵车一样把连接全部堵在路中间。
所以我后来处理这类问题,第一个动作不是调参,而是先确认今天跑的是不是有什么异动任务,再把所有正在执行的SQL抓出来看。调大连接数只能让你晚点死,不能让你活过来。
1.2 SHOW PROCESSLIST是定位慢SQL最快的入口
发现数据库连接异常后,第一现场我的习惯是执行:
sql复制SHOW FULL PROCESSLIST;
这个命令会把当前所有连接、每个连接正在执行的SQL和状态都列出来。我筛选时会重点看三列:
Time:这条SQL已经跑了多少秒,超10秒的基本都需要警惕;State:状态如果是Sending data、Copying to tmp table、Sorting result,通常意味着正在大量读取或排序;Info:正在执行的SQL原文,虽然可能被截断,但足以判断是哪类查询。
那晚抓出来的现场大概长这样:
| Id | Command | Time | State | Info |
|---|---|---|---|---|
| 31826 | Query | 38 | Sending data | SELECT o.order_no, u.name FROM orders o JOIN users u ... |
| 31831 | Query | 27 | Copying to tmp table | SELECT user_id, COUNT(*) FROM order_item GROUP BY user_id ... |
| 31842 |
