参考:密码:nfyq

徐庶—MySQL调优实践最全!必懂!知识点一站式掌握https://www.yuque.com/tulingzhouyu/sfx8p0/qnxql079alg2ghhz?singleDoc=#17014c6e

一、索引

1.1 什么是索引?

快速定位数据的一种存储结构,比如文章目录;设计思想:以空间换时间。

1.2 索引的种类

常见的索引分类:

按数据结构分 B+tree索引 Hash索引 Full-text索引
物理存储 聚集索引 非聚集索引
字段特性 主键索引(PRIMARY KEY) 唯一索引(UNIQUE) 普通索引(INDEX) 全文索引(FULLTEXT)
字段个数 单列索引 联合索引(复合、组合)

常见的索引数据结构和区别:

  • 二叉树、红黑树、B树、B+树
  • 区别:树的高度影响获取数据的性能(每一个树节点都是一次磁盘I/O)

1.3 B+tree索引

1.3.1 二叉树

特点:一个节点最多两个子节点,小的在左边,大的在右边

如果数据按顺序进入:

形成一个链表结构,树的高度很高,元素的查找效率等于链表查询O(n),数据检索效率低下。

1.3.2 红黑二叉树(平衡二叉树)

通过自旋平衡,减少树的高度。但是数据过多时,节点个数就越多,树的高度也会增高,影响查询效率。

1.3.3 B-树

特点:

B树的节点可以有多个子节点,多叉树

一个节点可以存储多个元素;

平衡多路查找树;

不适合范围查找

3阶B树
3阶B树

1.3.4 B+树

B+树结构图

对B-树的优化(为什么MySql用B+树):

  1. 所有的数据存储在叶子结点上(最下面一行),排序、分组、去重查询更简单
  2. 非叶子结点没有放数据,可以存更多的键值,树会更矮更胖,查询效率更快
  3. 每一个非叶子节点上都有对应的双向指针,范围查询、排序友好

如果一个表没有主键索引还会创建B+树吗?

答案
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓
会的,InnoDB是MySQL的一种存储引擎,会为每个表创建一个主键索引。如果表没有一个明确的主键索引,InnoDB会使用一个隐藏的、自动生成的主键创建索引,使用的是B+树结构。

​

1.4 Hash索引

InonoDB不支持显式创建Hash索引,只支持自适应Hash索引;

Memory引擎支持Hash索引

Hash索引底层原理 类似与java的HashMap的原理

1.5 聚集索引与非聚集索引

按物理存储分类:InnoDB存储方式是聚集索引

