MySql调优详解
参考:密码: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树的节点可以有多个子节点,多叉树;
一个节点可以存储多个元素;
平衡多路查找树;
不适合范围查找
1.3.4 B+树
对B-树的优化(为什么MySql用B+树):
- 所有的数据存储在叶子结点上(最下面一行),排序、分组、去重查询更简单
- 非叶子结点没有放数据,可以存更多的键值,树会更矮更胖,查询效率更快
- 每一个非叶子节点上都有对应的双向指针,范围查询、排序友好
如果一个表没有主键索引还会创建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. 无索引下推
- 引擎按
name LIKE '张%'找到 100 条索引记录(含主键)。 - 全部 100 次回表,读取整行数据。
- 返回 Server 层,再过滤
age = 20,只剩 10 条。
- 浪费 90 次回表 I/O。
2. 开启索引下推
- 引擎按
name LIKE '张%'遍历索引。 - 在索引叶子节点直接判断
age = 20。 - 只把符合条件的 10 条主键传给 Server。
- 仅 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.7.2 缺点
- 创建和维护索引 随数据量增加维护成本增加
- 占用额外磁盘空间,数据量越大,占用空间越大
- 降低写入性能(增删改):每插入 / 更新 / 删除数据,必须同步更新所有相关索引树,索引越多,写入越慢
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 等) |
最关键的三个判断点
-
type = ALL→ 全表扫描,性能最差,必须优化
-
key = NULL→ 索引没生效,白建了
-
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 = 1000,filtered = 10%引擎扫描 1000 行,最后只留下 100 行✘ 扫描太多无效数据,索引过滤性差,需要优化 -
rows = 100,filtered = 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,仅用了nameWHERE name = '张三' AND age = 18:key_len=78,用了name+ageWHERE 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'(跳过了age,position无法利用索引)
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 IN、NOT 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 高级也不难!
未完待续...
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)