伴随企业核心业务持续向云原生架构演进,数据库正在从传统本地部署模式,加速迈向存算分离架构。
作为百度智能云自研云原生数据库,GaiaDB 采用存算分离设计,实现计算与存储解耦,在提供更高弹性与吞吐能力的同时,也对复杂查询性能提出了更高要求。
GaiaDB 支持大容量、高性能、高弹性场景,并兼容 MySQL 生态。
但在存算分离架构下,数据页读写需要通过远程 RPC 完成,相比传统本地磁盘,呈现出明显的:
高延迟、高带宽
这一特点。
单次 IO 延迟可能从数十微秒提升至百微秒以上,但整体并发吞吐能力远高于本地磁盘。随着查询规模增加,大量同步远程 IO 容易成为影响查询性能的重要因素。
针对这一挑战,GaiaDB 推出了预读加速能力。
通过提前预测后续需要访问的数据页,并异步并行加载到内存,GaiaDB 将原本同步等待的数据访问过程,转变为主动准备。
在典型业务场景下,预读加速可实现 5~20 倍性能提升,加速后性能与本地盘持平,甚至超出数倍。
一句话概括:
查询还没开始,数据已经提前就位。
哪些场景收益最明显?
预读加速主要适用于存在大量同步远程 IO 的查询场景。
通常来说,扫描页数量越多、缓存命中率越低,收益越明显。
典型业务场景包括以下几类。
-
查询历史或冷数据
数据写入后长期未访问,已经从内存中淘汰。
例如:
-
查询用户 3 个月前的订单记录
-
拉取去年的操作日志
-
大范围时间区间扫描
WHERE 条件跨越较长时间范围,需要扫描大量索引页。
例如:
create_time BETWEEN '2025-01-01' AND '2025-04-01'
-
深度分页
当翻页位置较深时,需要扫描并跳过大量数据。
例如:
ORDER BY id DESC LIMIT 100000,20
-
二级索引命中大量数据后回表
通过非主键索引匹配大量记录后,需要逐行回到聚簇索引获取完整数据。
例如:
WHERE status = 0 AND type = 1
当命中上万行时,会产生大量随机访问。
-
低频大表报表与统计查询
后台跑批任务由于访问频率低,数据大概率不在缓存中。
例如:
-
每日凌晨对账汇总
-
运营数据导出
-
大表统计分析
-
新实例启动后的首批查询
实例启动或主备切换后,Buffer Pool 为空,所有访问都属于冷读。
这些场景虽然表现形式不同,但背后往往有同一个问题:
同步 IO 等待时间过长。
所以核心优化策略不是“等数据变快”,而是:
提前准备数据,而不是等待数据。
对此,百度智能云 GaiaDB 预读加速基于 B+ 树逻辑结构,提前预测后续访问的数据页,并异步并行预取至内存,从而消除逐页同步 IO 等待。
整体来看,预读加速主要覆盖两类核心查询路径:
-
主索引顺序扫描
-
二级索引回表
B+ 树逻辑预读:先看目录,再翻内容
B+ 树逻辑预读适用于顺序扫描索引的业务场景,例如:
-
大范围时间区间查询
-
报表分析
-
数据导出
-
批量扫描类查询
过去,这类查询往往需要持续扫描大量索引页。一个报表可能耗时几十秒,复杂场景甚至需要两分钟以上。
开启索引逻辑预读后,原本分钟级的查询可以缩短至秒级返回,实现数倍性能提升。
它的核心思路可以理解为:
先看目录,再翻内容。
当数据库执行大范围顺序扫描时,逻辑预读不会等当前页面读取完成后再访问下一页。
它会像翻书时先看目录一样,提前查看索引上层节点,获取接下来即将访问的数据页列表,并一次性批量加载至内存,从而大幅减少扫描过程中的等待时间。
逻辑预读如何工作?
整个索引逻辑预读流程由 4 个核心模块协同完成,按照:
触发 → 决策 → 执行 → 缓存
这一链路逐层推进。
模块一:用户查询线程
用户查询线程负责执行查询,触发索引顺序扫描,并识别预读边界。
主要流程包括:
-
对数据库索引叶子页执行顺序扫描;
-
按照 Page100 → Page101 → Page102 → …… 的顺序持续推进;
-
在扫描过程中持续检测预读边界;
-
将触发信号传递至逻辑预读决策层。
这一阶段负责正常查询执行,同时也是整个预读机制的触发入口。
模块二:逻辑预读决策层
逻辑预读决策层负责判断预读时机、筛选待预读页面,并生成批量任务。
主要流程包括:
-
边界条件检测:判断当前扫描位置是否接近 Level-1 索引节点边界;
-
主动触发预读:满足触发条件后,主动读取下一个 Level-1 索引节点;
-
解析页面信息:从 Level-1 节点中提取全部子页面指针;
-
缓存过滤:过滤已经存在于 Buffer Pool 中的页面;
-
生成预读任务:输出批量待预读页面列表,并提交至异步 IO 层。
这一阶段完成了从“发现下一批数据”到“确定真正需要读取哪些数据”的全过程。
模块三:异步 IO 层
异步 IO 层基于 Gaia AIO 框架,通过远程 RPC 并行请求数据,充分利用云存储高带宽能力。
主要流程包括:
-
基于 Gaia AIO 框架发起远程 RPC 调用;
-
将批量预读请求下发至存储节点;
-
实现多 IO 并行处理;
-
利用云存储高带宽特性提升整体 IO 吞吐效率;
-
IO 完成后,通过回调机制通知 Buffer Pool 完成页面加载。
这一阶段的关键价值在于:
把原本串行执行的数据读取过程,改造成并行执行。
模块四:Buffer Pool 缓冲池
Buffer Pool 负责缓存预读页面,为用户查询提供高速访问能力。
主要流程包括:
-
接收异步 IO 层完成加载的数据页;
-
将页面写入 Buffer Pool;
-
标记页面为可用状态;
-
后续用户线程访问该页面时,直接命中缓存,无需等待磁盘 IO。
这一阶段相当于提前完成“数据备货”。
当真正查询到来时,数据已经准备完成,实现了从:
查询等数据
到:
数据等查询
的转变。
整个过程中,原本“扫描一页、等待一次”的串行模式,被重构为:
边扫描、边预取、边缓存。
在数据量越大、扫描范围越广的场景下,预读对 IO 等待的隐藏效果越明显,加速收益也会进一步放大。
二级索引回表预读:提前准备“下一批要回表的数据”
除了索引顺序扫描,二级索引回表也是复杂查询中非常典型的性能瓶颈。
二级索引回表预读适用于二级索引扫描后,需要回表查询聚簇索引获取完整行数据的场景。
例如:
-
按条件筛选 + 分页的列表查询
-
通过索引字段过滤后再获取完整数据的业务 SQL
-
二级索引命中大量记录后的批量回表查询
在这类场景下,查询通常需要频繁执行回表操作。随着命中记录数量增加,同步 IO 等待会逐渐成为主要性能瓶颈。
开启预读能力后,可实现数倍甚至数十倍性能提升。
它和索引逻辑预读有什么不同?
二级索引回表预读的核心思路是:
在主线程逐行执行回表的同时,提前扫描后续二级索引记录,预测后续回表目标页面,并批量异步预读至 Buffer Pool。
当主线程真正执行回表时,目标页面已经提前加载到内存中,从而消除同步 IO 等待。
与索引逻辑预读不同,两者关注对象存在明显差异:
td {white-space:nowrap;border:0.5pt solid #dee0e3;font-size:10pt;font-style:normal;font-weight:normal;vertical-align:middle;word-break:normal;word-wrap:normal;}| 类型 | 关注对象 |
| 索引逻辑预读 | 当前索引自身后续即将访问的叶子页 |
| 二级索引回表预读 | 聚簇索引中回表目标对应的叶子页 |
简单来说:
一个提前准备“下一批要扫描的数据”,另一个提前准备“下一批要回表的数据”。
二级索引回表预读如何工作?
二级索引查询异步预读整体涉及 4 个核心模块,其中异步 IO 层和 Buffer Pool 与索引逻辑预读保持一致。
核心差异主要在前两个阶段。
模块一:用户查询线程
用户查询线程负责执行用户查询,完成二级索引扫描与逐行回表,并负责触发预读条件检测。
关键流程包括:
-
顺序扫描二级索引记录;
-
按照 idx_rec1 → idx_rec2 → idx_rec3 → …… 持续推进;
-
根据二级索引记录中携带的主键信息,逐行执行回表;
-
通过主键查询聚簇索引数据;
-
在扫描过程中持续检测预读触发条件;
-
达到预设阈值后,向预读决策层发送触发信号。
这一阶段主要负责“正常查询”,同时承担预读触发入口角色。
模块二:预读决策与执行层
预读决策与执行层是二级索引异步预读的核心控制单元,负责完成预读流程调度、页面解析和目标筛选。
主要流程包括:
-
提前扫描二级索引记录;
-
批量收集后续多条记录对应的主键值;
-
根据主键在聚簇索引中执行 Level-1 搜索;
-
解析并定位目标叶子页页号;
-
进行缓存过滤,自动剔除已存在于 Buffer Pool 中的常驻页面;
-
汇总缺失页面列表;
-
整理为批量预读任务,提交至异步 IO 层。
这一阶段完成了从“记录”到“页面”的映射和筛选,决定真正需要预取哪些数据。
优化效果:典型场景最高提速 18 倍
为了验证预读加速效果,GaiaDB 在统一机器规格和资源配置下,对真实线上业务 SQL 进行了测试,并排除了热数据影响。
测试结果显示,预读加速在多个典型场景中均取得明显收益。
td {white-space:nowrap;border:0.5pt solid #dee0e3;font-size:10pt;font-style:normal;font-weight:normal;vertical-align:middle;word-break:normal;word-wrap:normal;}| 业务 SQL 场景 | 本地盘耗时 | 未开启预读耗时 | 开启预读耗时 | 加速效果 |
| 条件过滤 + 排序查询 | 0.26s | 0.76s | 0.05s | 相比未开启提升约 15 倍,性能为本地盘 5 倍 |
| 条件查询 + LIMIT 1 | 20.25s | 3.88s | 2.07s | 相比未开启提升约 1.8 倍,性能为本地盘 10 倍 |
| 分组统计查询 | 0.55s | 0.54s | 0.31s | 相比未开启提升约 1.8 倍 |
| 多条件过滤 + 排序分页 | 3.34s | 3.89s | 0.21s | 相比未开启提升约 18 倍,性能为本地盘 16 倍 |
| 时间区间查询 + 排序 | 4.10s | 12.61s | 0.72s | 相比未开启提升约 17 倍,性能为本地盘 5.5 倍 |
整体来看,预读加速能力相比原始版本实现了数倍至数十倍提升。
随着同步读取页面数量持续增加,性能优势还会进一步扩大。
线上真实案例:查询时间缩短至约 1/6
目前,预读加速能力已经在部分生产实例上线,效果显著。
案例一:审计业务查询当天上午 SQL 记录
业务场景:
查询当天上午所有实例的 SQL 审计 / 慢日志记录。
SELECT id, database_ip, database_name, table_name, sql
FROM tb_001
WHERE create_time BETWEEN '2025-05-01' AND '2025-05-01 12:00:00'
AND database_port > 3300;
优化效果:
td {white-space:nowrap;border:0.5pt solid #dee0e3;font-size:10pt;font-style:normal;font-weight:normal;vertical-align:middle;word-break:normal;word-wrap:normal;}| 上线前耗时 | 预读加速后耗时 | 提速倍数 |
| 31.71s | 5.59s | 5.7x |
案例二:跨天数据库操作审计记录回溯
业务场景:
拉取跨天,即 1.5 天的数据库操作审计记录,用于月初回溯排查。
SELECT id, database_ip, database_name, table_name, sql
FROM tb_001
WHERE create_time BETWEEN '2025-04-01' AND '2025-04-02 12:00:00'
AND database_port > 3300;
优化效果:
td {white-space:nowrap;border:0.5pt solid #dee0e3;font-size:10pt;font-style:normal;font-weight:normal;vertical-align:middle;word-break:normal;word-wrap:normal;}| 上线前耗时 | 预读加速后耗时 | 提速倍数 |
| 121.83s | 18.05s | 6.7x |
在上述场景中,预读加速将查询时间缩短至原来的约 1/6。
随着扫描数据量增加,异步并行预读对 IO 等待的隐藏效果将更加明显,加速收益也会持续放大。
预读加速的本质,是改变数据访问方式
预读加速并不是简单提高读取速度,而是改变了数据库的数据访问方式。
它通过:
-
预测
-
异步
-
并行
-
缓存
将原本串行等待的数据访问过程,升级为主动准备。
也就是说,GaiaDB 不再让查询被动等待数据,而是让数据提前到位。
从:
查询等数据
变成:
数据等查询。
在冷数据、大范围扫描、深分页以及复杂回表等典型场景下,GaiaDB 预读加速实现了 5~20 倍性能提升,进一步释放了云原生数据库在复杂查询场景下的性能潜力。
