MySQL 索引全攻略:小白入门到面试发光

🧠《MySQL 索引从入门到精通》系列目录

📌 第一章:什么是索引?——你查得快,全靠它!

  • 生活中的类比:图书馆、快递仓库、外卖订单系统
  • 为什么没索引会很慢?
  • MySQL 是怎么“查找”的?

📌 第二章:索引长啥样?B+树来一棵!

  • 什么是 B+树?为啥不是 B 树或哈希?
  • 图解索引结构
  • 聚簇索引 VS 非聚簇索引(InnoDB 的套路)

📌 第三章:索引都有啥类型?认识你能建的这几种

  • 普通索引 / 唯一索引 / 主键索引 / 全文索引
  • 单列 VS 联合索引
  • 建立索引的语法大全 + 手把手示例

📌 第四章:索引怎么失效?(这才是踩坑大头)

  • 万恶的 like '%xx'
  • 函数包裹字段、隐式转换
  • 排序 / or 条件 / != 运算符
  • 索引失效的实战案例演示

📌 第五章:MySQL explain 用法详解(让它告诉你慢在哪)

  • 每一列参数讲解(type、key、rows、extra等)
  • explain + SQL 案例对照看懂执行路径
  • 如何判断某条语句有没有用上索引?

📌 第六章:联合索引的正确姿势

  • 什么是“最左前缀原则”?
  • 为什么顺序错了,索引就废了?
  • 索引覆盖、回表、下推

📌 第七章:建索引前要三思!什么时候别建?

  • 误用索引的真实代价(空间 + 写入速度)
  • 什么字段适合建?什么字段别碰?
  • 选择度是个啥?建索引先看它

📌 第八章:实战!从慢 SQL 到优化飞起

  • 几条常见慢 SQL 优化案例(含 explain)
  • 如何快速判断:要不要加索引?
  • explain + 索引策略调整 + 对比图表

📌 第九章:高频面试题讲透(附标准答案)

  • 联合索引 + 最左匹配 + 覆盖索引
  • 为什么索引失效?
  • explain 里 type = ALL 是不是就很糟糕?

📦 附录/Bonus 内容:

  • 创建测试表脚本(含10万条数据)
  • 常用 explain 模板对照表
  • MySQL 索引优化 checklist(上线前必查清单)

第一章:什么是索引?——你查得快,全靠它!

💡你以为你在查表?其实你查的是“表的导航”!


🥚小白困惑时间

你是不是也有这样的经历👇:

  • 表建好了,字段填满了,就是查得贼慢?
  • 明明一张只有十几万行的表,WHERE id = 123456 都能查半天?
  • DBA 一句话:“你这没加索引。”然后你就装作听懂的样子点点头。

好,今天我们把这事儿说清楚:索引到底是啥?为啥有它就快了?


📚我们来类比一下

假设你有 10 万本书,堆在一起。你要找一本叫《MySQL八股文》的,怎么办?

  • ❌ 方法一:一本一本翻,翻到天荒地老。
  • ✅ 方法二:先查目录,目录告诉你它在第 37893 本。直接拿就完了!

MySQL 也是一样。“目录” 就是 “索引”

有了它,MySQL 不用一个个数据页去翻,而是:

  1. 通过索引快速找到数据位置;
  2. 一把锁定,拿走你要的那一条记录。

🧠MySQL 索引到底是个啥?

通俗点说:

索引就是数据库帮你提前做好的“查找表”,你查的时候,它用这张表来“定位”到目标数据。

它的底层实现是一棵多路平衡搜索树(B+ 树),像这样👇:

       [30 | 70]
       /     \
  [10 20]   [40 50 60] [80 90]

我们不急着讲 B+ 树的原理(下章再整),这里只要记住:它查找特别快,通常是 O(log n) 的效率。


🔍实操小实验:有索引 VS 没索引

来,我们造一张大表。

CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50),
  email VARCHAR(100),
  INDEX idx_email (email)  -- 给 email 建个索引
);

插入数据:

INSERT INTO users (username, email)
SELECT 
  CONCAT('user', LPAD(id, 5, '0')),
  CONCAT('user', LPAD(id, 5, '0'), '@test.com')