聚集索引索引和数据存在一起(叶子节点存整行数据

MyISAM是非聚集索引

非聚集索引索引和数据分开存(叶子节点只存主键值

1.5.1 二级索引

非主键索引都是二级索引,也是非聚集索引

1.5.2 覆盖索引

已经在age索引中查到了id,节省了回表步骤,查询更快

这种索引列覆盖了查询字段就是覆盖索引

1.5.3 索引下推

索引下推 (Index Condition Pushdown, ICP),是MySQL5.6针对扫描二级索引的优化,用来在范围查询时减少回表次数,适用于MyISAM和InnoDB

查询SELECT * FROM user WHERE name LIKE '张%' AND age = 20;

1. 无索引下推
  1. 引擎按 name LIKE '张%' 找到 100 条索引记录(含主键)。
  2. 全部 100 次回表,读取整行数据。
  3. 返回 Server 层,再过滤 age = 20,只剩 10 条。
  • 浪费 90 次回表 I/O
2. 开启索引下推
  1. 引擎按 name LIKE '张%' 遍历索引。
  2. 在索引叶子节点直接判断 age = 20
  3. 只把符合条件的 10 条主键传给 Server。
  4. 仅 10 次回表,效率显著提升。

1.6 单列、组合索引

1.6.1 单列索引

单列索引:只对一个字段建索引

CREATE INDEX idx_name ON user(name);

1.6.2 联合索引(复合索引)

多个字段按顺序组合成一个索引。

CREATE INDEX idx_name_age_sex ON user(name, age, sex);

name → age → sex 按顺序排序

核心规则:最左前缀原则

        联合索引,必须从左往右数,连续不断才能用;跳过左边的列,右侧列全部失效;范围查询会截断右侧列。

例如索引 (a,b,c)

  • 能用到:where a=?
  • 能用到:where a=? and b=?
  • 能用到:where a=? and b=? and c=?
  • 用不到:where b=?
  • 用不到:where a=? and c=?(b 断了)

1.6.3 单列索引与联合索引的区别

对比维度 单列索引 (Single-Column Index) 联合索引 (Composite/Combined Index)
索引结构 仅对单个字段构建 B + 树索引 多个字段按顺序组合构建 B + 树索引
生效规则 仅该字段作为查询条件时生效 遵循最左前缀原则,从最左列开始的连续字段组合才能生效
多条件查询效率 多条件时 MySQL 通常仅能选择一个索引使用,无法同时利用多个单列索引过滤 可利用多字段连续过滤,一次索引扫描完成多条件筛选,效率远高于多个单列索引
回表次数 多条件查询时,仅用一个索引过滤,剩余条件需回表后筛选,回表次数多 多字段联合过滤,精准定位数据,回表次数大幅减少(覆盖索引可完全避免回表)
空间占用 每个索引是独立 B + 树,多个单列索引总空间占用大 一个索引树包含多列,总空间占用远小于多个单列索引
写入性能(增删改) 每新增 / 修改 / 删除数据,需更新所有相关单列索引,索引越多写入越慢 仅需更新一个联合索引,写入性能优于多个单列索引
维护成本 索引数量多,维护、优化、排查成本高 索引数量少,结构清晰,维护成本低
适用查询场景 1. 仅按单个字段高频查询2. 字段区分度极高(如手机号、身份证号)3. 多字段查询不固定,无法形成固定组合 1. 多字段固定组合高频查询2. 遵循最左前缀,可覆盖单个 / 多个连续字段的查询3. 排序、分组场景(ORDER BY/GROUP BY)
典型 SQL 示例 CREATE INDEX idx_name ON user(name);SELECT * FROM user WHERE name = '张三'; CREATE INDEX idx_name_age_city ON user(name, age, city);SELECT * FROM user WHERE name = '张三' AND age = 20 AND city = '北京';
核心优势 简单灵活,适配单字段查询,无需考虑字段顺序 多条件查询性能高,空间占用小,写入性能优,可实现覆盖索引
核心劣势 多条件查询性能差,空间浪费,写入性能差 需严格遵循最左前缀,字段顺序设计错误会导致索引失效

1.7 索引的优缺点及使用场景

1.7.1优点

  1. 提高检索效率
  2. 降低排序成本,索引对应的字段有自动排序功能,默认升序

1.7.2 缺点

  1. 创建和维护索引 随数据量增加维护成本增加
  2. 占用额外磁盘空间,数据量越大,占用空间越大
  3. 降低写入性能(增删改):每插入 / 更新 / 删除数据,必须同步更新所有相关索引树,索引越多,写入越慢

1.7.3 索引的使用场景

适合场景:

  • 经常出现在查询条件中的字段,尤其是大表,必须加索引

不适合场景:

  • 更新非常频繁的字段
  • 区分度极低的字段:如性别(男 / 女)、状态(0/1),索引过滤性差,加了反而拖慢性能
  • 几乎不查询的字段:仅存储、不参与查询的字段,完全没必要加索引

二、优化

2.1、优化方法

2.2 通过Explain干预执行计划

1、Explain含义

EXPLAIN 是 MySQL 的执行计划分析工具,可以查看 MySQL 将要如何执行一条 SQL,可以帮助我们发现查询瓶颈,优化查询性能

2、Explain作用

核心作用

  • 看这条 SQL 走没走索引
  • 看是全表扫描还是索引查找
  • 看有没有回表、文件排序、临时表等性能问题
  • 用来优化慢查询

3、Explain用法

例如:

Explain Extended select * from users;

结果:

执行结果关键字段(必懂)

字段 含义(重点)
id 查询执行顺序,越大越先执行
select_type 查询类型(简单查询 / 子查询 / 联合查询)
table 涉及哪张表
type 最重要:访问效率(从好到坏:system > const > eq_ref > ref > range > index > ALL)
possible_keys 可能用到的索引
key 实际用到的索引
key_len 索引使用长度
ref 与索引比较的列
rows 预计扫描行数
Extra 额外重要信息(Using index、Using filesort、Using temporary 等)

最关键的三个判断点

  1. type = ALL→ 全表扫描,性能最差,必须优化

  2. key = NULL→ 索引没生效,白建了

  3. Extra 出现这些要警惕

    • Using index:覆盖索引,很好
    • Using filesort:文件排序,性能差
    • Using temporary:用到临时表,性能差
    • Using index condition:索引下推,正常优化
3.1 id 列

查询中执行 SELECT 子句的顺序编号

  • id 越大,越先执行
  • id 相同,从上到下顺序执行
  • id 为 NULL,最后执行(一般是 UNION RESULT)
3.2 select_type 返回列详解
select_type 含义 出现场景 性能评价
SIMPLE 简单查询 不包含子查询、UNION 的普通 SELECT 最好,最常见
PRIMARY 主查询 包含子查询 / UNION 时,最外层的查询 正常
SUBQUERY 普通子查询 SELECT / WHERE 里的子查询,不依赖外层表 一般,可能被优化掉
DEPENDENT SUBQUERY 相关子查询 子查询依赖外层表的结果,需逐行执行 很差,性能灾难
DERIVED 衍生表 FROM 里的子查询,会生成临时表 一般,数据大就慢
UNCACHEABLE SUBQUERY 不可缓存子查询 子查询结果无法缓存,每行都要重算 极差
UNION UNION 中的第二个及以后查询 UNION 连接的多个查询 正常
UNION RESULT UNION 结果集 UNION 合并结果的临时表 正常
DEPENDENT UNION 相关 UNION UNION 里的查询依赖外层结果 差,和 dependent subquery 一样
MATERIALIZED 物化子查询 MySQL 把子查询结果存成临时表 比 dependent 好很多

1)SIMPLE

最简单的查询,没有子查询、没有 union。

SELECT * FROM user WHERE id = 1;

2)PRIMARY

