您好,欢迎访问数据库运维|优化|安装|迁移|服务官网!
13261661949
一条慢查询让数据库CPU飙到100%?加个索引三分钟解决!-行业新闻-数据库运维|优化|安装|迁移|服务_uDBok.com

新闻动态

联系我们

一条慢查询让数据库CPU飙到100%?加个索引三分钟解决!-行业新闻-数据库运维|优化|安装|迁移|服务_uDBok.com

地址:北京市昌平区高新经济开发区
手机:13261661949

咨询热线13261661949

一条慢查询让数据库CPU飙到100%?加个索引三分钟解决!

发布时间:2026-05-26 16:48:00人气:1048

前两天和一个做后端的朋友喝酒,他吐槽说,公司一个线上系统半夜突然崩了,查了半天,发现罪魁祸首是一条慢查询。那条 SQL 并不离谱,只是多表关联后加了个排序,数据量大概两百万行。按理说这量级不算大,却卡得死死的,数据库 CPU 直接飙到 100%。怎么解决的?加了个索引,三分钟搞定。他感慨说,做开发这么多年,索引这东西看着简单,用起来却像一门玄学。

一条慢查询让数据库CPU飙到100%?加个索引三分钟解决!

其实索引在 MySQL 里本质上就是一种数据结构,目的是让查询更快。我们最常用的 InnoDB 引擎默认使用 B+ 树。可以把 B+ 树想象成一本新华字典的目录:要查“索引”这个词,先翻目录找到对应的页码,直接翻过去就行,不用一页页翻。B+ 树也是同理,它把数据按顺序组织,查询时从根节点往下找,很快就能定位到目标行。但问题是,索引不是越多越好,也不是随便建一个就管用。很多开发者的习惯是,发现查询慢就凭感觉加索引,结果有时反而更慢,或者根本用不上。

最常见的坑之一是联合索引。假设订单表有字段 userid、status、createtime,你经常按 userid 查询并按 createtime 排序,于是建了联合索引 (userid, status, createtime)。看起来没问题,但如果查询只涉及 userid 和 createtime,跳过了中间的 status,索引只能使用前缀部分,后面的 createtime 排序仍然会走文件排序。原因是联合索引遵循“最左前缀”原则,不能跳过中间字段。此时可以把索引改成 (userid, createtime),或者在查询里加上 status 条件。

另一类常见问题是对索引列做函数操作。比如查询某个时间段的数据,写成 。MySQL 在索引列上用了 DATE 函数,索引就失效,变成全表扫描。正确写法是 。道理大家都懂,但忙的时候很容易忘。我见过一个项目,几十张表都有类似问题,导致查询慢得像蜗牛,开发团队花了整整一周才把所有函数操作改掉。

再说回那个朋友的故事。他加完索引后系统确实快了很多,但两周后又出问题了。这次不是查询慢,而是写入变慢。仔细一看,原来那张表上建了七八个索引,每次插入数据 MySQL 都要维护所有索引的 B+ 树结构,相当于每次写入都要更新七八个数据结构。对于高并发写入场景,索引越多写入性能越差。这就是索引的双刃剑:查询快了,写入慢了。所以,索引不是越多越好,关键是找到平衡点。一般来说,单表索引数量控制在 5 个以内比较合理,超过 7 个就需要仔细斟酌。

怎么判断一个索引到底有没有用?MySQL 提供了 EXPLAIN 命令,可以查看查询的执行计划。只要在 SQL 前面加上 EXPLAIN,就能看到 MySQL 会用哪个索引、扫描多少行、是否需要文件排序等信息。我的习惯是,每次建新索引之前先用 EXPLAIN 跑一遍,看看是否真的需要,或者是否已有索引可以复用。比如有时会发现,一个联合索引的前缀已经覆盖了查询条件,就没必要再建新索引。还有时候,虽然有索引,但优化器认为全表扫描更快,可能是因为数据量太小,或者索引选择性不够好。

索引选择性是个重要概念。简单说,就是索引列中不同值的数量占总行数的比例。比如性别字段只有男、女两种值,选择性极低,建索引基本没有意义,因为扫描一半数据和全表扫描差不多。而用户 ID、手机号这种每个值唯一的字段,选择性极高,建索引效果很好。因此,别在选择性低的字段上浪费索引空间。我见过有人给状态字段建索引,状态只有 0 和 1 两种,结果查询仍然慢,因为优化器根本不理这个索引。

还有一个容易忽略的点:索引的维护成本。你以为索引建好就万事大吉了?不是的。随着数据不断插入、更新、删除,索引会产生碎片。就像一本书的目录翻得次数多了,纸张会磨损,索引也会变得不那么高效。InnoDB 在数据页分裂、合并时会导致索引页碎片化,所以定期重建或优化索引是必要的。尤其是频繁更新的表,建议每隔一段时间使用 或 来整理碎片。该操作会锁表,最好在业务低峰期进行。

说一个实战技巧:覆盖索引。如果查询的所有字段都包含在索引里,MySQL 就不需要回表查数据行,直接从索引中返回结果,速度会快很多。比如建了索引 (userid, name),执行 时,查询完全在索引里完成,不需要去数据行里找。因此在设计索引时,尽量把查询常用的字段都包含进去,但也要避免把太多字段塞进索引,防止索引变得臃肿。

所以,索引优化归根结底是一场权衡。既要让查询快,又不能拖慢写入;既要覆盖常用查询,又不能建太多索引;既要理解 B+ 树的原理,又要了解 MySQL 优化器的“小脾气”。没有银弹,只有不断测试和调整。那个朋友后来跟我聊,他现在每建一个索引,都会先在测试环境用 EXPLAIN 跑一遍,再观察一周的性能数据,确认没有问题才上生产。我觉得这才是靠谱的做法。别指望一次优化能管一辈子,数据量在涨,业务在变,索引也得跟着调整。把索引当成活的东西,定期审视、优化,才能让数据库跑得又快又稳。

推荐资讯

13261661949