FROM (
  SELECT @row := @row + 1 AS id FROM 
  (SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t1,
  (SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t2,
  (SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t3,
  (SELECT @row := 0) t0
) a LIMIT 50000;

执行查询:

-- 用索引查
EXPLAIN SELECT * FROM users WHERE email = 'user12345@test.com';

-- 去掉索引,再查
ALTER TABLE users DROP INDEX idx_email;
EXPLAIN SELECT * FROM users WHERE email = 'user12345@test.com';

看执行计划的 typerows 字段,秒懂“有索引”和“没索引”的差距。


🔧你查得快,是因为有人提前把路铺好了

你以为你写了一条 SQL,数据库就硬刚 10 万行去扫?并不是。

有索引的时候:

  • 你是在走“高速公路”
  • 没索引的时候:
  • 你是在“摸黑爬山路”

🧾本章小结(划重点时间)

概念内容解释
索引(Index)一种加速数据查询的结构,类似于图书馆目录
作用降低查找时间复杂度,从 O(n) 降为 O(log n)
结构MySQL 默认使用 B+ 树 实现索引(尤其是 InnoDB 引擎)
注意索引不是越多越好!会占空间,也会影响写入性能

🎁彩蛋:你能靠这张图,向面试官讲清楚“索引是干嘛的”👇

- 有索引:EXPLAIN 结果 type = ref/index,rows < 1000
- 没索引:type = ALL,rows = 全表行数
- 对比图表:索引平均查询 < 1ms,全表扫描可能要 100ms+

第二章:索引长啥样?🌳B+树来一棵!

“不是所有的树都能爬,但数据库偏偏就爱爬B+树。”


🎯 一、MySQL 索引背后的秘密武器:B+ 树

当你写下一句:

SELECT * FROM users WHERE id = 123;

你以为 MySQL 是“从头翻到尾”找到 123 吗?错!

实际上,它打开了 InnoDB 的“小黄页”(也就是 B+树索引),顺着枝干走几步,直接定位到了那条记录。效率不是翻倍,是“乘以十倍”起步!


🌲 二、啥是 B+ 树?它和 B 树、二叉树有啥区别?

树类型特点用不用来做索引?
二叉搜索树(BST)一边大一边小,容易变斜❌ 太不稳定
B 树多路搜索树,数据存在每个节点🟡 一般般
B+树数据都在叶子节点,叶子之间还有链表✅ 索引首选,查找 + 范围都高效!

一图胜千言:

   [17, 35]
    /   |   \
[3, 9] [17, 25] [35, 50]

所有的数据(比如 3、9、17、25、35、50)都在叶子节点,内部节点只是导航用。
你就像个快递员,从门牌号导航直接走到你要送的门,效率嗖嗖滴!


🧠 三、InnoDB 的“聚簇索引” VS “二级索引”

InnoDB 存储引擎(你99%都在用)默认使用的是聚簇索引(Clustered Index),这和你想象的有点不一样:

  • 主键索引(聚簇索引):数据和索引放一起。
  • 辅助索引(Secondary Index):只存索引值 + 主键指针,真正数据还得回表查。

🍜 四、来点实际的 SQL 示例:一棵小小 B+树是怎样长出来的?

👷 创建一个简单表:
CREATE TABLE users (
  id INT PRIMARY KEY,
  username VARCHAR(50),
  email VARCHAR(100),
  age INT
) ENGINE=InnoDB;

InnoDB 会自动以 id聚簇索引,数据直接挂在 B+树叶子上。


🍱 再来加点料:创建辅助索引
CREATE INDEX idx_username ON users(username);

这是个辅助索引(非聚簇索引),它记录的是 username + id,真要查别的字段(比如 email),还得“回表”找。


🚦 五、B+树为什么这么牛?一棵树能有多高?

MySQL 的 B+树每个节点通常能存 几百条指针

就算你有 2000 万条记录,B+树也只需“走三层”就能命中目标。就像电梯直达 28 楼,不用一层层走楼梯。


🧪 六、实战:插入、查询、观察索引效果

-- 插入测试数据
INSERT INTO users (id, username, email, age)
SELECT id, CONCAT('user', id), CONCAT('user', id, '@test.com'), FLOOR(20 + RAND()*30)
FROM (
  SELECT @row := @row + 1 AS id FROM 
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t1,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t2,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t3,
  (SELECT @row := 0) r
) a
LIMIT 10000;
-- 用辅助索引查 username
EXPLAIN SELECT * FROM users WHERE username = 'user999';

你应该会看到:

key: idx_username
type: ref
rows: 1

说明它用上了刚才那个 idx_username 辅助索引!


🧘 七、小结 + 彩蛋冷知识

  • 索引是一棵 B+ 树,主键索引是数据本身的家。
  • 辅助索引只是“路标”,真正数据还在主键索引里。
  • 索引多≠一定好,建错了、用错了都是浪费空间+性能灾难。

💡彩蛋:InnoDB 的主键索引是强绑定的,没有主键它会自动找一个唯一键,没有唯一键它会悄悄加一个隐藏自增ID列来做主键!


第三章:索引都有哪些类型?认识你能建的这几种

“给我来一打索引!”
—— 一位刚学完索引的新手程序员,挥着建表语句喊道。

MySQL 提供了多种索引,就像你点外卖时能选黄焖鸡、炸鸡排、鸡公煲……
不同场景,不同口味,别乱点!


🥩 1. 普通索引(普通但不简单)

最常见的索引类型,支持快速查询,但不强制唯一。

语法:

CREATE INDEX idx_username ON users(username);

你可以在 username 上建个普通索引,用于模糊查人、分页等操作。


🧲 2. 唯一索引(想插重复的?没门)

强制不能有重复值,MySQL 会帮你“看大门”。

语法:

CREATE UNIQUE INDEX uniq_email ON users(email);

也可以直接在建表时声明:

CREATE TABLE users (
  id INT PRIMARY KEY,
  email VARCHAR(255) UNIQUE
);

💡适用于手机号、身份证号、邮箱这种天然唯一的字段。


👑 3. 主键索引(建表送一棵)

每个表只能有一个主键索引,默认也是唯一索引。InnoDB 引擎里,它就是聚簇索引(后面讲)。

建表时声明:

CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(100)
);

MySQL 会自动在 id 上建主键索引,你不说它也会自己安排。


🔍 4. 组合索引(加个群,查得快)

又叫联合索引,是多个字段合并成的一棵索引树。适合用于多个条件联合查询。

语法:

CREATE INDEX idx_user_info ON users(username, email);

查询时只要匹配 usernameusername + email,这个索引就生效。

⚠️ 注意最左前缀法则,顺序不能乱!后面专章讲。


🧬 5. 全文索引(支持模糊匹配的利器)

用于文本搜索(类似搜索引擎)。支持 MATCH ... AGAINST 语法,但别搞错,它不能支持 like ‘%xxx%’

语法:

CREATE TABLE articles (
  id INT PRIMARY KEY,
  title TEXT,
  FULLTEXT(title)
);

然后这样查:

SELECT * FROM articles 
WHERE MATCH(title) AGAINST('MySQL 索引');

🌟 MyISAM 默认支持全文索引,InnoDB 需 MySQL 5.6+ 才支持。


⚙️ 6. 空间索引(GIS场景专用)

仅用于存储空间数据(如经纬度坐标),用得不多,小白可以先跳过。


🎬 实战示例:一口气建几个索引!

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50),
  email VARCHAR(100),
  phone VARCHAR(20),
  bio TEXT,
  created_at DATETIME,
  
  UNIQUE KEY uniq_email (email),
  INDEX idx_username (username),
  INDEX idx_user_phone (username, phone),
  FULLTEXT KEY ft_bio (bio)
);

🔍 查数据就能利用这些索引加速:

-- 命中 username 索引
SELECT * FROM users WHERE username = 'coderxiaobai';

-- 命中联合索引 username + phone(注意字段顺序)
SELECT * FROM users WHERE username = 'coderxiaobai' AND phone = '13812345678';

-- 命中全文索引 bio
SELECT * FROM users WHERE MATCH(bio) AGAINST('DBA');

🧠 总结一句话:

类型是否唯一适用场景
普通索引常规字段加速查询
唯一索引邮箱、手机号、身份证
主键索引每张表只能有一个
联合索引依赖顺序多条件组合查询
全文索引文本搜索
空间索引地图/GIS应用

别一口气加太多索引,否则你的写入就像被加上了 10 个拖拉机轮子,动弹不得。


📌 第四章:索引怎么就突然“失效”了?!(含实战 SQL)

“你以为你用了索引,结果 MySQL 偷懒全表扫了!”


💥 索引失效是怎么回事?

MySQL 建了索引,不代表你一定能用上。

就像你花重金装修了厨房,但天天点外卖。MySQL 也可能选择 不用索引,转而“全表扫描”,一顿瞎忙。

这章我们来拆解:到底是什么操作,导致索引无效?


❌ 1. LIKE '%abc' —— 通配符开头,索引全废!

