LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

明明是同一条SQL,为什么有时候走索引a,有时候却走索引b ?

zhenglin
2025年12月5日 9:43 本文热度 204


前言

想象你是一家餐厅的服务员,面前有两个菜单:

  • 菜单A:按菜品分类排列(前菜、主菜、甜点)

  • 菜单B:按价格从低到高排列



当顾客说:"我要最便宜的川菜"。

你会:

先用菜单B找到所有低价菜

从中筛选川菜


或者:

先用菜单A找到所有川菜

再按价格排序


这就是MySQL优化器的日常决策

明明是同一条SQL,有时候走的索引a,而有时候走的索引b,就是它的锅。



一个让程序员崩溃的案例

现在有个需求:查询今年开始已付款的前100个订单。

给status字段创建了索引idx_status。

给create_time字段创建了索引idx_create_time。

查询订单的sql如下:


SELECT * FROM orders 

WHERE status = 'paid'      -- 状态条件

AND create_time > '2025-01-01' -- 时间条件

ORDER BY amount DESC 

LIMIT 100;


周一执行计划如下

使用索引:idx_status(状态索引)  

扫描行数:500行  

耗时:0.1秒


周二执行计划如下

使用索引:idx_create_time(时间索引)  

扫描行数:50万行  

耗时:8秒


周一只扫描了500行数据,而周二却扫描了50万行数据。

周一耗时0.1秒,而周二耗时却又8秒。

同一SQL在不同时间性能差异80倍!

让我们拆解背后的原因。


揭秘优化器的"决策三步曲"

MySQL优化器的决策流程如下:

成本计算示例

  

根据扫描行数、回表次数、排序成本,计算一个总成本的分数。

优化器会选择总成本更低的idx_create_time索引。


这里有复杂的商城项目实战,使用技术:SpringBoot、Spring Security、MySQL、Mybatis、shardingsphere、Nacos、JWT、ElasticSearch、Redis、RocketMQ、MongoDB、Caffeine、FreeMaker、Redisson、Minio、WebSocket、hanlp、mahout、jsoup、Docker等等,非常值得一看


导致索引切换的四大真凶

真凶1:数据分布变化

场景还原

  • 周一数据:已支付订单5万条,其中2025年的5万条

  • 周二数据:已支付订单50万条,其中2025年的50万条


这个例子中数据分布变化很大,周二的数据,比周一的数据一下子多了45万。

可能会影响总成本的分数。


我们可以通过下面的SQL查看数据分布:

代码高亮:

SELECT 

  COUNT(*) AS total,

  SUM(status='paid') AS paid_count,

  SUM(create_time>'2023-01-01') AS new_orders 

FROM orders;


真凶2:统计信息过期

统计信息过期,就像用去年的地图导航,新修的路不会出现在地图上。

MySQL的"地图"就是统计信息。

我们可以通过ANALYZE TABLE ... DELETE STATISTICS命令删除统计信息:

ANALYZE TABLE orders DELETE STATISTICS;

这时候查询可能变成全表扫描:

EXPLAIN SELECT...

显示type: ALL


那么,如何解决这个问题呢?

使用ANALYZE TABLE命令,刷新统计信息(相当于更新地图):

ANALYZE TABLE orders;


真凶3:索引覆盖度差异

点餐类比

  • 菜单A能直接看到菜品价格 → 无需问厨师(覆盖索引)
  • 菜单B只能看到菜品名 → 需要问厨师详情(回表查询)

下面的SQL会走idx_status(需要回表):

代码高亮:

SELECT * FROM orders WHERE status='paid';

下面的SQL会走idx_create_time(覆盖索引):


SELECT create_time FROM 

orders WHERE create_time>'2023-01-01';


真凶4:索引碎片化

索引碎片化就像书本的目录页被撕破,找内容变得困难。

检查方法


SHOW TABLE STATUS LIKE 'orders';

查看Data_free字段,值越大碎片越多。

优化方案

使用ALTER TABLE命令重建索引。

ALTER TABLE orders ENGINE=INNODB;

最近建了一些工作内推群,各大城市都有,欢迎各位HR和找工作的小伙伴进群交流,群里目前已经收集了不少的工作内推岗位。加苏三的微信:li_su223,备注:所在城市,即可进群。



问题排查四步法

第一步:查看当前执行计划

使用EXPLAIN查看当前SQL的执行计划:

代码高亮:

EXPLAIN 

SELECT * FROM orders 

WHERE status='paid' 

AND create_time>'2023-01-01';

第二步:检查统计信息

使用SHOW INDEX命令检查索引的统计信息:

SHOW INDEX FROM orders;

关注Cardinality字段,值越接近真实数据越好。


第三步:分析数据分布

使用下面的SQL分析数据分布:

SELECT 

  COUNT(*) AS total,

  AVG(LENGTH(status)) AS status_avg_len 

FROM orders;


第四步:追踪优化器思考过程

SET optimizer_trace="enabled=on";

SELECT * FROM orders WHERE ...;

SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;

开启optimizer_trace,然后通过INFORMATION_SCHEMA.OPTIMIZER_TRACE表查看追踪优化器思考过程。



三大终极解决方案

方案1:引导优化器选择

使用FORCE INDEX强制使用指定索引:

代码高亮:

SELECT * FROM orders FORCE INDEX(idx_status) WHERE ...;


方案2:创建更优索引

创建更优的联合索引:

ALTER TABLE orders 

ADD INDEX idx_status_create_time(status,create_time);



方案3:定期维护计划

  1. 定期统计信息更新

  2. 定期碎片率检查

  3. 定期索引重建


总结

六个必须检查的点

  • ​WHERE条件字段是否有合适索引

  • ​ORDER BY/GROUP BY是否利用索引排序

  • ​统计信息是否最新(尤其大表每天更新)

  • ​是否存在索引碎片(每月检查一次)

  • ​是否出现索引合并(INDEX_MERGE)

  • 是否使用覆盖索引(减少回表)


三条黄金法则

  1. 二八定律:20%的索引满足80%的查询

  2. 数据驱动:定期分析查询模式调整索引

  3. 防御编程:核心查询明确指定索引



参考文章:原文链接


该文章在 2025/12/5 9:43:28 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved