首页/案例库/数据库索引原理
进阶数据库

数据库索引原理

B+Tree 如何让查询从秒级变成毫秒级

没有索引的查询需要全表扫描,数据量大时极慢。B+Tree 索引将数据组织成平衡树,查询复杂度从 O(n) 降到 O(log n)。本案例深入剖析 B+Tree 的结构、页分裂与合并、聚簇索引与二级索引、覆盖索引、最左前缀匹配,以及如何用 EXPLAIN 分析查询计划。理解索引原理是数据库性能优化的基础。

索引B+TreeMySQL查询优化EXPLAIN聚簇索引
STEP_1
B+Tree 的高度低、节点大,非常适合磁盘存储
平衡多路搜索树 — PROCESSING
btree-structure.log
// B+Tree 节点结构(概念)
interface BPlusTreeNode {
  isLeaf: boolean
  keys: Key[]           // 索引键
  children: Node[]      // 非叶子节点:子节点指针
  values: Value[]       // 叶子节点:数据或行指针
  next: LeafNode | null // 叶子节点:下一个叶子
}

// 16KB 页能存储的键数量
// 假设键 8 字节、指针 6 字节
// 非叶子节点:16384 / 14 ≈ 1170 个键
// 3 层 B+Tree:1170 * 1170 * 1170 ≈ 16 亿行

// 查询过程(伪代码)
function search(key: Key): Value | null {
  let node = this.root
  
  // 从根节点向下搜索
  while (!node.isLeaf) {
    // 二分查找子节点指针
    const index = binarySearch(node.keys, key)
    node = node.children[index]  // 1 次磁盘 IO
  }
  
  // 在叶子节点查找
  const index = binarySearch(node.keys, key)
  return node.keys[index] === key ? node.values[index] : null
}

// 范围查询利用叶子链表
function rangeSearch(startKey: Key, endKey: Key): Value[] {
  let node = this.findLeaf(startKey)
  const results: Value[] = []
  
  while (node && node.keys[0] <= endKey) {
    for (let i = 0; i < node.keys.length; i++) {
      if (node.keys[i] >= startKey && node.keys[i] <= endKey) {
        results.push(node.values[i])
      }
    }
    node = node.next  // 沿链表遍历
  }
  return results
}

B+Tree 结构B+Tree 是平衡的多路搜索树:非叶子节点只存储键和指针,叶子节点存储键值对并用链表串联。一个节点通常等于一个磁盘页(16KB),可以存储上千个键,树的高度通常只有 3-4 层。3 层的 B+Tree 可以索引约 2000 万行数据,查询只需 3 次磁盘 IO。叶子节点的链表支持高效的范围查询。

实时沙盒SANDBOX
FAULT_INJECTED
快速场景
手动调节
表数据量
模拟的行数
1000k
数据量适中
使用索引
全表扫描,O(n) 查询
查询类型
模拟不同的查询模式
模拟选定的查询模式
显示执行计划
隐藏执行计划