-- 假设 name 上有索引
SELECT * FROM users WHERE name LIKE '%张三';

解释:
MySQL 无法用索引做定位,因为 % 开头,它无法从 B+ 树根节点快速定位,只能老老实实扫全表。

✅ 推荐方式:

-- 用右匹配可以使用索引
SELECT * FROM users WHERE name LIKE '张三%';

❌ 2. 对索引字段使用函数 or 运算

-- 索引字段被函数包裹了
SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';

这条语句没法用到 create_time 上的索引,因为 DATE(create_time) 改变了字段原始值,MySQL 无法用 B+ 树对比了。

✅ 推荐做法:

-- 改成范围查询,让索引派上用场
SELECT * FROM orders 
WHERE create_time >= '2024-01-01 00:00:00' 
  AND create_time < '2024-01-02 00:00:00';

❌ 3. 隐式类型转换,坑你没商量

-- user_id 是 INT 类型,但你写了字符串
SELECT * FROM users WHERE user_id = '123';

虽然很多时候 MySQL 能“猜”出来,但可能造成索引失效(特别是在一些字符集或非默认配置中)。

✅ 推荐写法:

SELECT * FROM users WHERE user_id = 123;

❌ 4. 使用 or 且字段未同时建立索引

SELECT * FROM products 
WHERE category_id = 1 OR name = '玩具';

只有 category_id 建了索引,而 name 没有,MySQL 就干脆全表扫。

✅ 推荐做法:

  • 给两个字段都建索引,或拆成 union 查询。
SELECT * FROM products WHERE category_id = 1
UNION
SELECT * FROM products WHERE name = '玩具';

❌ 5. 使用 !=<>NOT INNOT LIKE

SELECT * FROM employees WHERE salary != 5000;

这些操作让 MySQL 无法使用范围查找,因此大概率放弃索引。

✅ 推荐方式:
能换成 INBETWEEN 就换。


🧪【实战:索引失效案例对比】

准备数据:

CREATE TABLE test_users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(100),
  age INT,
  INDEX idx_name (name)
);

-- 插入一些数据
INSERT INTO test_users (name, age)
SELECT CONCAT('name', FLOOR(RAND()*1000)), FLOOR(RAND()*100)
FROM dual
CONNECT BY LEVEL <= 10000;

对比 explain:

-- 使用了索引
EXPLAIN SELECT * FROM test_users WHERE name LIKE 'name1%';

-- 索引失效,全表扫
EXPLAIN SELECT * FROM test_users WHERE name LIKE '%1';

type 字段:

  • 使用索引一般是 rangeref
  • 没用就是 ALL

📌 小结:常见索引失效行为列表

错误操作原因说明
LIKE '%abc'左通配符不能走索引
函数包裹字段 DATE(col)改变字段值
不匹配类型 WHERE id = '1'隐式转换
OR 条件未全部建索引查询合并不走部分索引
!= / NOT IN / NOT LIKE无法使用 B+ 树范围

📌 第五章:MySQL Explain 用法详解

