COPY 中的自动分区
历史
COPY 中的自动分区是 Aster Data 在 PostgreSQL 9.0 代码库上开发的一个提议功能。它已提交并经过审查 (1 2),但尚未被接受到核心产品中,无论是在该版本还是任何其他版本中。
架构
自动层次结构加载代码已集成到 Postgres 9.0 (当时仍编号为 8.5) 的 COPY 命令代码中。可以通过在 COPY 命令中添加一个选项来启用它,如下所示
COPY parent_table FROM file (PARTITIONING);
我们假设 COPY 是在父表上执行的,并且子约束定义正确 (没有重叠约束)。如果启用了 PARTITIONING 并且表有子表,我们将扫描子表列表,并检查每个子表元组是否使用 Postgres 内部 ExecRelCheck 方法满足约束 (我们只是修改了代码以导出该方法)。元组使用 heap_insert 移动到第一个匹配的子表中,索引将更新,并执行触发器。如果元组与任何子表约束不匹配,则会生成错误。
错误处理机制定义了在 COPY 期间如何处理错误 (有关更多详细信息,请参阅 COPY 中的错误日志记录 )。
详细设计
路由代码替换了 copy.c @ void CopyFrom(CopyState cstate) 中 COPY 命令的标准插入代码。它集成在错误日志记录的第二个 TRY/CATCH 块中,以处理会违反所有子表约束的元组。
静态 bool route_tuple_to_child(Relation parent_relation, HeapTuple tuple) 方法将元组路由到第一个满足其约束的子表中。我们扫描 InheritsRelationId 以查找子表,并使用 bool check_constraints_and_insert_tuple(Relation child_table_relation, HeapTuple tuple) 方法逐个尝试它们。约束使用 PostgreSQL 内部 ExecRelCheck 方法检查,如果满足约束,则返回 true。如果这是正确的子表,我们将执行 BEFORE ROW 触发器,使用 heap_insert 插入元组,更新索引并触发 AFTER ROW 触发器。
请注意,如果任何触发器修改了用于路由的键的值,元组将保留在错误的子表中,从而导致进一步访问时出现不可预测的行为。
作为优化,route_tuple_to_child 方法保留了最近使用过的子表 oid 的 LRU 排序列表。此缓存表会系统地在最前面尝试,如果连续的元组必须进入同一个子表 (这是一种可能的加载场景),这将防止任何子表查找。如果出现不匹配,将执行新的子表查找。
我们修改了 PostgreSQL 的 GUC 部分,以启用其他会话级参数。我们正在添加以下配置选项
* copy_partitioning_cache_size: 0 is disable, any value greater than 0 defines how many child tables oids are kept in the LRU cache
假设、限制和错误输出
以下情况将生成错误
- 子表没有定义任何约束
- 元组不满足任何子表约束
- 在执行路由后修改元组值的 ROW 和 STATEMENT 触发器会导致不可预测的错误。
如果子表有重叠,元组将插入找到的第一个子表中 (无论它是缓存表还是查找中出现的第一个表)。
示例和单元测试
这是一个用作回归测试的示例,展示了功能。我们首先创建一个具有 3 个子表的母表
CREATE TABLE y2008 ( id int not null, date date not null, value int ); CREATE TABLE jan2008 ( CHECK ( date >= DATE '2008-01-01' AND date < DATE '2008-02-01' ) ) INHERITS (y2008); CREATE TABLE feb2008 ( CHECK ( date >= DATE '2008-02-01' AND date < DATE '2008-03-01' ) ) INHERITS (y2008); CREATE TABLE mar2008 ( CHECK ( date >= DATE '2008-03-01' AND date < DATE '2008-04-01' ) ) INHERITS (y2008);
我们将以下数据 (每个子表 1 行) 复制到母表中
copy_input.data content: 11 '2008-01-10' 11 12 '2008-02-15' 12 13 '2008-03-15' 13 21 '2008-01-10' 11 31 '2008-01-10' 11 41 '2008-01-10' 11 22 '2008-02-15' 12 23 '2008-03-15' 13 32 '2008-02-15' 12 33 '2008-03-15' 13 42 '2008-02-15' 12 43 '2008-03-15' 13
让我们尝试在未启用分区的条件下进行 COPY
COPY y2008 FROM '/root/workspace/Postgres-8.3.6-Aster/src/test/regress/data/copy_input.data';
所有行都插入母表中。
SELECT COUNT(*) FROM y2008; count ------- 12 (1 row) SELECT COUNT(*) FROM jan2008; count ------- 0 (1 row) SELECT COUNT(*) FROM feb2008; count ------- 0 (1 row) SELECT COUNT(*) FROM mar2008; count ------- 0 (1 row) DELETE FROM y2008;
我们现在启用自动层次结构加载,行将自动加载到相应的子表中。
COPY y2008 FROM '/root/workspace/Postgres-8.5-Aster/src/test/regress/data/copy_input.data' (PARTITIONING); SELECT * FROM y2008; id | date | value ----+------------+------- 11 | 01-10-2008 | 11 21 | 01-10-2008 | 11 31 | 01-10-2008 | 11 41 | 01-10-2008 | 11 12 | 02-15-2008 | 12 22 | 02-15-2008 | 12 32 | 02-15-2008 | 12 42 | 02-15-2008 | 12 13 | 03-15-2008 | 13 23 | 03-15-2008 | 13 33 | 03-15-2008 | 13 43 | 03-15-2008 | 13 (12 rows) SELECT * FROM jan2008; id | date | value ----+------------+------- 11 | 01-10-2008 | 11 21 | 01-10-2008 | 11 31 | 01-10-2008 | 11 41 | 01-10-2008 | 11 (4 rows) SELECT * FROM feb2008; id | date | value ----+------------+------- 12 | 02-15-2008 | 12 22 | 02-15-2008 | 12 32 | 02-15-2008 | 12 42 | 02-15-2008 | 12 (4 rows) SELECT * FROM mar2008; id | date | value ----+------------+------- 13 | 03-15-2008 | 13 23 | 03-15-2008 | 13 33 | 03-15-2008 | 13 43 | 03-15-2008 | 13 (4 rows)
可以使用以下方法调整缓存大小
set copy_partitioning_cache_size = 3;
现在重复 COPY 命令将更快
COPY y2008 FROM '/root/workspace/Postgres-8.5-Aster/src/test/regress/data/copy_input.data' (PARTITIONING);
最后,我们删除表。
DROP TABLE y2008 CASCADE; NOTICE: drop cascades to table mar2008 NOTICE: drop cascades to constraint mar2008_date_check on table mar2008 NOTICE: drop cascades to table feb2008 NOTICE: drop cascades to constraint feb2008_date_check on table feb2008 NOTICE: drop cascades to table jan2008 NOTICE: drop cascades to constraint jan2008_date_check on table jan2008