SpringBoot数据库索引优化指南

 更新时间:2026年04月22日 08:53:19   作者:希望永不加班  
本文详细介绍了MySQL慢查询日志的配置与分析方法,包括动态配置和修改配置文件两种方式,以及SpringBoot项目中打印SQL执行日志的配置,需要的朋友可以参考下

前面我们已经完整攻克了整套缓存体系:从缓存双写一致性的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 > &#39;2024-01-01&#39;

执行后返回的执行计划表格,以及每个字段的详细说明如下(重点字段标红):

(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 > 10where 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配置阿里云仓库的实现方法

    maven配置阿里云仓库的实现方法

    本文主要介绍了maven配置阿里云仓库的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • Maven配置中repositories、distributionManagement、pluginRepositories用法及将已有jar包部署到私服

    Maven配置中repositories、distributionManagement、pluginRepositori

    这篇文章主要介绍了Maven配置中repositories、distributionManagement、pluginRepositories用法及将已有jar包部署到私服,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-03-03
  • 如何解决idea的Translation插件google翻译无法使用问题

    如何解决idea的Translation插件google翻译无法使用问题

    这篇文章主要介绍了如何解决idea的Translation插件google翻译无法使用问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • Spring @Import注解的使用

    Spring @Import注解的使用

    @Import注解算是SpringBoot自动配置原理中一个很重要的注解,本文介绍了该注解的源码分析及使用方法,感兴趣的朋友可以了解下
    2021-05-05
  • Java多态在Spring Boot 3中的实际应用实例教程

    Java多态在Spring Boot 3中的实际应用实例教程

    本文详细介绍了多态在SpringBoot中的应用,通过实际应用场景和设计模式的深度解析,展示了多态在构建松耦合、高内聚软件架构中的重要性,并提供了性能优化和最佳实践建议,感兴趣的朋友跟随小编一起看看吧
    2026-02-02
  • Java中使用ZXing和QRCode生成二维码的示例详解

    Java中使用ZXing和QRCode生成二维码的示例详解

    生成二维码在Java中有多种方法,常用的是通过第三方库来实现,比较流行的库包括 ZXing (Zebra Crossing) 和 QRCode,本文小编就给大家介绍了Java中使用ZXing和QRCode生成二维码的示例,需要的朋友可以参考下
    2024-09-09
  • 详解Spring Boot 使用Spring security 集成CAS

    详解Spring Boot 使用Spring security 集成CAS

    本篇文章主要介绍了详解Spring Boot 使用Spring security 集成CAS,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • 基于java构造方法Vector删除元素源码分析

    基于java构造方法Vector删除元素源码分析

    这篇文章主要介绍了基于java构造方法中对Vector删除元素的源码分析,有需要的朋友可以借鉴参考下,希望可以有所帮助,祝大家早日升职加薪
    2021-09-09
  • java发送url请求获取返回值的二种方法

    java发送url请求获取返回值的二种方法

    这篇文章主要介绍了java发送url请求获取返回值的二种方法,需要的朋友可以参考下
    2014-03-03
  • Java实现Treap树的示例代码

    Java实现Treap树的示例代码

    本文主要介绍了Java实现Treap树的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06

最新评论