当 SQL 里有子查询或 UNION 时,最外层查询就是 PRIMARY。

explain
select id from users
union
select id from products;

3)SUBQUERY

WHERE / SELECT 里的子查询,不依赖外层表

explain
select orders.*,(select name from products where id = 1) from orders;

4)DEPENDENT SUBQUERY(重点坑)

子查询用到了外层表的字段,必须循环执行,性能极差:

explain
select orders.*,(select name from products where products.id = orders.user_id) from orders;

外层查一行,子查询就要执行一次,数据多直接卡死。

5)DERIVED

FROM 括号里的子查询,会生成临时表

set session optimizer_switch='derived_merge=off'; #关闭MySQL5.7对衍生表合并优化

explain
select * from (select user_id from orders where id = 1) as temp;

set session optimizer_switch='derived_merge=on'; #还原配置

6)UNION & UNION RESULT

  • UNION:第二个及以后的查询
  • UNION RESULT:合并结果,删除两个查询中的相同列
explain
select id from users
union
select id from products;

7)MATERIALIZED

MySQL 5.6+ 对子查询的优化,把子查询结果物化成临时表,避免重复执行。比 DEPENDENT SUBQUERY 好很多。

3.3 type列

效率从高到低:system > const > eq_ref > ref > fulltext >ref_or_null > range > index > ALL ,一般保证到range,最好到ref

type 级别 含义(白话) 出现场景 性能评价
system 最优 系统表,只有 1 行数据 系统表、极少出现 顶级,几乎不用关心
const 极优 主键 / 唯一索引等值匹配,最多 1 行 where id=1(id 主键) 极快,理想状态
eq_ref 优秀 多表 join,关联字段是主键 / 唯一索引 join 关联主键 非常快,最优 join
ref 良好 普通索引等值匹配,可能多行 where name='xxx'(普通索引) 性能不错,常用
range 一般 索引范围查询 between、>、<、in、like 能接受,需注意优化
index 较差 遍历整个索引树 查索引全部,但不扫全表 比全表好,但依然慢
ALL 最差 全表扫描 无索引、索引失效 必须优化
3.4 key_len 列

MySQL 实际使用了索引的字节长度

MySQL 字段长度计算规则(必须知道)

1)常见字段占用字节

  • tinyint:1
  • int:4
  • bigint:8
  • char(n):n × 字符集字节(utf8mb4=4)
  • varchar(n):n × 字符集字节 + 2 字节长度
  • date:3
  • datetime:8
  • timestamp:4

2)额外开销

  • 允许 NULL:+1 字节
  • 变长字段(varchar):+2 字节

例如:

explain
select * from orders where user_id = 1;

user_id 是int类型,占4个字节,所以key_len=4

3.5 ref 列

