☰
MySQL索引优化实战:从慢SQL定位到索引设计全攻略
2026/10/2 9:10:42 网站建设 项目流程

很多人问我MySQL的索引到底怎么优化,尤其是在线上业务刚跑起来、SQL一慢就抓瞎的阶段。我做过不少次索引调优,也踩过不少坑,慢慢总结出一套比较实用的打法:索引不是越多越好,也不是一键建立的,而是要根据查询场景、数据分布、表结构来反向设计。如果你刚接触MySQL的索引优化,或者已经被慢查询折磨了一阵子,这篇文章就是给你准备的。

我在这篇文章里会讲清楚索引的底层逻辑、常见的设计误区、真实的调优案例,以及那些让人抓狂的“索引失效”场景。后面还会补充一些我在实践中总结出的排查技巧和常见问题速查表,适合DBA、后端开发、运维同学对照着实际操作,最好能直接抄作业。

1. 内容整体设计与思路拆解

1.1 从一张“查询变慢”的表说起

做索引优化不能凭空拍脑袋,第一步一定是回到表和数据本身。我记得有一次处理过一个订单记录表,数据量只有六十多万行,按理说不大,但一系列报表查询跑下来要四五秒,甚至更久。

当时第一反应是查慢查询日志,发现高频SQL无非是这几类:按用户ID查订单、按时间范围查订单、按状态统计数量。问题恰恰在于:表里的主键是自增ID,业务上却没有一个字段能直接命中查询条件,结果就是每次查询都全表扫描。

这里要说清楚一个基础概念:MySQL的InnoDB引擎里,主键索引本身就是一个聚簇索引,数据行就挂在主键的B+树叶子节点上。如果查询条件不是主键,就会先走二级索引(也就是普通索引),找到主键值后再回表去取整行数据。索引优化的本质,就是尽量让查询少走全表扫描、少回表,甚至完全在索引里拿到想要的数据。

所以我在设计索引之前,一定会先做两件事:一是打开慢查询日志,把TOP SQL捞出来;二是对每一条慢SQL执行EXPLAIN,看清楚它现在是怎么走(或不走)索引的。只有拿到这些现场数据,索引优化才有意义,不然很容易建了一堆没人用的冗余索引,反而拖累写入性能、放大存储占用。

1.2 为什么“随机建索引”是大忌

我见过很多同学的做法是:发现查询慢,就对着WHERE条件的几个字段各建一个单列索引;发现还慢,就再加一个联合索引,抱着“多建几个总能命中”的心理。

但实际上,每个索引都是一棵独立的B+树,不仅占空间,写入数据时还要同步维护。一个表如果有四五个冗余索引,写入性能下降几乎是必然的。更麻烦的是,优化器在选择索引时,会基于数据分布、索引区分度来估算成本,索引多了以后反而可能选错,让SQL跑得比以前更慢。

我比较推荐的做法是把这个过程反过来:先找出一组高频查询模式,再针对每个模式设计联合索引,用最左前缀原则去匹配最常用的查询列。一个联合索引能覆盖多个SQL场景,比一堆单列索引高效得多,这是索引方案设计里最核心的思路转变。

1.3 热词里藏着的真实痛点

结合当前比较热的搜索词来看,大家关注的不只是“MySQL索引创建”这一个点,还有“MySQL排序”“MySQL锁的分类”“MySQL锁表”“MySQL性能调优”“mysql表结构自动转TDengine超级表+子表”这类衍生问题。

说实话,这些问题很多都跟索引脱不开干系。比如“ORDER BY排序慢”,本质上是排序字段没走索引,或者索引顺序和排序方向不一致,导致MySQL不得不做filesort;再比如“锁表”,在InnoDB行锁机制下,索引失效后全表扫描引发的是大量记录被加锁,从而放大锁冲突概率;还有“MySQL同步到ClickHouse”这类数据同步场景,源表如果没有高效的覆盖索引,同步任务的查询性能一样会拖后腿。

所以你在学习索引优化时,别把视角局限在“怎么加索引”这一个动作上,还要理解索引是怎么影响排序、锁、统计、同步链路的。这也是我在后面几个章节里反复强调的思考方式。

