当前位置: 代码网 > it编程>编程语言>Java > SpringBoot数据库索引优化指南

SpringBoot数据库索引优化指南

2026年04月22日 Java 我要评论
前面我们已经完整攻克了整套缓存体系:从缓存双写一致性的3种落地策略、caffeine本地缓存与redis分布式缓存的多级架构整合,到分布式多实例缓存同步的redis发布订阅方案,每一步都贴合企业级高并

前面我们已经完整攻克了整套缓存体系:从缓存双写一致性的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数据库索引优化的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com