上周接到一个做连锁超市收银系统的客户电话,说后台订单查询页面转半天出不来,收银员录入完订单之后经常要等好几秒才能看到记录。
客户在山东临沂市兰山区义堂镇,开了七八家超市,用的是一台云服务器在济南。生意一直还行,日均订单量大概三四千单。
登录服务器一看,CPU和内存都正常,负载也不高。但连上MySQL之后,用SHOW PROCESSLIST一看,好几个查询都在执行状态,而且执行时间长的已经到了十几秒。
把其中一个正在跑的查询复制出来,单独执行了一下:
SELECT * FROM orders WHERE store_id = 5 AND order_time >= '2026-08-01' ORDER BY order_time DESC LIMIT 20;
EXPLAIN一看,type是ALL,意味着全表扫描。orders表已经快三百万条数据了,每次查询扫三百万行,不慢才怪。
更奇怪的是,这个表明明建了索引,store_id和order_time各有一个独立的索引,但MySQL优化器就是没用。
查了一下发现,之前在store_id字段上建了索引,但查询条件是store_id + order_time组合,两个单独索引在排序的时候效率不高,MySQL干脆放弃了,直接全表扫。
加一个复合索引
ALTER TABLE orders ADD INDEX idx_store_time (store_id, order_time);
加完索引之后再执行同样的查询,EXPLAIN显示type变成了ref,扫描行数从三百万降到了八百多。查询时间从14秒降到了0.3秒。
客户在义堂镇的门店里试了一下,页面”唰”就出来了,高兴得在微信上连发了三个大拇指。
问题虽然解决了,但有个细节没想通
索引是存在的,为什么之前查询不慢,偏偏最近才慢?
查了MySQL的information_schema,发现这个表之前建了五六个索引,其中有两个索引已经很久没被用过了。MySQL的查询优化器在选择索引的时候,会评估使用哪个索引成本最低。当表里的索引太多,尤其是数据量大的表,优化器评估成本的时候也会消耗计算资源,有时候甚至会”算晕了”,选了一个不是最优的方案。
顺手把冗余索引清理掉之后,查询速度又提升了一点。现在这个订单查询接口稳定在0.2秒以内。
后来跟客户聊起来这件事
客户说他们临沂兰山区总共有十几家同行在用类似的收银系统,之前也听别人说过系统变卡的问题,但一直不知道怎么处理。他在义堂镇的超市开业两年多,订单数据积累起来之后才开始感觉到慢。
其实这种情况很常见。很多系统刚上线的时候数据量小,什么问题都没有。用了半年一年,数据到了几百万行,索引设计不合理的问题就暴露出来了。收银系统还算好的,要是内容型网站,文章、评论、用户行为日志,增长更快。
顺手帮客户做了几件事
解决完索引问题之后,顺便检查了一下这台服务器的其他配置:
-
备份策略。之前没有自动备份,数据库每天手动导一次。帮客户在宝塔面板里配了计划任务,每天凌晨三点自动备份到腾讯云COS,保留最近30天的备份。客户在义堂镇门店的收银数据,丢了可是大事。
-
SSL证书。客户自己装了一个免费证书,再过俩月就到期了,帮他在宝塔里开了自动续期。
-
操作日志。这台服务器上跑着PHP程序,opcache之前没开,开了之后PHP执行效率提升了大概30%。
客户问这算不算一个大工程,我说其实不算,就一个索引加顺手优化。但就是这个”顺手”,很多人要么不知道,要么知道了也觉得麻烦拖着不弄,拖到系统彻底卡死了再找人来救火,代价反而更大。
做技术运维这行,有时候最值钱的反而不是多高深的技术,是经验——知道问题出在哪,知道怎么快速解决,知道哪些坑提前填上能省掉后面一堆麻烦。