2. 高效索引的设计方法论与核心细节

2.1 联合索引:按查询模式设计,而不是按字段设计

联合索引,也叫复合索引,是最常用也最容易设计出毛病的索引类型。很多人会问:“为什么我建了(a, b, c)这么一个联合索引,查询条件只带b或c的时候,索引根本没生效?”

这就得回到最左前缀原则。联合索引是按照字段顺序构建B+树的:先按第一个字段排序,第一个字段相同的再按第二个字段排序,以此类推。所以只有查询条件里包含了联合索引的最左列,或者从左开始连续匹配到某个字段,索引才可能被利用。

比如说(a, b, c)这个联合索引,真正能充分发挥作用的SQL有:

  • WHERE a = ?(直接用最左列)
  • WHERE a = ? AND b = ?(用前两列)
  • WHERE a = ? AND b = ? AND c = ?(全命中)

但下面这些情况就不行:

  • WHERE b = ?(没带a,跳过了最左列)
  • WHERE c = ?(更不行)
  • WHERE a = ? AND c = ?(a能用到,但c用不到索引靠中间列跳过了)

所以设计联合索引之前,先统计一下你们系统里查询条件的组合方式,把最常见的几个组合拎出来,再决定字段顺序。顺序的讲究简单来说就是:把区分度高、查询频率最高的字段放在前面,范围查询字段放在最后面。举个例子,一个订单查询场景经常是“用户ID+状态+下单时间”,那联合索引设计为(user_id, status, create_time)通常就比(status, create_time, user_id)更实用。

这里有一个很重要的实操细节:联合索引不是建得越多越好,一条核心查询覆盖到就够了。如果一张表同时有高频写入和复杂查询,更要克制索引数量。我自己的经验是单表二级索引控制在四到五个以内,超过这个量就得回头审视是不是有冗余。

2.2 覆盖索引:让SQL不回表的秘密武器

回表操作是查询慢的隐形杀手。每次通过二级索引找到主键后,都要再去主键索引的B+树上捞一遍整行数据,如果查询结果涉及大量行,回表成本就会被无限放大。

解决思路就是覆盖索引。如果一个二级索引本身就包含了查询需要的所有字段,MySQL优化器会发现可以完全靠这个索引返回数据,不再回表,这个最优状态就是“覆盖索引”。

举个具体的例子。订单表里有核心字段:id、order_no、user_id、status、amount、create_time。现在有一个高频统计SQL:

SELECT user_id, status, amount FROM orders WHERE status = 'PAID' AND amount > 100;

如果表结构里没有合适索引,这条语句会先看status列的区分度。如果status就几种取值,区分度很低,优化器大概率直接全表扫描。这时即便你建了(status, amount)联合索引,因为要拿到user_id,还得回表取整行,代价依然不低。

如果改成建一个(status, amount, user_id)的联合索引,那查询字段全都在索引里,直接从索引返回结果,不需要回表。这个优化在批量导出、统计报表场景下效果尤其明显,有时候能把查询时间从几百毫秒压到几十毫秒。

不过要注意,覆盖索引不是万能的。索引字段变多,存储开销和写入维护成本都会变大,所以覆盖索引应该只为核心高频SQL服务,不要想着覆盖所有列。把表里几十个字段全塞进一个索引,那是反模式。

2.3 一字段一索引的“伪优化”陷阱

我经常在代码评审里看到这样的写法:WHERE后面有三个条件,就建三个单列索引。比如(status)、 (user_id)、(create_time)各建一个。

这种做法的常见问题是:单列索引之间很难组合使用。MySQL早期版本几乎不会用索引合并(Index Merge)来同时利用多个单列索引,8.0里虽然支持一定程度的合并,但优化器能不能选对,还要看统计信息和成本估算。大概率的情况是只挑其中一个索引用,剩下的条件还得回表过滤,效果远不如一个精心设计的联合索引。