当前查询中,与索引列做等值匹配的对象是什么(是常量?还是某个字段?还是函数?)。

常见取值

ref 值 含义 出现场景
const 常量等值匹配 where name = '张三'
字段名(如 db.t.id 另一张表的字段关联匹配 JOIN 关联查询
func 函数 / 表达式匹配 索引失效前兆
NULL 没有等值匹配 范围查询、全表扫描、索引未使用

1) ref = const(最常见、最健康)

explain 
select * from users where name = '张三' and email = 'zhangsan@example.com';

  • name 是索引
  • 常量比较
  • ref = const

2) ref = 表。字段(JOIN 关联)

explain
select users.* from users inner join orders on users.id = orders.id;

  • orders.id 是索引
  • 与外层表 users.id 关联
  • ref = bl_data.users.id

3) ref = NULL

  • 范围查询:where id > 100
  • 模糊查询:where name like '张%'
  • 没走索引、全表扫描这些情况 ref 都是 NULL

4)ref = func(基本代表索引废了)

explain
select users.* from users inner join orders on users.id = trim(orders.id);

  • 对索引列使用了函数
  • MySQL 识别为和函数结果比较
  • ref = func索引大概率失效
3.6 rows 列
  • 含义:MySQL 认为执行这条 SQL 时,需要读取多少行数据才能找到结果。
  • 性质:是估算值,不是精确值,但足以反映性能趋势。
  • 影响rows 越小,查询效率越高;越大,性能越差。
explain
select * from orders where user_id >= 3 and total_price = 25;

两张表 JOIN:rows1 × rows2 就是预估的总循环次数。乘积越大,性能越差。

3.7 filtered 列

表示符合查询条件的数据百分比

  • 数值越大越好
  • 计算公式:最终有效行数=rows×100 / filtered​

举例

  • rows = 1000filtered = 10%引擎扫描 1000 行,最后只留下 100 行✘ 扫描太多无效数据,索引过滤性差,需要优化

  • rows = 100filtered = 90%扫描 100 行,留下 90 行✔ 索引精准,效率很高

3.8 Extra 列

是SQL查询的额外信息

Extra 内容 含义 性能 要不要优化
Using index 覆盖索引,无需回表 最优 ✅ 优秀
Using index condition 索引下推 ICP 良好 ✅ 正常
Using where 存储引擎查到数据,Server 层再过滤 一般 ⚠️ 正常现象
Using filesort 无法用索引排序,文件排序 ❌ 必须优化
Using temporary 创建临时表存放数据 很差 ❌ 必须优化
Using join buffer 没走索引,使用连接缓冲 ❌ 加索引
Impossible where where 条件永远不成立 - 逻辑错误
Select tables optimized away 聚合索引直接命中 极佳 ✅ 完美

1) Using index

覆盖索引查询字段全部在索引里,不用回表查原数据,性能极高。

explain
select user_id,id from orders where user_id = 1;


2) Using index condition

索引下推 ICP把部分过滤条件下推到引擎层,索引层先过滤,减少回表,MySQL5.6 + 默认开启。

explain
select * from orders where user_id > 3;


3) Using where

拿到数据后,在服务层用 where 条件过滤。正常现象,不代表慢

explain
select * from orders where total_price = 100;

explain
select * from orders where user_id = 1 and total_price = 100;


4) ✘ Using filesort(重点)

无法利用索引排序order by 字段没走索引,MySQL 额外磁盘排序,数据量大巨慢。解决:排序字段加入联合索引。

explain
select total_price from orders order by total_price;


5) ✘ Using temporary(重点)

创建临时表常见于:group by、distinct、多表复杂查询、无索引分组。临时表开销大,严重影响性能。

explain
select distinct total_price from orders;


6) Select tables optimized away

使用聚合函数(min,max)访问某个索引值

explain 
select min(id) from users;

explain 
select min(password) from users;

7) Using join buffer

关联查询没用到索引,使用连接缓冲区暴力匹配。解决:给关联字段加索引。

4、索引优化最佳实践

示例表:

CREATE TABLE `employees` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
  `position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
  `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='员工记录表';

