湖仓 – 第4阶段 扫描与查询
内容提要
湖仓查询阶段中,SELECT 查询先解析表与快照,获取该快照的数据文件,再利用列统计信息裁剪文件,跳过不可能匹配的文件。存活文件随后过滤删除行、读取 Parquet、按谓词筛选并投影列。统计裁剪、删除过滤、列投影和缺失列补 nil 共同实现高效扫描与模式演进。
延伸解读
统计裁剪:把 WHERE 变成文件级过滤
文章强调扫描引擎不会盲目读取所有数据文件,而是先查元数据目录,用 WHERE 条件对比每个文件的列统计信息,跳过不可能匹配的文件。例如 price > 100 时,若某文件 price 的 max 为 50,则直接跳过。这种裁剪本质上是 SQL 查询,依赖数据库目录,能大幅减少实际读取的 Parquet 文件数量。
删除过滤:数据文件不改,用位置映射跳过
删除行时数据文件保持不变,而是由删除文件记录需要跳过的行位置。扫描时将这些位置加载到 map 中,实现 O(1) 查找,逐行判断是否跳过。这样既保留了原始文件,又能在查询时正确排除已删除行,是湖仓支持删除操作的关键机制。
列投影:只读需要的列,减少 I/O
列投影分两层:Parquet 读取时只加载查询涉及的列,未请求的列块不会从磁盘读取;过滤后还会丢弃仅用于 WHERE 的列。例如 SELECT name, amount FROM orders WHERE price > 100,实际只读 name、amount、price,id、region、status 完全跳过。这能显著降低 I/O 和内存开销。
模式演进:缺失列补 nil 实现读时兼容
当读取旧 Parquet 文件时,若文件缺少后来新增的列,扫描会用 nil 填充。这样 ADD COLUMN 无需重写历史数据,调用方无论行来自哪个文件,都能看到一致的模式。文章指出这个简单函数完成了读取时的模式演进协调,是湖仓模式灵活性的重要支撑。
Q&A
湖仓查询阶段中,SELECT 查询是如何利用统计信息跳过不相关文件的?
扫描引擎先查询元数据目录,找到当前快照下属于该表的数据文件,然后将 WHERE 子句中的谓词与每个文件的列统计信息(如最小值、最大值)进行比较。如果根据统计信息可以确定文件不可能包含匹配行,则跳过该文件。例如,对于谓词 price > 100,若某文件 price 列的最大值 <= 100,则跳过该文件。
湖仓扫描时如何处理已删除的行?
删除行时不会修改原始数据文件,而是通过删除文件记录需要跳过的行位置。扫描引擎会加载这些位置到一个 map 中,在读取数据文件后,根据行位置过滤掉已删除的行,实现 O(1) 查找。
湖仓查询中列投影是如何工作的?
列投影发生在两个层面:Parquet 级别投影,打开 Parquet 文件时只读取查询实际使用的列,未请求的列块不会从磁盘读取;输出投影,某些列可能用于过滤但不在输出中,过滤后这些额外列会被丢弃。例如 SELECT name, amount FROM orders WHERE price > 100,Parquet 投影读取 name、amount、price,过滤后只输出 name、amount。
湖仓如何在不重写数据的情况下支持模式演进(如新增列)?
当读取旧 Parquet 文件时,如果文件中缺少后来新增的列,扫描引擎会用 nil 填充这些缺失的列。这样,无论行来自哪个文件,调用者都能看到一致的 schema,从而实现 ADD COLUMN 而无需重写数据。
湖仓扫描中谓词模型是怎样的?
谓词是简单的列比较,支持 =、<、>、<=、>=、!= 操作符,多个谓词之间是 AND 关系。行级评估时,会检查每个谓词,使用类型感知的比较,如果任一谓词不满足则行被过滤。
湖仓扫描的完整流程包括哪些步骤?
完整流程为:1. 解析表和快照;2. 获取快照过滤后的数据文件;3. 对每个文件使用统计信息进行裁剪;4. 对存活文件加载删除位置、读取 Parquet(列投影)、应用行级谓词、投影输出列;5. 返回合并结果。