欢迎光临
我们一直在努力

为什么你的 SQL 查询慢如蜗牛?——数据库索引原理一次讲透

为什么你的 SQL 查询慢如蜗牛?——数据库索引原理一次讲透

一条 SQL 在测试环境秒回,上线后拖垮整个页面——这是无数后端工程师的噩梦。90% 的慢查询,靠一个索引就能解决。但"加索引"三个字背后,是 B+ 树、扇出、回表、覆盖索引这一整套机制。今天一次讲透,看完你就能自己诊断慢查询。

一、先看真实差距:一千万行的表怎么查

假设一张表有一千万行数据,没有索引时查一条记录,数据库要从头到尾扫一遍——按每页 500 行算,约 2 万次页读取。而加了 B+ 树索引后,点查只需 3 次页读取(树高 3 层)。

2 万次 vs 3 次,差 6000 多倍。 这就是索引的价值。

二、为什么是 B+ 树?从 IO 代价推导

磁盘读写的最小单位是"页"(通常 8~16KB),一次页读取就是一次 IO。评价一个查找结构,就看完成一次查找需要几次页读取。

  • 二分查找有序文件:一千万行约 23 次随机页读,且插入要挪动数据 → 出局
  • 普通二叉树:每个节点 2 个分支,树高 23 层 = 23 次 IO → 出局
  • B+ 树:一个节点 = 一个页,页内塞满键和子指针。16KB 页、键+指针平均 32 字节 → 每页约 512 个分支。三层覆盖 512³ ≈ 1.3 亿键,一千万行的表树高只有 3!

B+ 树相对普通 B 树的两处关键改造,都是为数据库量身定制:

  • 内部节点只存键、不存数据:数据全部下沉到叶子层,内部节点扇出最大化,树更矮、缓存更友好
  • 叶子节点双向链表串联:范围查询定位起点后,沿链表顺序扫就行——这是哈希索引永远做不到的(哈希只能点查)
  • 三、点查与范围查询的代价账

    点查:从根到叶子,树高 h 次页访问(h=3,上层页常驻内存,真实 IO 常只有 1 次)
    范围查询:先点查定位起点(3 次),再沿叶子链顺序扫描命中行

    而范围查询才是 B+ 树的招牌科目——ORDER BY、GROUP BY 只要排序键和索引键一致,直接按叶子链输出,连排序算子都省了。

    四、聚簇索引、二级索引与"回表"的坑

    索引分两种形态:

    • 聚簇索引:叶子直接存完整数据行,表本身按主键组织成一棵 B+ 树
    • 二级索引:叶子只存"索引键 + 主键值"。查到主键后,还要再走一遍主键树才能拿到整行数据——这就是回表,一次查询隐含两次树查找

    回表的代价催生了覆盖索引技巧:如果查询需要的所有列都包含在二级索引里,叶子条目本身就能回答查询,回表整个免除。

    五、代码演示:亲手算一遍索引账

    下面这段 Python 模拟一千万行表的三种查询策略,页数对比一目了然:

    import math

    N = 10_000_000 # 一千万行
    page_size, entry = 16*1024, 32 # 16KB 页,键+指针 32 字节
    fanout = page_size // entry # 每页分支数(扇出)
    height = math.ceil(math.log(N, fanout)) # 树高
    rows_per_page = 500

    print(f"扇出 {fanout} -> 树高 {height} | 全表扫描 {N//rows_per_page:,} 页")

    def covering_index(r): # 覆盖索引:叶子链顺序扫,无回表
    return height + N * r / rows_per_page
    def secondary_index(r): # 二级索引:每行回表一次随机读
    return height + N * r / rows_per_page + N * r

    full = N / rows_per_page
    print(f"{'命中率':>6} | {'覆盖索引':>10} | {'二级索引':>10} | {'全表':>8}")
    for r in [0.0001, 0.01, 0.1, 0.3]:
    ci, si = covering_index(r), secondary_index(r)
    print(f"{r*100:5.1f}% | {ci:10.0f} | {si:10.0f} | {full:8.0f}")

    运行输出:

    扇出 512 -> 树高 3 | 全表扫描 20,000 页
    命中率 | 覆盖索引 | 二级索引 | 全表
    0.0% | 5 | 1005 | 20000
    1.0% | 203 | 100203 | 20000
    10.0% | 2003 | 1002003 | 20000
    30.0% | 6003 | 3006003 | 20000

    看懂这张表,你就懂了索引优化的全部精髓:

    • 覆盖索引:哪怕命中 30% 的行也只有 6000 页,始终碾压全表扫描
    • 二级索引:命中率一过 1%,回表的随机读就让代价爆炸(10 万页 vs 全表 2 万页)——此时数据库优化器会正确地放弃索引改走全表扫描

    六、避坑清单(面试+实战都考)

  • 主键要短:主键值会被复制进每条二级索引条目,UUID 字符串主键会让所有索引变胖
  • 最左前缀原则:复合索引 (a, b) 上,“a=1 AND b BETWEEN…” 高效,单独 “b BETWEEN…” 用不上索引
  • 高选择率别用二级索引:范围覆盖超 10%~30% 的表,回表随机读会超过全表顺序扫描
  • 覆盖索引是免费午餐:高频查询把需要的列塞进索引(include 列),消灭回表;但列别贪多,否则写放大
  • 自增主键利于顺序插入:随机键(UUID)处处分裂,填充因子要调低
  • 先看执行计划再优化:EXPLAIN 里 type 列是 index 还是 ALL,一眼判断有没有走索引
  • 七、想系统学数据库?

    本文精选自 ima 知识号【Kruptos】《数据库系统详解》订阅库(第 013 期 B+ 树索引结构与范围查询、第 014 期插入删除与并发控制等 100 期系统教程,从关系模型、索引、事务、锁到分布式数据库,每期配可运行 Python 代码)。

    📚 完整系列 100 期 + 配套代码,已在 ima 知识号发布

    • 🗂 66+ 技术知识库:信号与系统、SDR 软件无线电、数字信号处理、操作系统、AI Agent、大模型微调……几乎覆盖全部软硬件技术栈
    • 🧠 8 款 AI 技能:系列生产、知识库管理、CMMI 受管开发、自进化 Agent 等,已在 ima 技能广场上架,即装即用
    • ✅ 全部免费订阅,扫码即可关注,后续更新自动推扫一扫,订阅 ima 知识号【Kruptos】:

    在这里插入图片描述


    作者:Kruptos(西电毕业,13 年无线通信/DSP/嵌入式科研)|原创内容,转载注明出处

    赞(0)
    未经允许不得转载:171主机测评 » 为什么你的 SQL 查询慢如蜗牛?——数据库索引原理一次讲透
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址