graph TD A[SQL查询] --> B{是否使用索引?} %% 索引路径 B -->|使用索引| C[遍历B+树索引] C --> D[从根节点开始] D --> E{索引页在缓冲池?} E -->|是| F[读取索引页] E -->|否| G[加载索引页到缓冲池] G --> H[磁盘I/O] F --> I[定位下层节点] I --> J{是否叶子节点?} J -->|否| E J -->|是| K[获取数据页ID] %% 全表扫描路径 B -->|无索引| L[获取首个数据页ID] %% 公共数据页加载流程 K --> M{数据页在缓冲池?} L --> M M -->|是| N[直接读取数据] M -->|否| O[缓冲池空间分配] O --> P{空闲页可用?} P -->|是| Q[分配空闲页] P -->|否| R[LRU淘汰] R --> S[选择LRU尾部页] S --> T{是否脏页?} T -->|是| U[同步刷盘] T -->|否| V[直接释放] U --> W[磁盘写入] V --> X[释放空间] W --> Y[分配空间] X --> Y Y --> Z[磁盘读取数据页] Z --> AA[校验数据完整性] AA -->|成功| AB[加载到缓冲池] AA -->|失败| AC[Doublewrite恢复] AC --> AB AB --> AD[插入LRU Old区头部] AD --> AE[更新哈希表] AE --> AF[返回查询结果] N --> AF %% 后台进程 U -.-> BA[后台刷脏线程] BA --> BB[异步刷脏] %% 预读机制 Z --> CA{顺序访问?} CA -->|是| CB[触发线性预读] CA -->|否| CC{热点区域?} CC -->|是| CD[触发随机预读] CB --> CE[预加载后续数据页] CD --> CE

关键流程说明:

  1. 索引查找路径(核心优化点)

    graph LR A[索引根节点] --> B{在缓冲池?} B -->|是| C[立即读取] B -->|否| D[磁盘加载] C --> E[定位下层节点] D --> E E -->|非叶子节点| B E -->|叶子节点| F[获取数据页ID]
  2. 索引页 vs 数据页加载差异

    项目 索引页加载 数据页加载
    定位方式 B+树层级遍历 直接通过Page ID
    缓存位置 共享缓冲池 共享缓冲池
    加载频率 高频(非叶节点常驻内存) 按需加载
    预读触发 随机预读 线性/随机预读
  3. 索引优化技术

    • 节点预加载:启动时加载索引根节点
    • 缓存固定ALTER INDEX ... CACHE
    • 覆盖索引:避免回表查询
    -- 创建覆盖索引示例
    CREATE INDEX idx_cover ON users(name, age);
    -- 查询可直接使用索引
    SELECT name, age FROM users WHERE name LIKE 'A%';
    

完整示例:通过索引查找用户

1. 查询: SELECT * FROM users WHERE id = 100
2. 使用主键索引:
   - 访问根节点页 (PageID=101)
   - 访问非叶节点页 (PageID=203)
   - 到达叶节点页 (PageID=305)
3. 从叶节点获取数据页ID: 505
4. 加载数据页505到缓冲池:
   - 检查空闲页 -> 无
   - 淘汰LRU尾部页 (PageID=701)
   - 发现701是脏页 -> 同步刷盘
   - 加载505到缓冲池
   - 插入LRU Old区头部
5. 返回id=100的数据行