INSERT INTO `employees`(`id`, `name`, `age`, `position`, `hire_time`, `remark`) VALUES (2, '张三', 18, 'beijing', '2023-06-11 20:51:35', '测试');
INSERT INTO `employees`(`id`, `name`, `age`, `position`, `hire_time`, `remark`) VALUES (3, '张三', 23, 'shanghai', '2023-06-11 20:51:35', '测试2');
INSERT INTO `employees`(`id`, `name`, `age`, `position`, `hire_time`, `remark`) VALUES (1, '李四', 20, 'shanghai', '2023-06-11 20:51:35', '测试3');
INSERT INTO `employees`(`id`, `name`, `age`, `position`, `hire_time`, `remark`) VALUES (5, '王五', 19, 'beijing', '2023-06-12 14:32:15', NULL);
INSERT INTO `employees`(`id`, `name`, `age`, `position`, `hire_time`, `remark`) VALUES (4, '赵六', 22, 'shenzheng', '2023-06-12 14:33:00', NULL);
INSERT INTO `employees`(`id`, `name`, `age`, `position`, `hire_time`, `remark`) VALUES (6, '赵六', 24, 'beijing', '2023-06-12 14:37:50', NULL);
INSERT INTO `employees`(`id`, `name`, `age`, `position`, `hire_time`, `remark`) VALUES (7, '刘七', 20, 'shanghai', '2023-06-12 14:38:27', NULL);
INSERT INTO `employees`(`id`, `name`, `age`, `position`, `hire_time`, `remark`) VALUES (8, '刘七', 22, 'beijing', '2023-06-12 14:38:41', NULL);
INSERT INTO `employees`(`id`, `name`, `age`, `position`, `hire_time`, `remark`) VALUES (9, '王九', 9, 'shanghai ', '2023-06-12 14:40:17', NULL);
1. 全值匹配原则

查询条件包含联合索引的所有列,才能最大程度利用索引。

 错误示例

EXPLAIN SELECT * FROM employees WHERE name= '张三'; 

  • 示例索引:idx_name_age_position(name, age, position)
  • WHERE name = '张三':key_len=74,仅用了 name
  • WHERE name = '张三' AND age = 18:key_len=78,用了 name+age
  • WHERE name = '张三' AND age = 18 AND position = 'beijing':key_len=140,用了完整索引

 正确示例

EXPLAIN SELECT * FROM employees WHERE name= '张三' AND age = 18 AND position ='beijing'; 


2. 最左前缀法则

查询必须从索引的最左列开始,并且不能跳过中间列。口诀:“带头大哥不能死,中间兄弟不能断”

-- name 74   +age 78 + position 140
EXPLAIN SELECT * FROM employees WHERE name= '张三' and age = 18 AND position = 'beijing' ; 
-- 带头大哥不能死
EXPLAIN SELECT * FROM employees WHERE age = 18 AND position = 'beijing';
-- 中间兄弟不能断
EXPLAIN SELECT * FROM employees WHERE name= '张三' AND position = 'beijing'; 
  • ✅ 正确:name = '张三' AND age = 18 AND position = 'beijing'(连续有序前缀)
  • ❌ 错误:age = 18 AND position = 'beijing'(跳过了 name,索引失效)
  • ❌ 错误:name = '张三' AND position = 'beijing'(跳过了 ageposition 无法利用索引)

3. 不在索引列上做任何操作

对索引列使用计算、函数、类型转换(自动 / 手动),会导致索引失效,转为全表扫描。

索引是按原始字段值排序的 B+ 树,函数 / 计算破坏了 “有序性”

示例

-- 原始值:name = '张三',索引里是按 '张三' 排序的
SELECT * FROM employees WHERE name = '张三'; -- 走索引

-- 函数操作:left(name, 3) = '张三',原始值被修改了
SELECT * FROM employees WHERE left(name, 3) = '张三'; -- 索引失效

自动类型转换也是同样的道理

-- name 是 varchar 类型,索引按字符串排序
SELECT * FROM employees WHERE name = 123;

这里发生了隐式类型转换

  • MySQL 会把索引列 name 的每一行,都转成数字,再和 123 比较
  • 索引树里存的是字符串,不是数字
  • 所以还是得全表扫描,逐行转换后再过滤

索引是按原始值排序的,任何改变原始值的操作(函数、计算、类型转换),都会破坏索引的有序性,导致 MySQL 无法直接定位,只能全表扫描。


4. 范围条件会截断索引

范围查询会使后面字段无序,造成部分索引失效

-- 索引完全利用:key_len=140
EXPLAIN SELECT * FROM employees WHERE name = '张三' AND age = 18 AND position = 'beijing';

