上周三下午快下班时,客服突然喊了一声:‘后台订单列表打不开了!’ 刷新几次,页面动不动卡个五六秒。我们这系统平时响应都在200ms以内,明显不对劲。
从慢查询日志入手
登录服务器第一件事就是看MySQL的慢查询日志。果然,有几条执行时间超过2秒的SELECT语句,全集中在orders表。语句长这样:
SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid' ORDER BY created_at DESC LIMIT 20;
看起来挺正常,但EXPLAIN一下就露馅了:
EXPLAIN SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid' ORDER BY created_at DESC LIMIT 20;
结果显示key是NULL,rows扫了接近10万行。这张表现在有80多万数据,没索引可不就全表扫描了。
加复合索引,立竿见影
问题出在查询条件和排序字段上。user_id、status、created_at这三个字段得一起建个联合索引:
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, created_at);
索引建完再跑一遍EXPLAIN,rows降到几十行,执行时间从2秒多变成60毫秒。前端刷新,订单页秒开。
别只盯着索引,连接池也得管
过了两天,又发现高峰期偶尔报“Too many connections”。查了下max_connections设的是150,看着不少,但应用用的是PHP-FPM,默认每个请求都新建数据库连接。
改用持久连接还不够,还得控制FPM子进程数。把pm.max_children从50降到20,同时在MySQL里调高wait_timeout,避免空闲连接占着坑。
还有个小细节:text字段拖后腿
有次导出用户备注信息,发现一条SQL特别慢。语句只是查user_id和note,但note是TEXT类型,而且内容动辄几KB。
问题在于,即使只查两列,如果走的是主键索引回表,还是会把整行数据捞出来,包括那个大文本。解决方案是把note拆到单独的扩展表里,主表只留核心字段,查询轻快多了。
监控不能停
现在每天早上第一件事就是看前一天的慢查询报表。用pt-query-digest分析日志,配合Zabbix告警,新出现的慢SQL基本当天就能发现。
调优不是一锤子买卖,数据量涨了,业务逻辑变了,原来没问题的查询也可能变慢。就像家里水管,用久了得通一通,关键时候才不漏水。