——“让它告诉你:你的 SQL 慢在哪”


你是不是也经历过这种场景:

“SQL 跑得慢?我加了索引啊,怎么还是不行?”

别急!你得听听 MySQL 自己怎么说——这时候 EXPLAIN 就派上用场了。


🧪 什么是 EXPLAIN?

EXPLAIN 可以让你看到 MySQL 执行 SQL 时的“执行计划”,就像医生的体检报告,告诉你有没有用上索引,是不是全表扫描,有没有回表等等。

语法也很简单:

EXPLAIN SELECT * FROM users WHERE name = 'Alice';

或者新版语法更酷一点:

EXPLAIN FORMAT=JSON SELECT * FROM users WHERE name = 'Alice';

🔍 EXPLAIN 输出字段逐个看

字段名作用解释
id查询中 SELECT 的序号,越大越先执行
select_type简单查询 or 子查询 or union 等
table当前正在访问的表名
type连接类型,越靠前越好(比如 const > ref > ALL
possible_keys可能使用的索引
key实际使用的索引
key_len使用索引字段的长度
ref哪个列或常量与 key 做比较
rows预估扫描行数
Extra额外信息,比如 Using where, Using index, Using temporary

🧪 示例 SQL + EXPLAIN 结果解析

建个小表:

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    email VARCHAR(100),
    age INT,
    INDEX idx_name (name),
    INDEX idx_age (age)
);

插点假数据:

INSERT INTO users (name, email, age)
SELECT 
  CONCAT('User', FLOOR(RAND() * 10000)),
  CONCAT('user', FLOOR(RAND() * 10000), '@test.com'),
  FLOOR(RAND() * 80)
FROM information_schema.tables LIMIT 10000;

示例1:使用了索引的查询

EXPLAIN SELECT * FROM users WHERE name = 'User123';

返回:

idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEusersrefidx_nameidx_name1Using index condition

type=ref,表示使用了 name 索引,性能不错。


示例2:索引失效的写法

EXPLAIN SELECT * FROM users WHERE LEFT(name, 4) = 'User';
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEusersALLNULLNULL10000Using where

⚠️ type=ALL + key=NULL 表示全表扫描,索引完全没用上。

原因是用了 LEFT(name, 4),MySQL 没法用 name 的索引,索引字段不能被函数包住!


示例3:多条件查询匹配联合索引

ALTER TABLE users ADD INDEX idx_name_age (name, age);

EXPLAIN SELECT * FROM users WHERE name = 'User123' AND age = 25;
idselect_typetabletypekeykey_lenrowsExtra
1SIMPLEusersrefidx_name_age???1Using index condition

idx_name_age 起飞了,联合索引最左匹配原则满足!