联合索引本质上是把多个等值条件的匹配合并成一棵树遍历,效率自然更高。所以我在建索引时有一个基本判断:如果查询条件中经常同时出现多个字段,就优先建联合索引;如果一个字段的区分度极高,比如主键、唯一业务编号,那么它值得拥有一个独立的索引。

这里说的“区分度”,指的是某个字段不同值的比例。在MySQL里可以直接算:

SELECT COUNT(DISTINCT user_id) / COUNT(*) FROM orders;

这个值越接近1,说明字段区分度越高,索引越有用。如果区分度低到像性别字段只有两种取值,加索引基本帮不上忙,反而浪费空间,优化器还不一定会走。索引设计不是建了就完事,要尊重数据本身的分布。

2.4 索引字段类型与长度的“隐形坑”

有些人设计的索引虽然命中了,但还是慢,问题可能出在字段类型或长度上。

第一个坑是会隐式转换。如果表里某个字段是varchar,但SQL里传了一个数字类型,MySQL会先对索引列做类型转换,这就导致索引列上发生了函数操作,优化器直接放弃索引。比如:

SELECT * FROM users WHERE mobile = 13800138000;

mobile字段如果定义为varchar(20),那么这串数字会被转成字符串,或者反过来把索引列转成数字。反正一条式子经过函数加工之后,B+树里原有的顺序就失效了,索引走不了。最常见的就是电话号码、订单号这类字段上踩坑。解决办法只有一条:SQL里给字符串加上引号,保持类型统一。

第二个坑是索引长度过长。If你动不动就把VARCHAR(500)的字段放进联合索引,那索引文件会变得异常臃肿。InnoDB单页大小有限,索引行越长,每个数据页能存放的索引条目越少,树的高度和IO次数都会上升。对于文本类字段,一个很好的优化是用前缀索引:只对字段的前N个字符建索引,而不是存储整个字段。

ALTER TABLE articles ADD INDEX idx_title (title(20));

但前缀索引也有代价:它无法用于覆盖索引,因为索引里存的只是前缀,不是完整列值。所以具体前缀取多长,要结合数据算一下区分度,尽量做到截取后不同值占比接近完整字段。这是一个在写博客、文章类系统的表时很常见的优化手段。

2.5 索引顺序与排序方向的匹配

慢查询里还有一大类是“明明索引用了,但排序还是很慢”,比如ORDER BY create_time DESC LIMIT 10这种。

排序方向的问题容易被忽略。如果一个联合索引设计为(user_id, create_time ASC),而SQL要的是ORDER BY create_time DESC,那么查询匹配时确实能基于user_id走索引,但排序时方向不一致,优化器就可能老老实实把结果集拿出来再排一次,或者直接filesort。

其实MySQL 8.0开始支持降序索引,可以通过把索引字段定义为DESC来匹配查询排序方向。在创建索引时直接指定排序方向,可以避免额外的排序开销。设计联合索引时把排序字段放在末尾,并确认索引定义里的方向和你高频SQL里的排序方向一致,是一个性价比很高的优化点。

另外,如果查询是“WHERE user_id = ? ORDER BY create_time DESC LIMIT 10”,走了联合索引后,排序和LIMIT也可以在索引内部完成,速度非常快。这比先把该用户全部订单捞出来排序再截断要高效几个数量级。排序和索引的关系,平时测试时特别容易被忽略,建议你把线上慢SQL里所有ORDER BY都过一遍,看是不是每次都在filesort。

3. 实操过程:从定位慢SQL到落地索引优化

3.1 建立自己的“慢SQL发现机制”

优化索引的第一步不是建索引,而是找到该优化的SQL。我习惯的做法是打开慢查询日志。在MySQL 8.0里,可以动态设置:

SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1;

这样超过1秒的查询就会被记录下来。等你捞了一批慢SQL后,再用EXPLAIN逐个分析执行计划。