-- 范围条件 age > 18 截断了索引,position 无法利用:key_len=78
EXPLAIN SELECT * FROM employees WHERE name = '张三' AND age > 18 AND position = 'beijing';

5. 尽量使用覆盖索引(只访问索引的查询),减少 SELECT * 语句

规则说明:查询字段全部包含在索引中,避免回表查询,性能更高。口诀:覆盖索引不写星

示例:

-- 使用覆盖索引:Extra 显示 Using index,key_len=140
EXPLAIN SELECT name,age FROM employees WHERE name = '张三' AND age = 18 AND position = 'beijing';

-- SELECT * 会回表查询,key_len=140,但无 Using index
EXPLAIN SELECT * FROM employees WHERE name = 'Lilei' AND age = 23 AND position = 'manager';


6. 不等空值还有 OR,索引失效要少用
6.1 不等于 / 范围条件导致索引失效
  • 使用 !=><>=<= 时,MySQL 优化器会根据检索比例、表大小等因素,评估是否使用索引,大概率失效。
  • 使用 NOT INNOT EXISTS 时,通常无法使用索引,会导致全表扫描。

示例:

-- name != '张三' 导致 type=ALL,全表扫描
EXPLAIN SELECT * FROM employees WHERE name != '张三';

6.2 IS NULL / IS NOT NULL 通常无法使用索引

示例:

-- Extra 显示 Impossible where,索引未使用
EXPLAIN SELECT * FROM employees WHERE name is null;

6.3 OR 查询不一定使用索引

使用 OR 时,MySQL 不一定使用索引,优化器会根据检索比例、表大小评估,建议用 UNION 替代。

示例:

-- name = 'Lilei' OR name = 'HanMeimei' 导致 type=ALL,全表扫描
EXPLAIN SELECT * FROM employees WHERE name = 'Lilei' OR name = 'HanMeimei';

7. Like 百分号写最右

规则:Like 查询中,百分号 % 放在最右侧,索引才能生效。口诀:Like 百分写最右

示例说明

百分号在左:索引失效

-- 全表扫描,type=ALL,key=NULL
EXPLAIN SELECT * FROM employees WHERE name LIKE '%三';

百分号在右:索引生效

-- 索引生效,type=range,key=idx_name_age_position
EXPLAIN SELECT * FROM employees WHERE name LIKE '张%';

优化方案:解决 like '%xxx%' 索引失效问题

  • 方案 a:使用覆盖索引查询字段必须是建立覆盖索引的字段,这样即使 % 在左,也能走索引。

-- 覆盖索引生效,Extra 显示 Using where; Using index
EXPLAIN SELECT name,age,position FROM employees WHERE name LIKE '%Lei%';

  • 方案 b:无法使用覆盖索引时,可借助搜索引擎

8. 字符串不加单引号,索引失效

规则:字符串类型字段不加单引号,会导致隐式类型转换,索引失效。口诀:VAR 引号不可丢

示例说明

-- 正确写法:加单引号,索引生效
EXPLAIN SELECT * FROM employees WHERE name = '1000';

-- 错误写法:不加单引号,隐式类型转换,索引失效(type=ALL)
EXPLAIN SELECT * FROM employees WHERE name = 1000;


9. 范围查询优化

规则:MySQL 优化器会根据检索比例、表大小等因素,评估是否使用索引。大范围查询可能导致优化器最终选择不走索引。

示例说明

①添加索引

ALTER TABLE `employees` ADD INDEX `idx_age` (`age`) USING BTREE;

②大范围查询:索引失效

-- 大范围查询(age >=1 AND age <=2000),优化器认为走索引成本更高,选择全表扫描
EXPLAIN SELECT * FROM employees WHERE age >=1 AND age <=2000;

③优化方法:将大范围拆分成多个小范围

-- 拆分后,小范围查询可以正常使用索引
EXPLAIN SELECT * FROM employees WHERE age >=1 AND age <=1000;
EXPLAIN SELECT * FROM employees WHERE age >=1001 AND age <=2000;

④还原初始索引状态

ALTER TABLE `employees` DROP INDEX `idx_age`;

索引使用总结(口诀版)

全值匹配我最爱,最左前缀要遵守;带头大哥不能死,中间兄弟不能断;索引列上少计算,范围之后全失效;Like 百分写最右,覆盖索引不写星;不等空值还有 or,索引失效要少用;VAR 引号不可丢,SQL 高级也不难!


未完待续...

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