PostgreSQL执行器之Materialize
1. Materialize 简述
Materialize 是 PostgreSQL 执行计划的一个组成部分,用于生成一个可复用的临时数据集。
来看一个例子,表 t1 与 t2 关联查询,生成的执行计划如下:
postgres=# select * from t1; c1 | c2 | c3 ----+----+---- 1 | 1 | 1 2 | 2 | 2 (2 rows) postgres=# select * from t2; c10 | c20 | c30 -----+-----+----- 3 | 3 | 3 6 | 6 | 6 (2 rows) postgres=# explain select * from t1,t2 where t1.c1=t2.c10; QUERY PLAN ------------------------------------------------------------------- Nested Loop (cost=0.00..62489.90 rows=20808 width=24) Join Filter: (t1.c1 = t2.c10) -> Seq Scan on t1 (cost=0.00..30.40 rows=2040 width=12) -> Materialize (cost=0.00..40.60 rows=2040 width=12) -> Seq Scan on t2 (cost=0.00..30.40 rows=2040 width=12) (5 rows)
t1 与 t2 关联查询,最上层的执行计划节点是 Nested Loop,实际上就是两层循环嵌套,外层循环遍历 t1 表中的记录,针对 t1 表中的每一条记录,内部循环遍历 t2 表中的记录,然后判断这两条记录是否满足 join 过滤条件(t1.c1=t2.c10)。
上述过程中对 t2 表的扫描只需要执行一次,将其结果集缓存起来,以便在外层循环 t1 表推进到下一条记录时,直接使用 t2 表的缓存结果集即可,不用再次扫描 t2 表。对 t2 表的结果集缓存的操作就是通过 Materialize 方式实现。
2. Materialize 核心结构体与函数
结构体:
MaterialState 对应 Materialize 节点的执行状态,保存 Materialize 执行过程中的各种状态信息。
MaterialState.eof_underlying 表示其下层执行节点是否已经到达结尾,假设其下层执行节点是一个 SeqScan,表示该表扫描是否已扫描结束。
MaterialState.tuplestorestate 表示数据集临时存储状态,可以存储在内存中,如果超过一定大小,则会存储在文件中。
MaterialState.ss.ps.lefttree 指向其子结点,以上述例子来说,其子结点为 t2 表的 Seq Scan
函数:
- ExecInitMaterial(),初始化 Materialize 及其子节点
- ExecMaterial(),执行主函数
- ExecReScanMaterial(),设置状态用于重新读取 Materialize 缓存的数据集
- ExecEndMaterial(),结束 Materialize 以及其子结点的执行,释放 tuplestorestate,释放其他资源。
3. Materialize 实现逻辑
ExecMaterial() 是执行主函数,可能会被上层函数多次调用。当该函数第一次被调用时,tuplestorestate 为空,需要调用 tuplestore_begin_heap() 函数为 tuplestorestate 变量做初始化操作。
局部变量 eof_tuplestore 用来标示 tuplestorestate 存储的数据集是否已读取结束,当 tuplestorestate 为 true 并且 eof_underlying 为 false 时,即表示 tuplestorestate 已读取结束并且下层的结点没有读取结束,则会从下层结点继续读取数据。
下层结点读取出来的数据会放入 tuplestorestate 中, 以便外层循环的下一次循环执行时能够复用 tuplestorestate 中的数据。当下层结点读取的数据为空时,表示内层循环执行结束,设置 eof_underlying 为 true,返回 NULL。
当外层循环第二次,第三次等后续执行时,内层循环可以直接复用 tuplestorestate 中的数据集,不用再次执行下层结点(比如扫描表数据),提升性能。