针对一个SQL,重点看EXPLAIN输出里的几个关键列:

  • type:从好到差依次是system、const、eq_ref、ref、range、index、ALL。看到ALL基本就是全表扫描,意味着你的索引设计还没封住这条路。
  • key:优化器实际选择使用的索引名称,如果为NULL,就是没走索引。
  • rows:预估要扫描的行数,这个数字越大,SQL越可能需要优化。
  • Extra:出现Using filesort或者Using temporary,说明排序或去重没有利用索引,是有优化空间的信号;出现Using index说明走了覆盖索引,是比较理想的状态。

我个人最关注的是type和Extra这两列。很多时候SQL慢的根源不在SQL语法,而在于执行计划太差,这时候建对了索引,效果立竿见影。

3.2 一个真实案例:订单报表为什么跑三秒

之前提到过一个订单表的报表问题。我在做了慢SQL分析后,发现最痛的一条SQL是:

SELECT user_id, COUNT(*), SUM(amount) FROM orders WHERE status = 'PAID' AND create_time >= '2024-06-01' AND create_time < '2024-07-01' GROUP BY user_id;

当时表里的索引是主键id,外加一个user_id单列索引、一个status单列索引。EXPLAIN出来的type是ALL,也就是说即使有user_id索引,优化器也没选,而是在做全表扫描,因为status和create_time的过滤条件没法通过user_id索引收敛。

这种情况下,我的调整思路是建一个联合索引,把WHERE里的等值条件放前面,范围条件放后面:

ALTER TABLE orders ADD INDEX idx_status_time_user (status, create_time, user_id);

这里把user_id也放进索引,是为了让GROUP BY也可能通过索引完成,避免临时表。建完索引后再看EXPLAIN,type变成了ref,rows从几十万降到几万,查询时间从三秒多降到了零点二秒左右。

不过这个案例还有一个细节要注意:status字段本身区分度不高,单独放在索引最前面会不会导致前导列浪费?我的判断是:只要命中率稳定,即便前导列区分度低,也能把扫描范围缩小到某个时间区间内,索引的综合收益远大于全表扫描。真正要避免的是“索引字段全是低区分度字段且没有范围条件”,那啥索引也救不了。

3.3 索引的后续维护:不是建完就完事

索引建完之后,也不是一劳永逸。随着业务数据增长,索引的区分度也会变化,当初设计合理的索引可能半年后就不太灵了。

我建议用这几条SQL来定期巡检:

-- 查看所有索引的具体情况 SHOW INDEX FROM orders; -- 查看表的统计信息 SHOW TABLE STATUS LIKE 'orders';

当发现某张表的修改次数远大于查询次数时,可以重点审视索引数量是不是太多了。InnoDB在数据发生大量变更后,索引统计信息也可能变旧,导致优化器选错索引。这时可以主动执行ANALYZE TABLE来重新统计:

ANALYZE TABLE orders;

另外一个经验是:删除几乎没人用的索引,比新建索引还要重要。使用的判定标准是开启MySQL的Performance Schema,或者通过sys.schema_unused_indexes这个视图去查。我每隔几个月就会查一次,把没有命中记录的索引找出来,然后和业务方确认后删除,这样做效果非常好,写入性能会有明显改善,存储文件也会小一圈。

3.4 索引操作的常用命令整理

这里把我项目里高频用到的索引操作命令整理一下,都是实操里直接能用的:

操作类型具体命令
在已存在的表上建索引CREATE INDEX idx_name ON orders(user_id, create_time);
修改表时加索引ALTER TABLE orders ADD INDEX idx_status (status);
建表时直接指定索引CREATE TABLE test (id INT, age INT, KEY idx_age (age));
删除索引DROP INDEX idx_name ON orders;
查看表上所有索引SHOW INDEX FROM orders;
强制走某个索引SELECT * FROM orders FORCE INDEX (idx_user) WHERE user_id = 1;
忽略某个索引SELECT * FROM orders IGNORE INDEX (idx_user) WHERE user_id = 1;

这些命令本身不复杂,但有一点要重点提醒:不要在业务高峰期对超大表直接执行ALTER TABLE加索引。MySQL 8.0虽然支持了在线DDL,很多操作不再锁全表,但索引创建依然会有不小的IO开销。我建议把这类操作放在低峰期,或者用gh-ost、pt-online-schema-change一类工具做在线变更,降低对线上业务的影响。