🧠 常见 type 类型(性能由好到差)

  • const:常量,最优,比如 id=1
  • eq_ref:唯一索引且只返回一条(比如主键)
  • ref:普通索引匹配多条
  • range:范围扫描(比如 BETWEEN<
  • index:扫描整个索引
  • ALL:最差,全表扫描

🧾 总结 Checklist

✅ 看 type,是否为 ALL(全表扫描)
key 是否为空?表示有没有用上索引
rows 是否太大?太大可能是慢的原因
Extra 里有没有 Using temporaryUsing filesort,这些会拖慢查询速度


📘 第七章:建索引前要三思!什么时候别建?

🧠 开篇一问:索引不是越多越好吗?

很多新手误以为:“索引能提高查询速度,那我每个字段都建索引就好了啊!”
——大错特错

就像你给所有衣服都贴上标签,查找是快了,但你要花的时间贴标签、维护标签、搬衣服也变多了。而数据库的“搬衣服”过程,就是写入性能和空间开销的增加


🚫 什么时候不要建索引?

❶ 经常更新的字段(高频 UPDATE/DELETE)

举个栗子🌰:

CREATE TABLE user_status (
    id INT PRIMARY KEY,
    user_id INT,
    last_active_time DATETIME,
    login_count INT
);

如果你对 last_active_timelogin_count 频繁更新,而且它们也不是主要的查询条件,就不要建索引,不然每次更新都要维护索引页,得不偿失。


❷ 低基数字段(例如性别、布尔类型)
SELECT * FROM users WHERE gender = 'male';

gender 字段只有两种值(男/女),不论怎么建索引,MySQL 扫的记录还是占大半,优化不了啥

一般建议:字段不同值太少(比如不超过10种),大概率不值得建索引。


❸ 很少参与 WHERE 条件的字段

如果你几乎从不拿某个字段当查询条件、也不用来排序、分组,那建索引干嘛呢?

比如:

CREATE TABLE orders (
    id INT PRIMARY KEY,
    product_id INT,
    order_notes TEXT
);

order_notes 是备注字段,不查它,不用它排序,也不 GROUP BY 它,建它干嘛?省点磁盘空间不好吗?


📏 小技巧:评估一个字段值的“选择度”

选择度(selectivity)= 不同值的个数 / 总记录数

  • 值越多 → 选择度高 → 越适合建索引
  • 值越少 → 选择度低 → 建了也白建

用 SQL 简单评估一个字段选择度:

SELECT COUNT(DISTINCT gender) / COUNT(*) AS selectivity FROM users;

建议:选择度 < 0.1 的字段,一般不建索引(除非用于联合索引)


🛠 示例实操:建索引的前后对比

创建测试表
CREATE TABLE employees (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    department_id INT,
    gender CHAR(1),
    salary INT
);

-- 插入模拟数据
INSERT INTO employees (name, department_id, gender, salary)
SELECT
    CONCAT('User_', id),
    FLOOR(1 + RAND() * 10),  -- department_id 取值范围:1-10
    IF(RAND() > 0.5, 'M', 'F'),
    FLOOR(3000 + RAND() * 7000)
FROM (SELECT @id := @id + 1 AS id FROM information_schema.columns LIMIT 100000) a;
查询效率对比:gender 字段建不建索引
-- 不建索引执行查询
SELECT * FROM employees WHERE gender = 'M';

-- 建索引再查
CREATE INDEX idx_gender ON employees(gender);
SELECT * FROM employees WHERE gender = 'M';

然后用 EXPLAIN 对比两者的 rowskey 字段差异。


🎯 总结一下本章核心:

条件是否适合建索引
经常更新的字段❌ 不推荐
低基数字段(性别、状态)❌ 通常不推荐
不参与查询条件的字段❌ 没必要
选择度高、经常查询✅ 强烈推荐
用于排序、分组、JOIN 的字段✅ 建起来!

📘 第八章:实战!从慢 SQL 到优化飞起

🔍 一句慢 SQL 是怎么炼成的?

一个查询慢,不外乎:

  • 表太大
  • 没有用上索引
  • 用了低效的 SQL 写法
  • 索引失效
  • 回表太多
  • 查询方式不合理(比如用 %like%

下面我们一条条来实战讲解 + 优化示例。


🎯 场景一:没用上索引导致全表扫描

👎 慢 SQL 示例
SELECT * FROM employees WHERE department_id + 1 = 6;

你建了索引却没用:

CREATE INDEX idx_dept ON employees(department_id);

为什么没用?因为你写了 department_id + 1,MySQL 无法用索引做等值匹配。

✅ 优化写法
SELECT * FROM employees WHERE department_id = 5;

或者在应用层处理表达式。

🔍 EXPLAIN 对比
查询typekeyrowsExtra
原SQLALLNULL100000Using where
优化后refidx_dept1000Using index

🧊 场景二:范围查询 + 排序导致无法使用索引

👎 慢 SQL 示例
SELECT * FROM employees WHERE salary > 5000 ORDER BY department_id;

你以为有 salarydepartment_id 两个索引就行了?不对。

✅ 优化策略:覆盖联合索引 + 顺序写 WHERE
CREATE INDEX idx_salary_dept ON employees(salary, department_id);
SELECT * FROM employees WHERE salary > 5000 ORDER BY department_id;

MySQL 遇到范围条件(比如 >, <)后,会停止继续使用联合索引的剩余部分。所以字段顺序很重要!


⚠️ 场景三:LIKE 模糊查询导致索引失效

👎 典型反例
SELECT * FROM employees WHERE name LIKE '%张三%';

% 开头 → 索引无法使用,变全表扫描。

✅ 可选优化方式
  1. 改成右匹配:
SELECT * FROM employees WHERE name LIKE '张三%';  -- 可用索引
  1. 使用全文索引(MySQL 5.6+):
ALTER TABLE employees ADD FULLTEXT(name);
SELECT * FROM employees WHERE MATCH(name) AGAINST ('张三');

🧪 实战演练:抓一条慢 SQL 优化

❌ 初始查询:
SELECT * FROM employees WHERE gender = 'M' AND salary > 6000 ORDER BY department_id;
EXPLAIN 输出(简化):
typekeyrowsExtra
ALLNULL100000Using where; filesort
✅ 优化步骤:
  1. 创建覆盖联合索引:
CREATE INDEX idx_gender_salary_dept ON employees(gender, salary, department_id);
  1. 改写查询(避免 SELECT *):
SELECT id, name, department_id FROM employees
WHERE gender = 'M' AND salary > 6000
ORDER BY department_id;
优化后 EXPLAIN:
typekeyrowsExtra
rangeidx_gender_salary_dept2000Using index

🟢 索引生效,filesort 消失,速度飞起!


📌 小总结:优化思路

优化方向技巧
索引能否命中WHERE 条件别乱写表达式
排序是否走索引联合索引顺序别写错
避免全表扫描SELECT 字段要精准
避免回表使用覆盖索引
模糊匹配优化尽量右匹配或全文索引
EXPLAIN 解读typerowskey

📘 第九章:EXPLAIN 全解析 —— 看懂它,你就能读懂 MySQL 的大脑!

EXPLAIN 是 SQL 优化的放大镜,它能告诉你这条语句 是怎么执行的、用了啥索引、扫了多少行、有没有排序、有没有临时表……

来看个例子:

EXPLAIN SELECT name FROM employees WHERE department_id = 3;

结果大概长这样:

idselect_typetabletypepossible_keyskeykey_lenrefrowsExtra
1SIMPLEemployeesrefidx_deptidx_dept5const100Using where

我们来逐个解释每一列是什么意思👇


🧠 字段详解:你必须会读懂的部分

🔹 id:查询步骤的 ID
  • 单表查询就是 1
  • 多表关联时,ID 越大,表示执行顺序越靠后(子查询优先执行)
🔹 select_type:查询类型

常见值有:

含义
SIMPLE简单查询(非子查询/UNION)
PRIMARY最外层 SELECT
SUBQUERY子查询
DERIVED派生表(from 子查询)
UNIONUNION 中的第二个及以后查询
🔹 table:当前这一行对应的是哪个表
🔹 type:访问类型(越靠左越高效)
type说明
system表只有一行(rare)
const最优!用主键或唯一索引等值匹配
eq_ref每次从索引中取一行(JOIN 时常见)
ref用了索引,匹配多行
range范围扫描(BETWEEN、>、<)
index全索引扫描(没有用 WHERE)
ALL最差!全表扫描

🔥 优化目标是让 type 尽量往上靠。

🔹 possible_keys:哪些索引可以用

如果是 NULL,说明 SQL 写法有问题(比如字段运算、模糊匹配等)导致 一个索引都没用上

🔹 key:实际用的是哪个索引

不等于 possible_keys 中的第一个,不代表一定选最优的索引(优化器有自己的想法😅)。

🔹 key_len:用到的索引长度(字节数)

判断是否用上了联合索引的所有字段,可以推断是否发生了“索引下推”或“索引裁剪”

🔹 ref:用哪个列和索引比较

比如:const, func, NULL, 或者 table.column

🔹 rows:预估要扫描多少行

这个值越大越慢,可以配合 ANALYZE 来精确估算。一般用于衡量慢 SQL 是否需要优化。

🔹 Extra:其他重要信息

常见值:

Extra含义
Using where用了 WHERE 过滤条件
Using index使用了覆盖索引(无需回表)
Using filesort额外排序操作(可能慢)
Using temporary用了临时表(常出现在 GROUP BY)
Using join bufferJOIN 时无法用索引,走了内存缓冲区

Using filesortUsing temporary 是性能杀手,要特别注意。


🧪 示例:一条普通 SQL 的 EXPLAIN 解读

SELECT id, name FROM employees WHERE department_id = 2 ORDER BY name;
EXPLAIN:
idselect_typetabletypekeykey_lenrowsExtra
1SIMPLEemployeesrefidx_dept5100Using where; filesort
解读:
  • ref 表示走了索引
  • filesort 表示用了排序,没法走索引排序
  • 👉 想消掉 filesort?加个联合索引:
CREATE INDEX idx_dept_name ON employees(department_id, name);

再执行一次:

idselect_typetabletypekeykey_lenrowsExtra
1SIMPLEemployeesrefidx_dept_name10100Using where

🟢 排序问题解决,执行效率更高!


✅ 小结:EXPLAIN 是 MySQL 的 CT 扫描仪

你查得慢,MySQL心里都写在 EXPLAIN 里了。

掌握它,你就能:

  • 看出有没有走索引
  • 判断是否发生了回表、排序、临时表
  • 推断查询扫描的行数,评估是否该加索引
  • 理解优化器的“奇怪行为”背后逻辑

📘 第十章:索引设计实战宝典

—— 加错字段,白加索引!


🎯 一、索引设计的终极目标

索引不是越多越好,而是用得好、加得巧才有效。设计好的索引能:

  • 大幅提高查询速度
  • 减少磁盘IO
  • 降低服务器负载

设计差了,非但没提升,反而拖垮性能,写入变慢还浪费空间。


🧩 二、实战原则 1:明确“最常用查询场景”

问问自己:

  • 业务中最常用的查询条件是什么?
  • 你最频繁用哪个字段做 WHEREJOINORDER BYGROUP BY
  • 查询结果返回哪些字段?是否能用覆盖索引?

🔑 三、实战原则 2:遵循“最左前缀原则”

联合索引的字段顺序很关键!索引只会利用从左边开始的连续字段。

例如:

CREATE INDEX idx_name_age ON users(name, age);
  • WHERE name = '张三' 会用上索引
  • WHERE name = '张三' AND age = 30 也会用
  • WHERE age = 30 不会用这个索引!

🧰 四、实战原则 3:避免冗余索引,合并重复字段

比如:

  • 有索引 (a,b)
  • 又有索引 (a)

这时候可以只保留 (a,b),因为它包含了 (a) 的查询能力。


🚦 五、实战原则 4:适当利用覆盖索引

覆盖索引指的是查询需要的字段都在索引里,MySQL 无需回表。

CREATE INDEX idx_name_age ON users(name, age);

SELECT name, age FROM users WHERE name = '张三';

这时查询很快,因为索引本身就是数据。


⚠️ 六、实战原则 5:避开会导致索引失效的写法

  • 避免字段上用函数(如 DATE(col)
  • 避免在索引字段上进行运算(如 col + 1
  • 避免 LIKE '%abc',尽量 LIKE 'abc%'
  • 避免隐式类型转换

🛠 七、实战示例

假设你有个订单表:

CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    user_id INT,
    product_id INT,
    status VARCHAR(20),
    created_at DATETIME,
    INDEX idx_user_status (user_id, status),
    INDEX idx_created_at (created_at)
);

你经常执行:

SELECT * FROM orders WHERE user_id = 123 AND status = 'paid';

这个查询完美利用 (user_id, status) 联合索引。

但是你执行:

SELECT * FROM orders WHERE status = 'paid';

则无法用联合索引,只能用索引中的 status 单列索引(没有建单列索引的话就全表扫)。


🎯 八、总结

设计原则注意点
明确业务查询场景聚焦常用查询字段
最左前缀原则联合索引字段顺序不能乱
合理合并索引避免冗余
利用覆盖索引提高查询速度,减少回表
避免索引失效写法函数、运算、隐式转换、左模糊查询慎用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JavaPub-rodert

谢谢老板

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值