32-慢查询排查全流程(下)-索引优化实战与最左前缀原则

文章目录

  • 你的接口为什么慢?(下)------索引优化实战:什么列该建索引、最左前缀原则与联合索引
    • 导入语
    • [1 ~> B+Tree 与最左前缀原则------联合索引的生死线](#1 ~> B+Tree 与最左前缀原则——联合索引的生死线)
      • [1.1 B+Tree 的数据排列](#1.1 B+Tree 的数据排列)
      • [1.2 最左前缀原则](#1.2 最左前缀原则)
      • [1.3 为什么不建四个单列索引而行不通](#1.3 为什么不建四个单列索引而行不通)
    • [2 ~> Django 中建索引------`db_index` 与 `Meta.indexes`](#2 ~> Django 中建索引——db_indexMeta.indexes)
      • [2.1 单列索引](#2.1 单列索引)
      • [2.2 联合索引](#2.2 联合索引)
    • [3 ~> 覆盖索引------为什么 `SELECT *` 是索引的敌人](#3 ~> 覆盖索引——为什么 SELECT * 是索引的敌人)
      • [3.1 什么是覆盖索引](#3.1 什么是覆盖索引)
      • [3.2 Django 层面如何利用覆盖索引](#3.2 Django 层面如何利用覆盖索引)
    • [4 ~> 索引选择性------不是每列都该建索引](#4 ~> 索引选择性——不是每列都该建索引)
      • [4.1 选择性计算](#4.1 选择性计算)
      • [4.2 在 Django 中检查选择性](#4.2 在 Django 中检查选择性)
    • [5 ~> 真实案例------多条件搜索接口 3 秒到 90ms](#5 ~> 真实案例——多条件搜索接口 3 秒到 90ms)
    • [思考 && 总结](#思考 && 总结)
    • 结尾

你的接口为什么慢?(下)------索引优化实战:什么列该建索引、最左前缀原则与联合索引

📖 文章简介: 上篇用 Debug Toolbar 抓到了慢查询,用 EXPLAIN 读懂了执行计划。下篇进入索引实战------不是"给所有列都加索引",而是精准找出对业务最有用的那几列。从 B+Tree 的排列规则讲起,推导出最左前缀原则的完整含义------为什么 (a, b, c) 联合索引在 WHERE b = 1 时根本用不上。覆盖索引(Using index)为什么比普通索引快、为什么 SELECT * 让索引优化效果打折、以及 Django db_indexMeta.indexes 的单列与联合索引建法。配有一个真实案例------一个多条件搜索接口从 3 秒降到 90ms,核心是建了一个正确的联合索引而不是四个单列索引。


🎬 个人主页: 源码骑士

专栏传送门: 《Android开发基础》《python基础课程》

⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂


🎬 源码骑士的简介:

5年Android Framework系统开发经验,曾主导多项系统级性能优化专项

技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)

累计产出原创技术文章100+篇,文章以源码拆解为特色,被读者评价为"看一篇胜过啃一周文档"


导入语

上篇我们学会了用 Debug Toolbar 抓查询、用 EXPLAIN 读计划。这个星期我们真正动手------给表加索引。但"加索引"三个字说起来容易,落实到具体的列上就模糊了:该加哪几列?单列索引还是联合索引?联合索引的列顺序怎么排?

2021 年我优化过一个多条件搜索接口------按"作者名 + 图书分类 + 出版日期范围"三个条件组合查询。最初建了三个单列索引------两个有用到一个被忽略。后来换成 (author_id, category_id, pub_date) 的联合索引------同样三个条件的查询,响应时间从 3 秒降到 90ms。区别就在于------你懂不懂最左前缀原则。


1 ~> B+Tree 与最左前缀原则------联合索引的生死线

1.1 B+Tree 的数据排列

MySQL InnoDB 引擎的索引底层是 B+Tree(大部分情况下)。联合索引的内部排列是先按第一列排序,再按第二列排序------后面的列依赖于前面的列。一个联合索引 (a, b, c) 的内部数据排列:

复制代码
索引树节点(按 a → b → c 排序):
(1, 2, 3), (1, 2, 5), (1, 4, 1), (2, 1, 3), (2, 3, 1), (2, 3, 7) ...

1.2 最左前缀原则

联合索引在匹配时从最左列开始------不能跳级。

你的 WHERE 子句 索引 (a, b, c) 能用吗 用什么
WHERE a = 1 ✅ 能用 用 a 列
WHERE a = 1 AND b = 2 ✅ 能用 用 a 和 b 列
WHERE a = 1 AND b = 2 AND c = 3 ✅ 全能用 a、b、c 都参与
WHERE b = 2 ❌ 不能 索引被跳过------无法定位
WHERE c = 3 ❌ 不能 索引被跳过
WHERE a = 1 AND c = 3 ⚠️ 只用 a c 被跳过了,索引停在 a

简单记:联合索引像"一排书架------每层按 a 字母排序,同 a 下再按 b 排序。你要找 b 一样的东西,必须先从 a 进去,没从 a 进来的话无法定位。

1.3 为什么不建四个单列索引而行不通

sql 复制代码
-- 单列索引各有建一个:idx_author、idx_category、idx_pub_date
SELECT * FROM book
WHERE author = "鲁迅" AND category = "文学" AND pub_date >= "2020-01-01";

MySQL 的优化器通常只会选择一个索引------而不是"三个各自用一段"。它选了 idx_author 后,另外两个列的过滤条件无法用到索引,只能回表读取数据后逐行检查。联合索引 (author, category, pub_date) 能一次性定位到数据------三列都参与索引扫描。


2 ~> Django 中建索引------db_indexMeta.indexes

2.1 单列索引

python 复制代码
class Book(models.Model):
    author = models.CharField(max_length=100, db_index=True)   # 单列索引
    title = models.CharField(max_length=200)

2.2 联合索引

python 复制代码
class BorrowRecord(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    book = models.ForeignKey(Book, on_delete=models.CASCADE)
    borrowed_at = models.DateTimeField()
    is_returned = models.BooleanField(default=False)

    class Meta:
        indexes = [
            models.Index(fields=["user", "is_returned", "borrowed_at"]),
            # 用于"某用户借了哪些未还的书"的查询
        ]

Django 迁移会自动生成 CREATE INDEX

sql 复制代码
CREATE INDEX borrow_user_returned ON borrowrecord (user_id, is_returned, borrowed_at);

3 ~> 覆盖索引------为什么 SELECT * 是索引的敌人

3.1 什么是覆盖索引

当一个索引包含了查询需要的所有列 ,MySQL 可以直接从索引返回结果,不需要再去访问数据行本身。这就是覆盖索引------EXPLAIN 里 Extra 列会显示 Using index

sql 复制代码
-- 有联合索引 (user_id, borrowed_at)
-- 查询只用到这两列
SELECT user_id, borrowed_at FROM borrowrecord WHERE user_id = 42;
-- Extra: Using index  ← 覆盖索引------只扫描索引,没碰数据行

但如果 SELECT *

sql 复制代码
-- 有同样的索引,但查询用到了索引之外的列(如 book_id、is_returned 等)
SELECT * FROM borrowrecord WHERE user_id = 42;
-- Extra: Using index condition  ← 用了索引定位,但要回表读取其他列

SELECT * 让覆盖索引优化失效------每次都多一次"回表"操作。

3.2 Django 层面如何利用覆盖索引

python 复制代码
# ❌ 回表查询------ORM 默认 SELECT *
records = BorrowRecord.objects.filter(user_id=42)

# ✅ 覆盖索引------只选索引中包含的列
records = BorrowRecord.objects.filter(user_id=42).values_list("user_id", "borrowed_at")

4 ~> 索引选择性------不是每列都该建索引

4.1 选择性计算

索引选择性 = 不重复的值数 ÷ 总行数。选择性越接近 1,索引效果越好。

sql 复制代码
-- 性别列:只有 "男" "女" 两个值,1 百万行 → 选择性 0.000002------极差
-- 用户 ID:1 百万行中 1 百万个唯一值 → 选择性 1.0------完美索引列
-- 订单状态:"pending/paid/shipped/cancelled" 四个值,选择性 0.000004------还是太差

选择性低于 10-15% 的列不建议建单独索引。 MySQL 优化器可能直接忽略它,选择全表扫描。

4.2 在 Django 中检查选择性

python 复制代码
from django.db.models import Count

# 查看某列的不重复值数占比
total = BorrowRecord.objects.count()
distinct_statuses = BorrowRecord.objects.values("is_returned").distinct().count()
selectivity = distinct_statuses / total
print(f"选择性: {selectivity:.4f}")   # is_returned 只有 True/False → 选择性极低

5 ~> 真实案例------多条件搜索接口 3 秒到 90ms

2021 年那个多条件搜索------原始没有联合索引:

sql 复制代码
SELECT * FROM book WHERE author = "鲁迅" AND category = "文学"
                     AND pub_date >= "2020-01-01";
-- EXPLAIN: type=ref, key=idx_author, rows=5000, Extra=Using where

选了 idx_author 索引,categorypub_date 的过滤在回表后逐行判断。索引只起到了"缩小集合"的作用,没起到"过滤"的作用。

建联合索引后:

sql 复制代码
ALTER TABLE book ADD INDEX idx_multi (author, category, pub_date);

SELECT * FROM book WHERE author = "鲁迅" AND category = "文学"
                     AND pub_date >= "2020-01-01";
-- EXPLAIN: key=idx_multi, rows=120, Extra=Using index condition

扫描行数从 5000 降到 120,响应从 3 秒降到 90ms。核心不是"多加索引",而是"把多个条件组合成一个联合索引"。


思考 && 总结

索引优化的关键公式:

  1. 最左前缀原则 ------联合索引的列从最左开始匹配,不能跳级。WHERE 只用到 b 时无法用 (a, b, c) 索引。
  2. 覆盖索引比普通索引快 ------因为它不需要回表。能不用 SELECT * 就不用。
  3. 索引选择性决定列是否值得建索引。 低于 10-15% 的列不建单独索引。
  4. 联合索引的顺序最重要。 把选择性高、查询频率高的列放最左边。范围查询列放最后。

结尾

慢查询排查上下篇完结。下篇进入内存排查------Python 内存泄漏的排查工具箱。

源码骑士 --- 源码级拆解,从底层看透技术

👀 关注:跟博主一起从源码视角深耕底层原理

❤️ 点赞:让优质内容被更多人看见

收藏:核心知识点存好,随用随查

💬 评论:分享你的经验或疑问,一起交流

🔄 一键四连:别忘了给博主一键四连!

🗡️ 寄语:一个正确的联合索引胜过四个单列索引。

结语:索引不是越多越好------是越精准越值钱。EXPLAIN + 联合索引 + 覆盖索引,三板斧就能解决 90% 的慢查询。一键四连!

相关推荐
闲猫3 分钟前
Python FastAPI + SQLAlchemy 入门教程:从零搭建你的第一个 Web 应用
前端·python·fastapi
北极星日淘3 小时前
中古货品品相评级算法实战|Java权重计分实现标准化五级品相体系
开发语言·python
hangyuekejiGEO3 小时前
临沂GEO服务企业技术选型分析
人工智能·python
闲猫5 小时前
Python 虚拟环境 virtualenv & uvicorn 服务搭建 & FAstAPI 使用
开发语言·python
AI视觉网奇5 小时前
vllm 多卡部署
python
精明的身影5 小时前
网络计划WebApp求解:融合Python与AI决策的项目管理系统
网络·python·web app
AI科技星5 小时前
全域谱分析:无穷维超复数信息场分形统一场论——自然、量子、金融多重分形第一性原理完整体系(中英双语终稿)
人工智能·python·算法·金融·乖乖数学·全域数学
用户0332126663675 小时前
使用 Python 在 Word 文档中添加批注
python
蜡笔削薪5 小时前
财联万业(杭州)数字科技有限公司能否给代理划定独家经营区域?
大数据·人工智能·python·科技
站大爷IP6 小时前
Python的字典合并坑了我三天,原来是踩了这个坑
python