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
关键流程说明:
-
索引查找路径(核心优化点):
graph LR A[索引根节点] --> B{在缓冲池?} B -->|是| C[立即读取] B -->|否| D[磁盘加载] C --> E[定位下层节点] D --> E E -->|非叶子节点| B E -->|叶子节点| F[获取数据页ID] -
索引页 vs 数据页加载差异:
项目 索引页加载 数据页加载 定位方式 B+树层级遍历 直接通过Page ID 缓存位置 共享缓冲池 共享缓冲池 加载频率 高频(非叶节点常驻内存) 按需加载 预读触发 随机预读 线性/随机预读 -
索引优化技术:
- 节点预加载:启动时加载索引根节点
- 缓存固定:
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的数据行