SpringBoot数据库索引优化指南
前面我们已经完整攻克了整套缓存体系:从缓存双写一致性的3种落地策略、Caffeine本地缓存与Redis分布式缓存的多级架构整合,到分布式多实例缓存同步的Redis发布订阅方案,每一步都贴合企业级高并发落地标准。
缓存作为系统性能优化的“上层手段”,核心作用是减轻数据库的查询压力、缩短接口响应时间,但很多开发同学会陷入一个致命误区:只要加了缓存,系统性能就一定能达标。
实际上,在企业真实生产项目中,80%以上的系统性能瓶颈,根源都不在缓存,而在底层MySQL数据库本身。小数据量(几万条以内)场景下,哪怕是全表扫描、劣质SQL,也能做到毫秒级响应,看不出任何问题;但一旦单表数据量突破几十万、几百万,甚至上千万,没有合理的索引、不规范的SQL写法、大量慢查询,会直接导致接口耗时从几十毫秒飙升到几百毫秒、几秒,甚至几十秒,MySQL的CPU、磁盘IO会被直接打满,数据库连接池耗尽。
更关键的是:哪怕你的缓存架构设计得再完美,底层数据库本身扛不住流量,缓存也会失去意义——一旦缓存失效、击穿,海量请求会瞬间涌入早已不堪重负的数据库,直接引发系统雪崩。
这里必须强调一个性能优化的核心优先级:先优化SQL与数据库索引,再做缓存优化,最后做架构层面(分库分表、读写分离)的优化。数据库是系统性能的“根基”,只有把根基打牢,缓存才能真正发挥最大价值,否则一切都是空中楼阁。
一、SpringBoot 环境下开启MySQL慢查询日志
想要优化慢查询,第一步永远是“精准定位慢SQL”——只有找到所有执行耗时过长的SQL,才能针对性地进行优化。MySQL内置了完善的慢查询日志功能,能够自动记录所有超过阈值的SQL,包含完整的执行明细,是线上排查慢查询的最权威工具。
结合SpringBoot项目的开发、生产环境,我们提供两种配置方式,适配不同场景,全部可直接复制落地。
1. 方式一:SQL动态配置(线上推荐,无需重启MySQL)
线上生产环境禁止随意重启MySQL服务(重启会导致服务中断),因此我们优先使用SQL命令动态开启慢查询日志,即时生效,无需重启数据库,排查完成后还可以动态关闭,不影响线上服务。
完整动态配置SQL
-- 1. 开启慢查询日志(1=开启,0=关闭),全局生效 SETGLOBAL slow_query_log =ON; -- 2. 设置慢查询阈值,单位:秒,这里设置为0.5秒(500ms),适配普通业务接口 SETGLOBAL long_query_time =0.5; -- 3. 开启“记录未使用索引的查询”(非常关键!即使SQL执行耗时未超过阈值,只要没走索引,也会被记录) -- 避免遗漏“隐性慢查询”(比如数据量增长后,未走索引的SQL会逐渐变成慢查询) SETGLOBAL log_queries_not_using_indexes =ON; -- 4. 设置慢查询日志的存储路径(可选,默认路径可通过SHOW VARIABLES查看) -- 注意:路径需确保MySQL用户有读写权限,避免日志无法生成 SETGLOBAL slow_query_log_file ='/var/lib/mysql/slow.log'; -- 5. 设置日志输出格式(可选,默认FILE,即输出到文件;可设置为TABLE,存储到mysql.slow_log表中) SETGLOBAL log_output ='FILE,TABLE'; -- 6. 查看所有慢查询配置是否生效(验证配置) SHOW VARIABLES LIKE'%slow_query%'; -- 查看慢查询日志相关配置 SHOW VARIABLES LIKE'long_query_time'; -- 查看慢查询阈值 SHOW VARIABLES LIKE'log_queries_not_using_indexes'; -- 查看未走索引查询的记录配置
注意事项
- 修改
GLOBAL全局参数后,需要重新断开数据库连接(比如重启SpringBoot服务、重新连接Navicat),新的连接才会加载最新的配置;已存在的连接,依然使用旧的配置。 - 线上环境排查完成后,建议关闭“记录未使用索引的查询”(
SET GLOBAL log_queries_not_using_indexes = OFF),避免大量无索引的普通SQL占用日志空间,影响慢查询日志的可读性。 - 如果慢查询日志文件过大(超过1G),可以使用
mysqldumpslow工具进行分析,或者手动清空日志(echo "" > /var/lib/mysql/slow.log),避免占用过多磁盘空间。
2. 方式二:修改MySQL配置文件(永久生效,适合开发/测试环境)
适合开发环境、测试环境,或者新项目初始化配置,修改MySQL的配置文件后,重启MySQL服务即可永久生效,无需每次手动执行SQL配置。
不同系统的配置文件路径
- Linux系统(CentOS、Ubuntu):
/etc/my.cnf或/etc/mysql/my.cnf; - Windows系统:
MySQL安装目录/my.ini(比如C:Program FilesMySQLMySQL Server 8.0my.ini); - Docker部署的MySQL:需要挂载配置文件,或者进入容器内部修改
/etc/my.cnf。
完整配置内容
[mysqld] # 开启慢查询日志(必填) slow_query_log = ON # 慢查询阈值,单位:秒,设置为0.5秒(500ms)(必填) long_query_time = 0.5 # 慢查询日志存储路径(必填,确保路径可写) slow_query_log_file = /var/lib/mysql/slow.log # 记录所有未使用索引的查询(开发环境建议开启,线上排查时开启,平时可关闭) log_queries_not_using_indexes = ON # 日志输出格式:FILE(输出到文件)+ TABLE(存储到mysql.slow_log表),方便多方式查看 log_output = FILE,TABLE # 忽略系统数据库(mysql、information_schema等)的慢查询,避免日志冗余 ignore_db_dirs = mysql,information_schema,performance_schema,sys # 记录慢查询的详细信息(可选,默认开启) log_slow_admin_statements = ON# 记录管理员操作中的慢查询(如alter table) log_slow_slave_statements = ON# 主从复制场景下,记录从库的慢查询
配置生效步骤
1. 修改配置文件后,保存退出;
2. 重启MySQL服务(不同系统重启命令不同):
- Linux(CentOS):
systemctl restart mysqld; - Linux(Ubuntu):
systemctl restart mysql; - Windows:在服务中找到“MySQL”,右键重启;
- Docker:
docker restart 容器ID。
3. 重启后,连接MySQL,执行 SHOW VARIABLES LIKE '%slow_query%',验证配置是否生效。
3. SpringBoot 项目配置:打印SQL执行日志(本地开发调试)
开发环境中,我们可以通过配置SpringBoot的日志,直接打印SQL的执行语句、执行耗时,方便本地快速排查慢SQL,无需依赖MySQL的慢查询日志。
以下配置适配MyBatis、MyBatis-Plus,复制到 application.yml 即可生效:
spring:
datasource:
# 数据库连接配置(替换为自己的数据库信息)
url:jdbc:mysql://localhost:3306/springboot_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username:root
password:123456
driver-class-name:com.mysql.cj.jdbc.Driver
# MyBatis-Plus 配置(如果使用原生MyBatis,配置类似)
mybatis-plus:
mapper-locations:classpath:mapper/*.xml# mapper文件路径
type-aliases-package:com.xxx.entity# 实体类包路径
configuration:
# 打印完整SQL语句、执行耗时(本地开发开启,线上关闭)
log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
# 开启驼峰命名映射(可选,避免字段名与实体类属性不匹配)
map-underscore-to-camel-case:true
# 日志配置(可选,细化日志输出,避免冗余)
logging:
level:
# 打印指定包下的SQL日志(替换为自己的mapper包路径)
com.xxx.mapper:debug
# 关闭其他无关日志,提升可读性
org.springframework:warn
com.baomidou.mybatisplus:warn配置生效后,启动SpringBoot项目,执行接口请求,控制台会输出类似如下日志,清晰看到SQL执行耗时:
==> Preparing: SELECT id,name,age,create_time FROM user WHERE id = ? ==> Parameters: 1(Long) <== Columns: id, name, age, create_time <== Row: 1, 张三, 25, 2024-01-01 10:00:00 <== Total: 1 <== Updates: 0 <== Elapsed: 12.35 ms # 执行耗时,一目了然
4. 慢查询日志查看与分析工具
慢查询日志生成后,我们需要对日志进行分析,提取出耗时最长、执行最频繁的慢SQL,针对性优化。这里推荐4种常用工具,适配不同场景。
(1)原生日志查看
直接通过命令行查看慢查询日志,适合线上临时排查,无需安装额外工具:
# 1. 实时查看慢查询日志(最新的慢SQL会实时输出) tail -f /var/lib/mysql/slow.log # 2. 查看日志的前10行(快速了解日志格式) head -n 10 /var/lib/mysql/slow.log # 3. 统计日志中所有慢SQL的数量 grep -c "Query_time" /var/lib/mysql/slow.log # 4. 查找耗时超过1秒的慢SQL grep "Query_time>1" /var/lib/mysql/slow.log
(2)mysqldumpslow
MySQL自带的慢查询日志分析工具,无需额外安装,能够对慢SQL进行汇总、排序,快速找到最耗时、最频繁的慢SQL,线上最常用。
常用命令(复制可用):
# 1. 按执行耗时排序,查看耗时最高的10条慢SQL(最常用) mysqldumpslow -s t -n 10 /var/lib/mysql/slow.log # 2. 按执行次数排序,查看最频繁执行的10条慢SQL mysqldumpslow -s c -n 10 /var/lib/mysql/slow.log # 3. 按锁定时间排序,查看锁定时间最长的10条慢SQL mysqldumpslow -s l -n 10 /var/lib/mysql/slow.log # 4. 过滤指定数据库的慢SQL(比如只查看springboot_demo库的慢SQL) mysqldumpslow -d springboot_demo /var/lib/mysql/slow.log # 5. 输出详细的慢SQL信息(包含执行时间、扫描行数、返回行数) mysqldumpslow -v /var/lib/mysql/slow.log
命令参数说明:-s 表示排序方式(t=耗时、c=次数、l=锁定时间),-n 表示显示的条数,-d 表示指定数据库,-v 表示显示详细信息。
(3)pt-query-digest
Percona Toolkit中的核心工具,比 mysqldumpslow 功能更强大,能够对慢查询日志进行深度分析,生成详细的统计报告,适合慢SQL数量多、场景复杂的线上环境。
安装命令(Linux):yum install percona-toolkit -y(CentOS)、apt install percona-toolkit -y(Ubuntu)。
常用命令:
# 分析慢查询日志,生成详细报告(输出到屏幕) pt-query-digest /var/lib/mysql/slow.log # 分析慢查询日志,将报告输出到文件(方便后续查看) pt-query-digest /var/lib/mysql/slow.log > slow_query_analysis.log
报告核心信息:会按SQL执行频率、耗时排序,标注每条SQL的扫描行数、返回行数、执行用户、执行时间,甚至会给出优化建议,非常实用。
(4)可视化工具
开发环境中,我们可以使用可视化工具查看慢查询日志,操作简单、直观:
注意:线上环境建议使用 mysqldumpslow 或 pt-query-digest 分析慢查询日志,避免使用可视化工具(需要连接线上数据库,存在安全风险,且可能占用数据库资源)。
二、Explain 执行计划全字段详解
找到慢SQL后,下一步就是分析“为什么这条SQL执行慢”——核心工具就是 explain 执行计划。
explain 是MySQL提供的一个核心命令,在SQL语句前加上 explain,可以查看MySQL优化器对这条SQL的执行计划,包括:SQL的执行方式(全表扫描还是索引扫描)、使用了哪个索引、扫描了多少行数据、返回多少行数据、是否使用了临时表、是否进行了排序等关键信息。
掌握 explain 的使用,是区分“新手”和“资深开发者”的关键,也是面试高频考点。下面我们结合SpringBoot项目中的真实SQL,逐字段详解 explain 执行计划,确保每个人都能看懂、会用。
1. Explain 基本使用方法
使用非常简单,在需要分析的SQL语句前加上 explain 即可,示例:
-- 分析单表查询 EXPLAIN SELECT id, name, age FROMuserWHERE age >20; -- 分析多表关联查询 EXPLAIN SELECT u.id, u.name, o.order_no FROMuser u LEFTJOIN `order` o ON u.id = o.user_id WHERE u.age >20; -- 分析更新、删除语句(查看执行计划,判断是否走索引) EXPLAIN UPDATEuserSET name ='李四'WHERE id =1;
执行后,MySQL会返回一个包含12个字段的表格,每个字段都对应SQL执行的关键信息,我们逐一拆解。
2. Explain 12个字段逐字详解
我们以SpringBoot项目中的商品表(product)为例,表结构如下(复制可创建):
CREATE TABLE `product` ( `id` bigintNOT NULL AUTO_INCREMENT COMMENT '商品ID(主键)', `name` varchar(100) NOT NULL COMMENT '商品名称', `category_id` bigintNOT NULL COMMENT '分类ID', `price` decimal(10,2) NOT NULL COMMENT '商品价格', `stock` intNOT NULL COMMENT '库存', `create_time` datetime NOT NULL COMMENT '创建时间', `update_time` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_category_id` (`category_id`), -- 分类ID索引 KEY `idx_create_time` (`create_time`) -- 创建时间索引 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
我们以这条SQL为例,分析 explain 执行计划:EXPLAIN SELECT id, name, price FROM product WHERE category_id = 10 AND create_time > '2024-01-01'
执行后返回的执行计划表格,以及每个字段的详细说明如下(重点字段标红):
(1)id:SQL执行的顺序标识
核心作用:标识SQL语句中每个查询块的执行顺序,有以下三种情况:
示例中,id=1,说明只有一个查询块,按顺序执行即可。
(2)select_type:查询类型
标识当前查询的类型,决定了查询的复杂度和执行方式,常见值及说明(重点记前5个):
select_type | 说明 | 实战场景 |
SIMPLE | 简单查询,无子查询、无union | SELECT * FROM product WHERE id = 1 |
PRIMARY | 主查询,包含子查询时,最外层的查询 | SELECT * FROM user WHERE id IN (SELECT user_id FROM order) |
SUBQUERY | 子查询,嵌套在主查询中的查询(不依赖主查询结果) | 同上,子查询 SELECT user_id FROM order |
DERIVED | 派生表查询,子查询返回的结果作为临时表 | SELECT * FROM (SELECT id FROM product) AS t |
UNION | union查询的第二个及以后的查询 | SELECT id FROM user UNION SELECT id FROM product |
UNION RESULT | union查询的结果汇总 | 同上,汇总两个查询的结果 |
示例中,select_type=SIMPLE,说明是简单查询,无复杂嵌套。
(3)table:当前查询涉及的表
显示当前查询块正在操作的表名,如果是子查询、派生表,会显示临时表的名称(如derived2、union1)。
示例中,table=product,说明当前查询操作的是商品表。
(4)type:访问类型(判断是否走索引)
这是 explain 中最核心的字段,标识MySQL访问表的方式,即“如何获取数据”,决定了查询的效率,按效率从高到低排序(重点记前6个):
面试必背:type 字段的优化目标是“至少达到 range 级别,最好达到 ref 或 const 级别”,如果出现 ALL(全表扫描),说明没有走索引,需要优先优化。
示例中,type=ref,说明通过普通索引 idx_category_id 查询,效率较高。
(5)possible_keys:可能使用的索引
显示MySQL优化器认为当前查询“可能”使用的索引,不一定会实际使用(可能有多个,用逗号分隔)。
示例中,possible_keys=idx_category_id,idx_create_time,说明优化器认为可能使用分类ID索引或创建时间索引。
(6)key:实际使用的索引
显示MySQL优化器实际使用的索引,如果为NULL,说明没有使用任何索引(走全表扫描)。
示例中,key=idx_category_id,说明实际使用的是分类ID索引,与possible_keys中的一个一致。
关键注意:如果 possible_keys 有值,但 key 为 NULL,说明索引建立不合理,或者SQL写法有问题,导致优化器放弃使用索引。
(7)key_len:实际使用的索引长度(单位:字节)
核心作用:判断索引的使用情况,尤其是联合索引,通过key_len可以判断联合索引使用了哪些字段(遵循最左前缀匹配原则)。
计算规则(简单记):
varchar(100):utf8mb4编码,每个字符占4字节,100*4=400字节,加上null标识(1字节),共401字节;bigint:8字节,int:4字节,datetime:8字节;如果字段为NOT NULL,不需要null标识,减少1字节。
示例中,key=idx_category_id(category_id是bigint NOT NULL),key_len=8,符合计算规则,说明索引使用正常。
(8)ref:与索引匹配的列或常量
显示与当前使用的索引匹配的列名,或者常量值,说明索引是如何被使用的。
示例中,ref=const,说明category_id=10(常量),与索引idx_category_id匹配,符合查询条件。
(9)rows:MySQL预估要扫描的行数
显示MySQL优化器预估的、需要扫描的行数,不是实际扫描的行数,但能反映查询的效率——行数越少,查询效率越高。
示例中,rows=100,说明优化器预估需要扫描100行数据就能找到符合条件的结果;如果rows=100000,说明需要扫描10万行数据,效率极低,大概率是全表扫描。
关键注意:如果rows数值很大,但实际返回的行数很少,说明索引建立不合理,或者查询条件过滤性差,需要优化。
(10)Extra:额外信息
这是 explain 中最灵活、最有价值的字段,包含了SQL执行的额外细节,很多慢查询的问题都能从这里找到原因,常见值及说明(重点记红框内的):
✅ 理想状态(优化到位):
Using index:使用了覆盖索引(查询的字段都在索引中,无需回表查询数据),效率极高,是优化的目标;
Using where:使用了where条件过滤数据,过滤效果较好;
Using index condition:使用了索引条件推送(ICP),减少回表查询的次数,提升效率。
❌ 需要优化的状态(慢查询常见):
Using filesort:无法使用索引排序,需要在磁盘或内存中进行排序(文件排序),耗时极长,尤其是数据量大时;
Using temporary:需要创建临时表存储查询结果,再进行后续操作(比如group by、distinct、union),耗时较长;
Using join buffer:多表关联时,没有使用索引,需要使用连接缓冲区存储关联数据,效率低;
Using where; Using filesort:使用了where过滤,但排序没有使用索引,需要优化排序字段的索引;
Using where; Using temporary; Using filesort:最糟糕的情况,需要创建临时表、进行文件排序,必须优先优化。
示例中,Extra=Using index condition; Using where,说明使用了索引条件推送和where过滤,执行效率较好。
(11)filtered:过滤比例(百分比)
显示经过where条件过滤后,剩余数据占总扫描行数的比例,比例越高,说明过滤效果越好(查询条件越精准)。
示例中,filtered=80,说明经过where条件过滤后,剩余80%的扫描行数是符合条件的,过滤效果较好;如果filtered=1,说明过滤效果极差,大部分扫描的行数都不符合条件,需要优化查询条件。
(12)partitions:分区表相关(可选)
如果表是分区表,显示当前查询涉及的分区;如果不是分区表,显示为NULL。
示例中,partitions=NULL,说明商品表不是分区表。
3. Explain 实战案例(判断慢SQL原因)
结合上面的字段详解,我们用一个实战案例,演示如何通过 explain 分析慢SQL的原因。
案例:慢SQL语句
-- 商品表有80万条数据,查询分类ID为10、价格大于100的商品,耗时1.2s SELECT * FROM product WHERE category_id = 10 AND price > 100;
执行 Explain 后的关键字段:
分析原因:
虽然建立了 idx_category_id 索引,但查询条件中包含 price > 100,而 price 字段没有建索引,且 idx_category_id 是单字段索引,MySQL优化器判断“使用索引后,还需要回表查询price字段,再过滤”,效率不如直接全表扫描,因此放弃使用索引,导致慢查询。
优化方案:
建立联合索引 idx_category_price(category_id, price),遵循最左前缀匹配原则,查询条件中的category_id在前,price在后,能够直接命中索引,同时过滤两个条件,无需回表。
优化后 Explain 关键字段:
优化后,SQL执行耗时从1.2s降至20ms,性能提升60倍。
面试实战题:如何通过 Explain 判断SQL是否走索引?如何判断慢SQL的原因?(标准回答)
三、InnoDB索引底层原理
很多开发同学只会建索引、用索引,却不懂索引的底层原理,导致遇到索引失效、性能不达标的问题时,无法从根源上解决。想要真正做好索引优化,必须先吃透InnoDB索引的底层实现——毕竟,所有的索引优化技巧,都源于对底层原理的理解。
InnoDB是MySQL最常用的存储引擎(企业生产环境首选),其索引底层基于B+树实现,这也是MySQL索引高效的核心原因。下面我们不搞复杂的理论堆砌,结合实战场景,拆解B+树索引的核心特性、索引类型及底层存储逻辑,重点解决“为什么这么建索引高效”“为什么有些索引会失效”的问题。
1. InnoDB B+树索引核心特性
B+树是一种平衡多路查找树,InnoDB对其进行了优化,使其更适配数据库的读写场景,核心特性如下(直接决定索引的使用效率):
2. InnoDB 两大索引类型:聚簇索引 vs 非聚簇索引
InnoDB有两种核心索引类型,两者的存储逻辑、查询效率差异极大,很多慢查询都源于对这两种索引的混淆,必须严格区分。
(1)聚簇索引(主键索引,Clustered Index)
聚簇索引是InnoDB的核心索引,也是表的“主索引”,每个表只能有一个聚簇索引,其底层存储逻辑如下:
举个例子:查询select * from product where id = 10(id是主键,聚簇索引),MySQL会通过B+树找到id=10的叶子节点,直接读取该节点中的完整商品数据,无需额外操作,耗时通常在10ms以内。
(2)非聚簇索引(二级索引,Secondary Index)
非聚簇索引是除聚簇索引以外的所有索引(如普通索引、联合索引、唯一索引),也叫二级索引,一个表可以有多个非聚簇索引,其底层存储逻辑与聚簇索引完全不同:
举个例子:查询select * from product where category_id = 10(category_id是普通索引,非聚簇索引),查询流程如下:
关键结论:非聚簇索引查询会多一次回表操作,比聚簇索引查询效率低;如果能避免回表查询,就能大幅提升非聚簇索引的查询效率——这就是“覆盖索引”的核心价值(后面会详细讲解)。
面试必背:聚簇索引与非聚簇索引的核心区别?
3. 回表查询与覆盖索引(优化非聚簇索引的核心)
通过上面的讲解,我们知道:非聚簇索引的查询效率低,核心原因是“回表查询”——多一次磁盘IO操作,尤其是数据量大、查询频繁时,回表会严重拖慢查询性能。而解决这个问题的核心方案,就是覆盖索引。
(1)回表查询的危害
假设商品表(product)有80万条数据,非聚簇索引idx_category_id(category_id),执行查询select * from product where category_id = 10:
可见,回表查询的磁盘IO开销极大,是慢查询的常见诱因之一,而覆盖索引能彻底解决这个问题。
(2)覆盖索引的定义与实战用法
定义:如果非聚簇索引的索引键,包含了查询语句中所有需要的字段(select后面的字段),那么通过这个非聚簇索引查询时,无需回表,直接从索引中获取所有数据,这个非聚簇索引就是覆盖索引。
核心逻辑:让非聚簇索引的叶子节点,不仅存储主键值,还存储查询所需的其他字段,从而避免回表。
实战案例(延续前文商品表):
(3)覆盖索引的使用技巧
4. 索引失效的底层原因
前面我们提到“SQL写法不规范、索引建立不合理会导致索引失效”,但背后的底层原因,都与InnoDB B+树索引的存储逻辑有关,总结3个核心底层原因:
如果你在实战中遇到问题,欢迎在评论区留言交流,一起避坑、一起进步!
别忘了点赞+在看+收藏三连,关注我,解锁更多 SpringBoot AOP 实战干货,下期再见❤️
• Navicat:连接MySQL后,点击「工具」→「慢查询日志」,即可查看、筛选慢SQL;
• IDEA:安装「Database Tools」插件,连接MySQL后,在「Database」面板中找到「Slow Queries」,即可查看慢查询日志;
• phpMyAdmin:登录后,点击「状态」→「慢查询日志」,即可查看和分析。
• id相同:执行顺序由上到下(单表查询、简单多表关联);
• id不同:id值越大,执行优先级越高(子查询场景,先执行子查询,再执行主查询);
• id为NULL:最后执行(比如union查询的汇总操作)。
• system:表中只有一行数据(系统表),效率最高,几乎不会出现;
• const:通过主键或唯一索引查询,只匹配一行数据,效率极高(比如 where id = 1);
• eq_ref:多表关联时,通过主键或唯一索引关联,每行数据只匹配一行关联数据(比如user表和order表,通过user.id=order.user_id关联,user.id是主键);
• ref:通过普通索引查询,匹配多行数据(比如 where category_id = 10,category_id是普通索引);
• range:通过索引范围查询(比如 where id > 10、where age between 20 and 30),效率比ref略低;
• index:全索引扫描(扫描整个索引表,不扫描数据),效率较低;
• ALL:全表扫描(扫描整个表的数据),效率最低,慢查询的主要原因之一。
• Using index:使用了覆盖索引(查询的字段都在索引中,无需回表查询数据),效率极高,是优化的目标;
• Using where:使用了where条件过滤数据,过滤效果较好;
• Using index condition:使用了索引条件推送(ICP),减少回表查询的次数,提升效率。
• Using filesort:无法使用索引排序,需要在磁盘或内存中进行排序(文件排序),耗时极长,尤其是数据量大时;
• Using temporary:需要创建临时表存储查询结果,再进行后续操作(比如group by、distinct、union),耗时较长;
• Using join buffer:多表关联时,没有使用索引,需要使用连接缓冲区存储关联数据,效率低;
• Using where; Using filesort:使用了where过滤,但排序没有使用索引,需要优化排序字段的索引;
• Using where; Using temporary; Using filesort:最糟糕的情况,需要创建临时表、进行文件排序,必须优先优化。
• type = ALL(全表扫描)
• possible_keys = idx_category_id
• key = NULL(未使用任何索引)
• rows = 800000(预估扫描80万行)
• Extra = Using where(只使用where过滤,无索引)
• type = range(索引范围查询)
• possible_keys = idx_category_price
• key = idx_category_price(实际使用联合索引)
• rows = 5000(预估扫描5000行,大幅减少)
• Extra = Using index condition; Using where(使用索引条件推送,过滤效果好)
1. 判断是否走索引:看 type 字段(是否为const、ref、range)和 key 字段(是否为非NULL);如果key为NULL、type为ALL,说明未走索引;
2. 判断慢SQL原因:结合 rows(扫描行数)、Extra(是否有filesort、temporary)、type 字段,比如:rows过大说明扫描行数多,Extra出现filesort说明排序无索引,type为ALL说明全表扫描。
• 平衡树结构,查询效率稳定:B+树的高度固定(一般为3-4层),无论查询哪个数据,都只需要3-4次磁盘IO操作,耗时稳定(磁盘IO是MySQL性能瓶颈,减少IO次数就是提升性能)。比如单表数据量千万级时,B+树高度仅为4层,查询耗时可控制在10ms以内。
• 叶子节点有序且相连,支持范围查询:B+树的所有叶子节点按顺序排列,且叶子节点之间通过指针相连,这也是“range查询”(如id>10、age between 20 and 30)高效的核心原因——MySQL只需找到范围的起始叶子节点,就能通过指针遍历所有符合条件的节点,无需回表扫描整个索引。
• 非叶子节点只存索引键,叶子节点存完整数据(聚簇索引):这是InnoDB索引与MyISAM索引的核心区别,也是理解“回表查询”“覆盖索引”的关键,后面会详细拆解。
• 索引键有序,支持排序优化:B+树的索引键是有序存储的,因此当SQL中包含order by、group by时,如果排序字段与索引键一致,MySQL可以直接利用索引的有序性完成排序,避免出现“Using filesort”(文件排序),大幅提升排序效率。
• 索引键:默认使用**主键(primary key)**作为索引键;如果表没有主键,MySQL会自动选择一个唯一非空字段作为聚簇索引;如果没有唯一非空字段,MySQL会自动生成一个隐藏的row_id作为聚簇索引。
• 存储结构:B+树的非叶子节点存储主键值,叶子节点存储整个行的数据(而非指针)。也就是说,聚簇索引的叶子节点就是表的实际数据行,索引与数据是“聚簇”在一起的。
• 查询效率:通过聚簇索引查询时,找到叶子节点就直接获取到了完整行数据,无需回表,效率极高(type可达const级别)。
• 索引键:可以是任意字段(或字段组合),如category_id、create_time、(name, age)等。
• 存储结构:B+树的非叶子节点存储非聚簇索引的键值,叶子节点不存储完整行数据,只存储聚簇索引的键值(主键值)。
• 查询流程(重点!回表查询的根源):通过非聚簇索引查询时,首先找到叶子节点中的主键值,然后再通过聚簇索引(主键索引)查找对应的叶子节点,才能获取到完整的行数据——这个“通过非聚簇索引找到主键,再通过聚簇索引找数据”的过程,就是回表查询。
1. 通过非聚簇索引(idx_category_id)的B+树,找到所有category_id=10的叶子节点,获取对应的主键值(id);
2. 再通过聚簇索引(主键id)的B+树,根据主键值找到对应的叶子节点,获取完整的商品数据;
3. 将所有符合条件的商品数据汇总,返回给客户端。
1. 存储内容:聚簇索引叶子节点存完整行数据,非聚簇索引叶子节点存主键值;
2. 数量限制:聚簇索引每个表只能有一个,非聚簇索引可以有多个;
3. 查询效率:聚簇索引无需回表,效率更高;非聚簇索引需回表,效率较低;
4. 索引键:聚簇索引默认用主键,非聚簇索引可自定义字段。
• 如果没有覆盖索引:需要先通过idx_category_id找到所有category_id=10的主键id(约5000条),再通过聚簇索引逐一回表查询5000条数据,共产生5001次磁盘IO(1次找主键,5000次回表),执行耗时约500ms;
• 如果有覆盖索引:无需回表,直接从非聚簇索引中获取所有需要的字段,仅需1次磁盘IO,执行耗时可降至20ms以内。
• 慢查询语句:select id, name, price from product where category_id = 10(当前索引:idx_category_id,仅包含category_id);
• 问题:查询需要id、name、price三个字段,idx_category_id仅包含category_id,叶子节点只存主键id,因此需要回表查询name和price字段,耗时约500ms;
• 优化方案:创建联合索引idx_category_name_price(category_id, name, price),该索引包含了查询所需的所有字段(category_id用于过滤,name、price用于返回结果);
• 优化后:通过该联合索引查询时,叶子节点包含category_id、name、price、主键id,无需回表,执行耗时降至20ms以内,Explain的Extra字段会显示“Using index”(标识使用了覆盖索引)。
• 避免滥用select *:select *会查询表中所有字段,几乎不可能使用覆盖索引(除非索引包含所有字段,这会导致索引过大),因此尽量只查询需要的字段;
• 联合索引的字段顺序:将查询条件中的过滤字段(where后面的字段)放在联合索引的前面,查询所需的返回字段放在后面,既保证能命中索引,又能实现覆盖;
• 避免索引过大:覆盖索引虽好,但不能包含过多字段(尤其是大字段,如varchar(255)、text),否则会导致索引体积过大,增加磁盘占用和写入开销(insert/update/delete时需要维护索引)。
• 索引键无法有序匹配:B+树索引的查询依赖于索引键的有序性,如果SQL写法破坏了索引键的有序性(如函数操作、运算、左模糊查询),MySQL无法通过索引键快速定位数据,只能放弃索引,走全表扫描;
• 索引过滤性太差:低区分度字段(如status、gender)的索引,无法有效过滤数据,MySQL优化器判断“使用索引的开销(回表、索引扫描)大于全表扫描的开销”,会放弃使用索引;
• 联合索引不满足最左前缀匹配:联合索引的B+树,是按“最左前缀”的顺序构建的,若查询条件不包含最左前缀字段,无法命中索引(后面会详细拆解)。
以上就是SpringBoot数据库索引优化指南的详细内容,更多关于SpringBoot数据库索引优化的资料请关注脚本之家其它相关文章!
相关文章
Maven配置中repositories、distributionManagement、pluginRepositori
这篇文章主要介绍了Maven配置中repositories、distributionManagement、pluginRepositories用法及将已有jar包部署到私服,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2025-03-03
如何解决idea的Translation插件google翻译无法使用问题
这篇文章主要介绍了如何解决idea的Translation插件google翻译无法使用问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2024-05-05
Java多态在Spring Boot 3中的实际应用实例教程
本文详细介绍了多态在SpringBoot中的应用,通过实际应用场景和设计模式的深度解析,展示了多态在构建松耦合、高内聚软件架构中的重要性,并提供了性能优化和最佳实践建议,感兴趣的朋友跟随小编一起看看吧2026-02-02
详解Spring Boot 使用Spring security 集成CAS
本篇文章主要介绍了详解Spring Boot 使用Spring security 集成CAS,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2017-05-05


最新评论