注意:FORCE INDEX这类hint只建议用来临时验证索引效果,不建议写死在业务代码里。因为数据变化后,优化器可能会给出更好的方案,hint反而会绑住它的手脚。

3.5 一张表最多能建多少索引才科学

这是我在评审时经常被问到的问题。官方没有硬性限制,但实际操作中,一张表的索引越多,写入时B+树同步维护的开销越大。我在一些高流量写入表上,遇到过因为索引过多导致插入耗时翻倍的案例。

我的经验值是这样的:线上OLTP表,单表索引数量尽量控制在5个以内,联合索引优先。如果是OLAP分析类表,查询模式更固定,索引设计可以更“重”,但也要考虑存储成本和查询收益。

这里有一个简单的收益判断逻辑:如果一个索引一个月都触发不了几次,那就是白占空间;如果一条核心查询因为一个索引从3秒变0.03秒,那就算这个表有了七八个索引,也值得留着。索引设计的本质始终是服务查询,而不是为了“看起来专业”。

4. 索引失效场景与常见问题排查

4.1 常见的“有索引却不走”的八种情况

很多同学在排查时会发现,明明建了索引,EXPLAIN结果却还是ALL。根据我自己踩过的坑,这里梳理一下索引失效的典型场景:

  • 索引列参与了运算。比如WHERE age + 1 = 30,这种情况索引列被表达式包裹,B+树没法快速定位。正确写法是改成WHERE age = 29。
  • 索引列使用函数。函数会使索引列的顺序关系打破,比如WHERE DATE(create_time) = '2024-07-01'。解决办法是用范围条件:WHERE create_time >= '2024-07-01' AND create_time < '2024-07-02'。
  • 隐式类型转换。varchar字段和数字比较时,索引失效。前面说的手机号就是典型。
  • LIKE模糊查询使用了前导通配符。LIKE '%abc'这种没法用索引,LIKE 'abc%'才能走索引。
  • 联合索引不满足最左前缀原则。比如联合索引(a, b),却用WHERE b = ?。
  • NOT IN、!=、<>等否定条件。这些往往会导致优化器放弃索引,走全表扫描。对于这种场景,建议改成范围查询或者拆成多条正向查询。
  • NULL值判断。在MySQL里,IS NULL不一定导致索引失效,这取决于优化器成本和索引统计。但如果字段大量为NULL,索引区分度会降低,优化器可能选择全扫。
  • 优化器判定全表扫描更快。特别是小表,或者低区分度字段,优化器认为回表成本比全表扫描还高,于是果断放弃索引。

这八种情况下,前七种都可以通过改写SQL或者设计思路规避,最后一种则要靠覆盖索引、减少回表来改变成本估算值。

4.2 排查索引问题的“三板斧”

我在给团队做排查培训时,常说的三板斧是:捞慢SQL、看EXPLAIN、查索引使用情况。

第一步,捞慢SQL并理解业务场景。我一般是把慢查询日志打开一段时间,再结合业务反馈,缩小到一个高频SQL集合。

第二步,对每一条慢SQL跑EXPLAIN。这里要特别注意,EXPLAIN的结果基于当时的表统计信息和数据量,如果你在数据不同的环境里执行,结果可能不一样。所以生产环境的慢SQL排查,最好直接在生产环境的只读实例或者从库上跑EXPLAIN,不要拿一个几万行的测试表来推算百万行表的行为。

第三步,查冗余索引和未使用索引。通过sys.schema_unused_indexes可以很快找到长期没被用到的索引。比如:

SELECT * FROM sys.schema_unused_indexes;

这个视图基于Performance Schema的数据统计,如果打开了这个开关,就能拿到比较准确的索引使用情况。我每次做索引优化复盘时,都会用这个视图来检查,确实帮我发现过好几个“当初建了就一直没用过”的索引。

4.3 排序慢、锁等待、同步慢背后的索引因素

很多人在搜索时会分门别类地搜“MySQL ORDER BY优化”“MySQL锁表怎么办”“MySQL数据同步慢”,但从我的观察来看,这三个问题的背后,往往都有同一个根源:索引不合理。

先看排序。ORDER BY走不了索引时,Extra里会出现Using filesort。这个“filesort”其实不一定发生在磁盘文件里,它可能是在内存排序区完成的,但整体额外开销明显。排序字段如果能直接融入联合索引,并保持方向一致,SQL是不用额外排序的。

再看锁等待。InnoDB是行锁,但如果你更新数据时WHERE条件没有命中索引,MySQL实际锁定的就是全表扫描范围内的所有行,锁冲突概率直接放大。尤其在批量更新场景下,一条没走索引的UPDATE,完全可能把一张本来并发挺好的表给拖成“半锁死”状态。排查这类问题,第一反应不是去看锁配置,而是先看SQL的索引命中情况。

最后看同步。MySQL同步到ClickHouse或者TDengine时,很多同步工具会执行源库的查询。如果源表没有合适的索引,同步任务的数据扫描会特别慢,导致同步时延越拉越大。所以我在做这类同步方案时,都会先让中间件团队把源库的增量查询SQL拉出来,确认字段都走了索引,尽量减少源库压力。

4.4 常见问题速查表

为了方便对照,我整理了一张日常排查里最常见的索引问题速查表:

症状可能原因解决方向
单条SELECT耗时大未命中索引或回表过多用EXPLAIN确认type,设计联合索引或覆盖索引
ORDER BY排序慢排序字段未走索引或方向不符排序字段放入联合索引末尾,匹配排序方向
UPDATE/INSERT慢表中索引过多定期删除未使用索引,联合索引合并替代
并发更新锁等待多WHERE条件未走索引导致锁范围过大给筛选字段建索引,减少锁定行数
统计报表慢频繁GROUP BY/COUNT且回表使用覆盖索引,避免临时表和回表
迁移/同步慢源表扫描条件无索引在源表关键同步字段上补索引
LIMIT深分页变慢扫描大量行后丢弃用游标方式按上次位置取值,配合索引
优化器选错索引统计信息过期ANALYZE TABLE刷新统计信息后观察

这张表不算大而全,但覆盖了我在项目里经常遇到的硬骨头。很多看似复杂的性能问题,往下一层看就是索引问题,这很正常,毕竟索引就是SQL执行计划的“导航地图”,地图不准,行驶效率自然上不去。

4.5 我怎么判断索引优化后“真的变好了”

优化结束后,一定要做前后对比,不能主观感觉。我通常会记录三条线:执行时间、扫描行数、内存排序/临时表出现频率。

  • 执行时间,可以用Java或者命令行工具多次跑同一SQL取平均值,排除网络波动。
  • 扫描行数,直接看EXPLAIN里的rows字段,优化前后对比是否大幅下降。
  • Extra列里是否还出现Using filesort和Using temporary,这两个关键词出现意味着仍有额外开销。

拿前面订单报表的例子来说,优化前扫描行数是六十几万,优化后降到两万左右,执行时间从三秒下降到零点二秒,响应时间直接一个数量级的变化。我在优化报告里不会只写“加了索引”,而是把这些数字列出来,业务方也容易理解。

有一点要说清楚:索引不是越快越好的唯一要素,硬件、参数配置、SQL写法都会影响最终表现。但索引确实是性价比最高的切入点。有一次我接手的系统MySQL配置参数很一般,机器也不是高配,仅靠合理设计索引就把一批慢查询解决了,省下来的成本非常可观。

5. 从索引到更大的MySQL性能体系

5.1 索引只是入口,别忽略执行计划的全貌

会看索引,不代表就能彻底解决性能问题。因为在MySQL 8.0的优化器里,最终执行计划是成本模型算出来的,它综合了IO成本、CPU成本、内存排序成本等。有时候你调整了一个参数,或者改变了一条SQL的关联顺序,执行计划就完全不同。

所以我在做性能分析时,索引只是一个入口。真正想通一个SQL的快慢,还需要关注join的顺序、子查询的展开方式、临时表是否落入磁盘、数据是否能在Buffer Pool里命中。索引做得好,能解决百分之七八十的慢查询,但剩下百分之二三十,要配合SQL改写和参数调优一起处理。

