目录
- 验证索引执行效率
- 联合索引|最左前缀法则
- 全部索引失效常见原因
- or、null、数据分布引发的索引失效
- SQL 索引提示(use /ignore/force index)
- 覆盖索引、回表原理
- 前缀索引使用教程
- 单列索引 vs 联合索引对比
- 索引创建全套设计原则
一、验证索引效率
无索引状态
sql
sql
select *from tb_sku where sn = '1000000003124'
没有索引会进行全表扫描,查询速度很慢。
创建 B + 树单列索引
sql
sql
create index idx_sku_sn on tb_sku(sn);
建立索引之后查询会走 B + 树索引检索,大幅提速。
MySQL 普通索引底层结构为 B+‑Tree
二、联合索引核心:最左前缀法则
多字段联合索引,查询必须匹配索引最左侧前列,不能跳过索引字段;跳过后面字段,跳过位置之后索引失效
示例联合索引顺序:(profession,age,status)
- 正常命中全部索引
sql
sql
explain select *from tb_user where profession = '软件工程' and age = 31 and status = '0';
- 命中前两列索引
sql
sql
explain select *from tb_user where profession = '软件工程' and age = 31;
- 跳过最左首字段 → 索引直接失效,type=ALL 全表扫描
sql
sql
explain select *from tb_user where age = 31 and status = '0';
- 跳过中间 age 字段 → age 之后 status 索引失效,仅 profession 生效
sql
sql
explain select *from tb_user where profession = '软件工程' and status = '0';
- where 条件字段顺序不影响,MySQL 优化器会自动调整顺序,满足最左前缀即可生效
sql
sql
explain select *from tb_user where age = 31 and status = '0' and profession = '软件工程'
范围查询陷阱
联合索引遇到
< >范围条件,范围右侧所有字段索引直接失效
sql
sql
-- age>30 是范围查询,status索引失效
select *from tb_user where profession= '软件工程' and age > 30 and status = '0'
优化方案: 优先使用 >= <= 等值边界代替大于小于范围,尽量把范围字段放在联合索引最后一位。
三、高频索引失效场景
1. 在索引列上做运算、函数、隐式类型转换
sql
sql
-- phone为字符串索引字段,不加单引号发生隐式转换,索引失效
explain select *from tb_user where phone = 17799999015;
索引字段必须保持原生类型,不要运算、不要调用函数、不要类型转换
2. like 模糊查询
like 'xxx%':尾部通配,索引生效like '%xxx'、like '%xxx%':头部带通配符,索引失效全表扫描
3. or 连接条件,一侧无索引则整条 SQL 索引失效
sql
sql
-- id带索引、age没有索引,or导致索引失效
explain select *from tb_user where id = 10 or age = 23;
解决办法:给 or 后面 age 字段新建索引。
4. 字段 is null /is not null
sql
sql
-- 有概率命中索引
select *from tb_user where profession is null
-- 大概率索引失效,全表扫描 type=all
select *from tb_user where profession is not null
5. MySQL 优化器判断全表扫描更快,主动放弃索引
当查询条件匹配表中绝大多数的数据,索引查找成本高于全表扫描,MySQL 自动放弃索引。
sql
sql
-- 绝大多数phone满足该条件,触发全表扫描
explain select *from tb_user where phone >= '1973892485'
四、SQL 提示:手动控制 MySQL 选用索引
适合一张表存在多条索引,MySQL 优化器选错索引时手动干预
sql
sql
-- 创建单列索引
create index idx_user_pro on tb_user(profession);
-- 1.use index:建议MySQL使用指定索引
select *from tb_user use index(idx_user_pro) where profession = '软件工程';
-- 2.ignore index:忽略指定索引
select *from tb_user ignore index(idx_user_pro) where profession = '软件工程';
-- 3.force index:强制使用该索引
select *from tb_user force index(idx_user_pro) where profession = '软件工程';
五、覆盖索引 & 回表查询
基础原理
- 聚集索引(主键索引):B + 树叶子节点存放该行全部字段数据
- 辅助索引(普通索引):叶子节点只存放主键 id
回表:通过普通索引拿到主键 id 之后,再去主键索引读取整行数据,多出一次 IO 查询。
覆盖索引定义
查询所需要的全部字段,都存在于联合索引当中,不需要回表读取主键索引 执行计划 Extra 字段出现 Using index = 命中覆盖索引,性能最高 Using index condition = 使用索引检索,但是需要回表查询数据
案例
联合索引 (profession,age,status)
sql
sql
-- select * 需要回表
explain select *from tb_user where profession = '软件工程' and age = 31 and status = '0'
-- 查询字段全部包含在索引内,触发覆盖索引,Using‑index
explain select id, profession from tb_user where profession = '软件工程' and age = 31 and status = '0'
避坑准则:禁止习惯性写
select *,极易触发回表,拖慢查询速度
SQL 优化例题
现有字段 id, username, password, status
sql
sql
select id, username, password from tb_user where username = 'itcast';
优化方案:建立联合索引 idx_username_up(username,password),触发覆盖索引,免去回表。
六、前缀索引
适用场景
varchar、text 等超长字符串字段,完整建立索引占用磁盘 IO 大、索引体积臃肿。 只存储字段前面一部分字符当作索引,节省索引存储空间。
创建语法
sql
sql
-- 取该字段前n位作为索引
create index idx_xxxx on table_name(column(n));
索引选择性(核心参数)
选择性 = 不重复数值 ÷ 表总记录数;数值越接近 1,索引效果越好,唯一索引选择性 = 1
sql
sql
-- 完整字段选择性
select count(distinct email) / count(*) from tb_user;
-- 截取前10位前缀选择性
select count(distinct substring(email, 1, 10))/count(*) from tb_user;
调试不同截取长度,在存储空间与选择性之间取平衡点。
七、单列索引 vs 联合索引
单列索引
一个索引只包含单个字段
sql
sql
-- phone单列索引,查询phone+name条件,命中phone索引之后必须回表查找name
select *from tb_user where phone = '1779999991110' and name = '韩信'
联合索引
sql
sql
create index idx_user_phone_name on tb_user(phone, name)
查询条件 phone + name,索引已经携带两个字段,能够触发覆盖索引、跳过回表查询。
开发规范:优先建立合适顺序的联合索引,减少大量单列索引;遵循最左前缀规划索引字段顺序,等值字段放前面、范围查询字段放在索引末尾。
八、完整索引创建设计原则
- 数据表数据量大的时候才建立索引,小表没必要;
- 经常用于 where 查询、join 连接、order by、group by 的字段建立索引;
- 区分度高的字段建立索引,优先创建唯一索引;
- 长字符串 varchar/text 字段,采用前缀索引节约磁盘;
- 优先设计联合索引,利用覆盖索引避免回表 IO;
- 一张表索引数量需要管控,索引过多会拖慢 insert/update/delete 写入速度;
- 索引字段设置
NOT NULL,NULL 会造成索引失效、查询优化困难。