首页 / 知识库 / python服务端进阶 / 数据库优化-索引与分库分表

01 - 索引的本质:为什么能加速

数据库优化,第一件事永远是索引——它性价比最高、见效最快。这一章不背命令,而是让你从直觉上理解索引为什么能让查询快成百上千倍,以及它的代价。

一句话先记住:索引就像书的目录——不用一页页翻(全表扫描),直接翻到目录定位页码。代价是目录本身要占空间、书更新时目录也得跟着改。


1.1 没有索引,数据库怎么找数据

假设一张 100 万行的 users 表,你要查 WHERE email = 'a@x.com'

没有索引 → 全表扫描(Sequential Scan)
  从第 1 行开始,一行一行比对 email
  第 1 行?不是。第 2 行?不是。……第 999999 行?不是。第 1000000 行?是!
  → 最坏要看 100 万行,慢得要死

数据量小时无所谓,数据量一大,全表扫描就是灾难。


1.2 有了索引,就像查字典

索引是数据库为某个列提前排好序、建好的一个查找结构。查字典时你不会从第一页翻,而是用拼音/部首目录直接定位。

email 上建了索引后:
  索引里的 email 是排好序的,数据库用「二分查找」快速定位
  100 万行 → 只需要约 20 次比较就能找到(log₂ 1000000 ≈ 20)
  → 从「看 100 万行」变成「看 20 次」,快了几万倍

1.3 索引的底层结构:B+ 树(直觉版)

绝大多数数据库索引(包括 PostgreSQL 默认索引)用的是 B+ 树。你不需要会实现它,只要有这个直觉:

                 [50]              ← 根节点
              /        \
        [20, 35]      [70, 90]     ← 中间节点(像目录的目录)
        /   |   \      /   |   \
     ...   ...  ...  ...  ...  ...  ← 叶子节点(存实际的键 + 指向数据的位置)
     └──────────────────────────┘
        叶子节点之间还用链表连起来 → 范围查询(如 age > 30)也很快

为什么 B+ 树快:

  • 层数很少:几百万行数据,树也就 3-4 层。查一个值最多走 3-4 步就到叶子
  • 有序:天然支持 =><BETWEENORDER BY
  • 叶子相连:范围扫描时顺着链表走即可,不用回头

记住这个数字感:几百万行的表,B+ 树索引查一条数据,通常只需要 3-4 次磁盘访问。 这就是它快的根源。


1.4 聚簇索引 vs 非聚簇索引

这是一个重要概念,不同数据库实现不同:

聚簇索引(Clustered):
  索引的叶子节点,直接就是「数据本身」
  → 数据行按索引顺序物理存储
  → 找到索引 = 找到数据,一步到位

非聚簇索引(Non-clustered / 二级索引):
  索引的叶子节点,存的是「指向数据行位置的指针」
  → 找到索引后,还要再顺着指针去取真正的数据行(叫"回表")

PostgreSQL 的特点:PostgreSQL 没有 MySQL InnoDB 那种「主键即聚簇索引」的设计。它的表数据独立存放(叫 heap),所有索引(包括主键索引)都是「指向数据物理位置」的,性质上更接近「非聚簇」。所以在 PG 里,通过索引查到后,通常都要再去 heap 取数据行。(这也让「覆盖索引」在 PG 里格外有价值,见第 02 章。)


1.5 索引的代价(为什么不能乱建)

索引不是免费的,也不是越多越好:

① 占空间
   索引本身是一份额外的数据结构,要占磁盘(大表的索引可能很大)

② 拖慢写入
   每次 INSERT / UPDATE / DELETE,不光改数据,
   所有相关索引也都要跟着更新(维护 B+ 树的有序性)
   → 索引越多,写越慢

③ 可能没用
   给一个「很少被查询、或区分度很低」的列建索引,纯属浪费
   (比如给"性别"列建索引,就两个值,索引几乎没用)
好处代价
查询快(读加速)占额外空间
支持排序、范围查询拖慢写入(增删改)

核心权衡:索引是「用空间 + 写入变慢」换「查询变快」。读多写少的列值得建索引;写极频繁、或很少查询的列要谨慎。


1.6 什么样的列适合建索引

✅ 适合:
   · 经常出现在 WHERE 条件里的列
   · 经常用于 JOIN 连接的列(外键)
   · 经常用于 ORDER BY / GROUP BY 的列
   · 区分度高的列(值很分散,如 email、手机号、id)

❌ 不适合:
   · 区分度极低的列(性别、状态只有几个值)
   · 很少被查询的列
   · 频繁更新、但很少用于查询条件的列
   · 超大文本字段(应考虑专门的全文索引)

本章小结

你要记住要点
索引本质像书的目录,避免全表扫描
底层结构B+ 树,几百万行只需 3-4 层
聚簇 vs 非聚簇PG 的表数据独立存放,索引都要「回表」取数据
代价占空间 + 拖慢写入,不能乱建
建索引原则高频查询、高区分度的列才值得建

下一章预告:知道了索引原理,实战中还有一堆讲究——联合索引怎么建、什么是覆盖索引、为什么你建了索引却「没走索引」。


下一章 → 02 - 索引优化实战