💻 第八章 一用就会的数据库
📄 一句话概括

索引像书的目录——查得快,代价是写入慢一点、多占一块空间;常查的列才值得建。

📝 本节和前面几节的关系

表设计好了、数据也进去了,本节解决【查得慢】这个最日常的痛点。

一、两条路:逐行翻还是直达

没有索引时,DBMS 只能全表扫描:一行行比对,找到为止——数据一多就慢得明显; 建了索引,DBMS 顺着索引的结构直扑目标——和 6.3 的二分查找是同一个思路:先有顺序结构,再有对数级查找; 数据库自己会告诉你走了哪条路——查询计划里 SCAN 是全表扫、SEARCH 是走索引。

二、索引的代价

写入变慢:每插一条、改一条,索引也要跟着维护更新; 多占空间:索引是额外存的一份数据结构,不是白来的; 所以索引不是越多越好——只为【经常用来查】的列建。

三、什么时候建索引

常出现在 WHERE 条件、JOIN 关联、排序(ORDER BY)里的列,优先考虑; 几乎不查的列、本身就很大的文本列,建了也白占地方; 【⚠️ 2026 现状】现代数据库还有自动索引推荐、覆盖索引等技巧,但【用空间与写入换读取】这个权衡,几十年没变。

四、动手 Demo:有索引与没索引的查询计划

同一张表同一句查询,加索引前后让数据库自己报告它走了哪条路。

import sqlite3
con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT)")
con.executemany("INSERT INTO books (title) VALUES (?)",
                [("书%02d" % i,) for i in range(1, 31)])
plan = "EXPLAIN QUERY PLAN SELECT id FROM books WHERE title='书17'"
for p in con.execute(plan): print(p[3])
con.execute("CREATE INDEX idx_title ON books(title)")
for p in con.execute(plan): print(p[3])

实跑输出:

--------------------------------------------------------------------
索引能够提升数据的检索速度
--------------------------------------------------------------------

查找数据有两条路: 逐行翻(全表扫描) 和 按索引直达。
同一张表, 一次无索引、一次有索引, 看数据库自己的说法:

    [无索引] 找 title=书17 的执行计划:
        SCAN books
    [建索引后] 同样的查询:
        SEARCH books USING COVERING INDEX idx_title (title=?)

    SCAN = 一行行扫; SEARCH ... USING INDEX = 拿索引直扑目标。

  方面         有索引               没索引
  ---------------------------------------------
  读取速度     快(直达)             慢(逐行扫)
  写入速度     稍慢(要维护索引)     快
  占用空间     额外存一份索引       不占

索引像书的目录: 查得快, 但多印了几页纸——常查的列才值得建。

SCAN 是逐行翻、SEARCH ... USING INDEX 是拿索引直扑目标——查询计划是判断索引有没有生效的第一手证据。 索引与 6.3 的二分查找同源:都是【先花代价造好有顺序的结构,换取查得快】。

30 秒实验

  1. 说出索引的两项代价:为什么它会让写入变慢、还多占空间?
  2. 给一张订单表挑一列:哪列最该建索引?(常出现在查询条件里的那列)

常见误区

  1. 以为索引越多越好——每个索引都要占空间、拖慢写入。
  2. 以为索引能解决一切慢查询——查整个范围、查不常查的列,索引可能帮不上。
自测三问(点开对答案) 1. 索引的收益与代价各是什么? —— 收益是查得快,代价是写入变慢、多占空间。 2. 数据库怎么告诉你它走没走索引? —— 查询计划:SCAN 是全表扫,SEARCH 是走索引。 3. 索引和二分查找的共同思路? —— 先用代价造好有序结构,换取对数级的查找速度。

相关笔记