这里补一个实用工具:MySQL 8.0提供了EXPLAIN ANALYZE,可以真实执行SQL并带出实际耗时和返回行数。这个比纯EXPLAIN估算更准确,非常适合验证索引改动前后的效果。用法很简单:

EXPLAIN ANALYZE SELECT user_id, COUNT(*) FROM orders WHERE status = 'PAID' GROUP BY user_id;

它会把每一步的实际执行时间、扫描行数都打印出来,一眼就能看到瓶颈在哪个环节。

5.2 索引优化对“MySQL锁”问题的间接影响

前面提到的锁等待,其实从很多DBA的视角看,是一个“SQL索引命中率”的问题。我在处理线上死锁和锁等待时,会先查performance_schema的锁等待事件,然后立刻核对当前执行慢SQL的索引使用情况。

一个很典型的场景:一个更新操作WHERE update_time < '2024-01-01',如果update_time字段没有索引,InnoDB为了锁定符合条件的行,会扫描整个聚簇索引,并且逐行加锁。虽然InnoDB在扫描到不满足条件的记录时会把行锁释放掉,但大量无意义加锁和扫描的过程足以让其他事务产生严重的锁竞争。

所以在设计索引时,不仅是“查询快不快”的问题,还关系到“并发稳不稳”。一张表的事务提交速度、锁等待数量、甚至死锁频率,都可能通过索引优化得到改善。这也是为什么我说索引设计要放在建表阶段就考虑清楚,而不是等出了问题再补。

5.3 数据同步场景下的索引再思考

现在很多团队都在做MySQL到ClickHouse、TDengine这类分析型数据库的数据同步。热词里也有不少这类搜索,大家关心“mysql表结构自动转TDengine超级表+子表”以及“Flink实现MySQL同步到ClickHouse”。

在同步链路里,源库的查询压力不能忽视。不管是基于CDC的Binlog监听,还是基于定时任务拉取增量数据,源端SQL的执行效率都会影响同步的实时性。如果定时任务里的WHERE条件没有索引,每次同步都要全表扫一遍,对源库的冲击会非常大。

我做过一个项目,同步任务每五分钟扫一次订单表,条件是根据update_time来捞增量数据。刚开始update_time上没索引,每次同步扫全表,把主库的CPU打到很高,业务还反馈有延迟。后来在update_time上建了一个索引,同时把查询字段控制在一个覆盖索引范围内,整个同步任务的源库压力骤降,同步时延也稳定下来了。

5.4 存储成本与索引空间的平衡

最后想提醒一点:索引是有存储成本的。每建一个索引,就是在磁盘上写一棵新的B+树。对一个大表来说,一个索引几GB很常见。如果机器磁盘容量本来就不宽裕,盲目加索引可能导致磁盘水位告警。

我一般会在索引设计阶段评估两个指标:一是索引占用空间,二是这个索引的预期收益。如果空间成本很大,但高频SQL只涉及几个字段,就可以用覆盖索引+联合索引的组合,而不是堆一堆类似字段的单列索引。

MySQL 8.0里可以通过information_schema.tables查看表大小,通过information_schema.statistics查看索引信息,综合起来就能估算单张表的索引体积。如果发现某张表索引体积已经超过数据体积的百分之二三十,就该反思索引是不是建多了或者建重了。

索引优化真正的走向,是从“建很多索引求心安”到“精确设计索引提升核心链路”。它需要你理解业务查询模式、理解数据分布、理解执行计划,才能做得恰到好处。

在这里,我个人觉得最值得养成的一个工作习惯是:每写一条SQL,都顺手在测试环境跑一遍EXPLAIN;每做一次索引变更,都记录下变更前后的性能数字。时间久了,你对索引的感知会非常敏锐,别人还在慢查询日志里捞SQL,你扫一眼表结构和查询条件,基本就能猜出执行计划大概长什么样。这种手感,只有在一线堆SQL和慢查询日志里才能练出来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询