PostgreSQL技能内幕17:PG分区表

[复制链接]
发表于 2025-12-31 05:23:48 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
0.简介

本文告急先容PG中分区表的概念,产生分区表技能的缘故起因,利用方式和其内部实现原理,旨在能对PG分区表技能有一个体系的阐明。
1.概念先容

分区表是数据库用于管理大量数据的一种技能,它答应将一个大表分割成多个小表,这些小表在物理上是独立的,但在逻辑上作为一个团体被查询和更新。分区表的告急上风在于进步查询性能,特殊是当查询会集在少数几个分区时。别的,分区表还可以简化数据的批量删除和加载,以及将不常用的数据迁徙到本钱较低的存储介质上实现冷热分离。

1)主表/父表/Master Table:该表是创建子表的模板。它是一个正常的平凡表,但正常环境下它并不储存任何数据。
2)子表/分区表/Child Table/Partition Table:这些表继承并属于一个主表。子表中存储全部的数据。主表与分区表属于一对多的关系,也就是说,一个主表包罗多个分区表,而一个分区表只附属于一个主表
2.分区表技能产生的配景

在利用数据库过程中,随着时间的推移,每张表数据量会不停增长,造成查询速率越来越慢,在分区表之前有许多查询的技能去优化它,比如添加特殊的索引,将磁盘分区(把日记文件放到单独的磁盘分区),调解参数等等。这些优化技能都能对查询性能做出或多或少的提拔,但其并没有对于表特点以及局部性的原理举行公道应用,由于对于许多应用来说,许多汗青数据对于查询大概并没有太多用处,大概是某一列是特定值时是更为关系的数据,如果可以大概将不常用数据举行隐蔽,就能大大进步查询速率,分区表就是为相识决这个标题而产生的。比如可以按照时间作为分区键举行分区将新老数据分离。
3.分区范例及利用方式

PG 10以后支持三种分区,以下都利用主流的利用方式声明式分区(尚有表继承)举行阐明:
1)范围(Range)分区
  1. CREATE TABLE students (grade INTEGER) PARTITION BY RANGE(grade);
  2. CREATE TABLE stu_fail PARTITION OF students FOR VALUES FROM (MINVALUE) TO (60);
  3. CREATE TABLE stu_pass PARTITION OF students FOR VALUES FROM (60) TO (MAXVALUE);
  4. \d+  students
  5.                                  Table "public.students"
  6. Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description
  7. --------+---------+-----------+----------+---------+---------+--------------+-------------
  8. grade  | integer |           |          |         | plain   |              |
  9. Partition key: RANGE (grade)
  10. Partitions: stu_fail FOR VALUES FROM (MINVALUE) TO (60),
  11.             stu_pass FOR VALUES FROM (60) TO (MAXVALUE)
  12. \d+ stu_fail
  13.                                  Table "public.stu_fail"
  14. Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description
  15. --------+---------+-----------+----------+---------+---------+--------------+-------------
  16. grade  | integer |           |          |         | plain   |              |
  17. Partition of: students FOR VALUES FROM (MINVALUE) TO (60)
  18. Partition constraint: ((grade IS NOT NULL) AND (grade < 60))
复制代码
  1. 可以看出,其中最大值是小于关系,不是小于等于关系。
复制代码
2)列表(List)分区
列表分区明确指定根据某字段的某个具体值举行分区,默认分区(可选值)生存不属于任何指定分区的列表值。
  1. CREATE TABLE students (status character varying(30)) PARTITION BY LIST(status);
  2. CREATE TABLE stu_active PARTITION OF students FOR VALUES IN ('ACTIVE');
  3. CREATE TABLE stu_exp PARTITION OF students FOR VALUES IN ('EXPIRED');
  4. CREATE TABLE stu_others PARTITION OF students DEFAULT;
  5. \d+  students
  6.                                          Table "public.students"
  7. Column |         Type          | Collation | Nullable | Default | Storage  | Stats target | Description
  8. --------+-----------------------+-----------+----------+---------+----------+--------------+-------------
  9. status | character varying(30) |           |          |         | extended |              |
  10. Partition key: LIST (status)
  11. Partitions: stu_active FOR VALUES IN ('ACTIVE'),
  12.             stu_exp FOR VALUES IN ('EXPIRED'),
  13.             stu_others DEFAULT
  14. \d+  stu_others;
  15.                                         Table "public.stu_others"
  16. Column |         Type          | Collation | Nullable | Default | Storage  | Stats target | Description
  17. --------+-----------------------+-----------+----------+---------+----------+--------------+-------------
  18. status | character varying(30) |           |          |         | extended |              |
  19. Partition of: students DEFAULT
  20. Partition constraint: (NOT ((status IS NOT NULL) AND ((status)::text = ANY (ARRAY['ACTIVE'::character varying(30), 'EXPIRED'::character varying(30)]))))
复制代码
3)哈希(Hash)分区
通过对每个分区利用取模和余数来创建hash分区,modulus指定了对N取模,而remainder指定了除完后的余数。
  1. CREATE TABLE students (id INTEGER) PARTITION BY HASH(id);
  2. CREATE TABLE stu_part1 PARTITION OF students FOR VALUES WITH (modulus 3, remainder 0);
  3. CREATE TABLE stu_part2 PARTITION OF students FOR VALUES WITH (modulus 3, remainder 1);
  4. CREATE TABLE stu_part3 PARTITION OF students FOR VALUES WITH (modulus 3, remainder 2);
  5. \d+ students;
  6.                                  Table "public.students"
  7. Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description
  8. --------+---------+-----------+----------+---------+---------+--------------+-------------
  9. id     | integer |           |          |         | plain   |              |
  10. Partition key: HASH (id)
  11. Partitions: stu_part1 FOR VALUES WITH (modulus 3, remainder 0),
  12.             stu_part2 FOR VALUES WITH (modulus 3, remainder 1),
  13.             stu_part3 FOR VALUES WITH (modulus 3, remainder 2)
  14. \d+ stu_part1;
  15.                                  Table "public.stu_part1"
  16. Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description
  17. --------+---------+-----------+----------+---------+---------+--------------+-------------
  18. id     | integer |           |          |         | plain   |              |
  19. Partition of: students FOR VALUES WITH (modulus 3, remainder 0)
  20. Partition constraint: satisfies_hash_partition('16439'::oid, 3, 0, id)
复制代码
PG分区还支持创建子分区:LIST-LIST,LIST-RANGE,LIST-HASH,RANGE-RANGE,RANGE-LIST,RANGE-HASH,HASH-HASH,HASH-LIST和HASH-RANGE;以及宁静凡表之间相互转换,DETACH PARTITION可以将分区表转换为平凡表,而attach partition可以将平凡表附加到分区表上。
4.实现原理

4.1 分区表创建

分区表创建相对简朴,对PG来说现实是一张逻辑表对应多张物理表,下面简朴看创建时其分区表相干的调用流程。
  1. --> transformPartitionBound  
  2.                     --> RelationGetPartitionKey
  3.                     --> get_partition_strategy
  4.                     --> transformPartitionBoundValue
  5.                     --> transformPartitionRangeBounds
  6.                         --> validateInfiniteBounds
  7.                 --> check_new_partition_bound
  8.                 --> StorePartitionBound // Update pg_class tuple of rel to store the partition bound and set relispartition to true
  9.                 --> StoreCatalogInheritance // 向系统表pg_inherits插入信息
  10.                 // 处理stmt->partspec
  11.                 --> transformPartitionSpec
  12.                 --> ComputePartitionAttrs
  13.                 --> StorePartitionKey // 向pg_partitioned_table中插入分区键等信息
复制代码
4.2 分区表查询

分区表查询是要根据条件查询肯定命量的子表然后举行返回,其告急分为三步:
1)辨认分区表并找到全部的分区子表
  1. /*
  2. * expand_inherited_tables
  3. *    Expand each rangetable entry that represents an inheritance set
  4. *    into an "append relation".  At the conclusion of this process,
  5. *    the "inh" flag is set in all and only those RTEs that are append
  6. *    relation parents.
  7. */
  8. void
  9. expand_inherited_tables(PlannerInfo *root)
  10. {
  11.   Index    nrtes;
  12.   Index    rti;
  13.   ListCell   *rl;
  14.   /*
  15.    * expand_inherited_rtentry may add RTEs to parse->rtable. The function is
  16.    * expected to recursively handle any RTEs that it creates with inh=true.
  17.    * So just scan as far as the original end of the rtable list.
  18.    */
  19.   nrtes = list_length(root->parse->rtable);
  20.   rl = list_head(root->parse->rtable);
  21.   for (rti = 1; rti <= nrtes; rti++)
  22.   {
  23.     RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl);
  24.     expand_inherited_rtentry(root, rte, rti);
  25.     rl = lnext(rl);
  26.   }
  27. }
复制代码
2)根据束缚条件辨认须要查询的分区,也就是分区裁剪,只读取须要的分区;
  1. prune_append_rel_partitions
  2. *    Process rel's baserestrictinfo and make use of quals which can be
  3. *    evaluated during query planning in order to determine the minimum set
  4. *    of partitions which must be scanned to satisfy these quals.  Returns
  5. *    the matching partitions in the form of a Relids set containing the
  6. *    partitions' RT indexes.
  7. *
  8. * Callers must ensure that 'rel' is a partitioned table.
  9. */
  10. Relids
  11. prune_append_rel_partitions(RelOptInfo *rel)
  12. {
  13.   Relids    result;
  14.   List     *clauses = rel->baserestrictinfo;
  15.   List     *pruning_steps;
  16.   GeneratePruningStepsContext gcontext;
  17.   PartitionPruneContext context;
  18.   Bitmapset  *partindexes;
  19.   int      i;
  20.   Assert(clauses != NIL);
  21.   Assert(rel->part_scheme != NULL);
  22.   /* If there are no partitions, return the empty set */
  23.   if (rel->nparts == 0)
  24.     return NULL;
  25.   /*
  26.    * Process clauses to extract pruning steps that are usable at plan time.
  27.    * If the clauses are found to be contradictory, we can return the empty
  28.    * set.
  29.    */
  30.   gen_partprune_steps(rel, clauses, PARTTARGET_PLANNER,
  31.             &gcontext);
  32.   if (gcontext.contradictory)
  33.     return NULL;
  34.   pruning_steps = gcontext.steps;
  35.   /* Set up PartitionPruneContext */
  36.   context.strategy = rel->part_scheme->strategy;
  37.   context.partnatts = rel->part_scheme->partnatts;
  38.   context.nparts = rel->nparts;
  39.   context.boundinfo = rel->boundinfo;
  40.   context.partcollation = rel->part_scheme->partcollation;
  41.   context.partsupfunc = rel->part_scheme->partsupfunc;
  42.   context.stepcmpfuncs = (FmgrInfo *) palloc0(sizeof(FmgrInfo) *
  43.                         context.partnatts *
  44.                         list_length(pruning_steps));
  45.   context.ppccontext = CurrentMemoryContext;
  46.   /* These are not valid when being called from the planner */
  47.   context.partrel = NULL;
  48.   context.planstate = NULL;
  49.   context.exprstates = NULL;
  50.   /* Actual pruning happens here. */
  51.   partindexes = get_matching_partitions(&context, pruning_steps);
  52.   /* Add selected partitions' RT indexes to result. */
  53.   i = -1;
  54.   result = NULL;
  55.   while ((i = bms_next_member(partindexes, i)) >= 0)
  56.     result = bms_add_member(result, rel->part_rels[i]->relid);
  57.   return result;
  58. }
复制代码
3)对效果集实行APPEND,作为最闭幕果输出,这和其他表append利用同等,利用ExecInitAppend和ExecAppend函数。
  1. /* ----------------------------------------------------------------
  2. *     ExecAppend
  3. *
  4. *    Handles iteration over multiple subplans.
  5. * ----------------------------------------------------------------
  6. */
  7. static TupleTableSlot *
  8. ExecAppend(PlanState *pstate)
  9. {
  10.   AppendState *node = castNode(AppendState, pstate);
  11.   if (node->as_whichplan < 0)
  12.   {
  13.     /*
  14.      * If no subplan has been chosen, we must choose one before
  15.      * proceeding.
  16.      */
  17.     if (node->as_whichplan == INVALID_SUBPLAN_INDEX &&
  18.       !node->choose_next_subplan(node))
  19.       return ExecClearTuple(node->ps.ps_ResultTupleSlot);
  20.     /* Nothing to do if there are no matching subplans */
  21.     else if (node->as_whichplan == NO_MATCHING_SUBPLANS)
  22.       return ExecClearTuple(node->ps.ps_ResultTupleSlot);
  23.   }
  24.   for (;;)
  25.   {
  26.     PlanState  *subnode;
  27.     TupleTableSlot *result;
  28.     CHECK_FOR_INTERRUPTS();
  29.     /*
  30.      * figure out which subplan we are currently processing
  31.      */
  32.     Assert(node->as_whichplan >= 0 && node->as_whichplan < node->as_nplans);
  33.     subnode = node->appendplans[node->as_whichplan];
  34.     /*
  35.      * get a tuple from the subplan
  36.      */
  37.     result = ExecProcNode(subnode);
  38.     if (!TupIsNull(result))
  39.     {
  40.       /*
  41.        * If the subplan gave us something then return it as-is. We do
  42.        * NOT make use of the result slot that was set up in
  43.        * ExecInitAppend; there's no need for it.
  44.        */
  45.       return result;
  46.     }
  47.     /* choose new subplan; if none, we're done */
  48.     if (!node->choose_next_subplan(node))
  49.       return ExecClearTuple(node->ps.ps_ResultTupleSlot);
  50.   }
  51. }
复制代码
4.3 分区表写入

分区表写入分为两个阶段,一个是查找到要写入的分区,然后就是正常去做写入,下面来看查找分区的函数。
  1. /*
  2. * ExecPrepareTupleRouting --- prepare for routing one tuple
  3. *
  4. * Determine the partition in which the tuple in slot is to be inserted,
  5. * and modify mtstate and estate to prepare for it.
  6. *
  7. * Caller must revert the estate changes after executing the insertion!
  8. * In mtstate, transition capture changes may also need to be reverted.
  9. *
  10. * Returns a slot holding the tuple of the partition rowtype.
  11. */
  12. static TupleTableSlot *
  13. ExecPrepareTupleRouting(ModifyTableState *mtstate,
  14.             EState *estate,
  15.             PartitionTupleRouting *proute,
  16.             ResultRelInfo *targetRelInfo,
  17.             TupleTableSlot *slot)
  18. {
  19.   ModifyTable *node;
  20.   int      partidx;
  21.   ResultRelInfo *partrel;
  22.   HeapTuple  tuple;
  23.   /*
  24.    * Determine the target partition.  If ExecFindPartition does not find a
  25.    * partition after all, it doesn't return here; otherwise, the returned
  26.    * value is to be used as an index into the arrays for the ResultRelInfo
  27.    * and TupleConversionMap for the partition.
  28.    */
  29.   partidx = ExecFindPartition(targetRelInfo,
  30.                 proute->partition_dispatch_info,
  31.                 slot,
  32.                 estate);
  33.   Assert(partidx >= 0 && partidx < proute->num_partitions);
  34.   /*
  35.    * Get the ResultRelInfo corresponding to the selected partition; if not
  36.    * yet there, initialize it.
  37.    */
  38.   partrel = proute->partitions[partidx];
  39.   if (partrel == NULL)
  40.     partrel = ExecInitPartitionInfo(mtstate, targetRelInfo,
  41.                     proute, estate,
  42.                     partidx);
  43.   /*
  44.    * Check whether the partition is routable if we didn't yet
  45.    *
  46.    * Note: an UPDATE of a partition key invokes an INSERT that moves the
  47.    * tuple to a new partition.  This check would be applied to a subplan
  48.    * partition of such an UPDATE that is chosen as the partition to route
  49.    * the tuple to.  The reason we do this check here rather than in
  50.    * ExecSetupPartitionTupleRouting is to avoid aborting such an UPDATE
  51.    * unnecessarily due to non-routable subplan partitions that may not be
  52.    * chosen for update tuple movement after all.
  53.    */
  54.   if (!partrel->ri_PartitionReadyForRouting)
  55.   {
  56.     /* Verify the partition is a valid target for INSERT. */
  57.     CheckValidResultRel(partrel, CMD_INSERT);
  58.     /* Set up information needed for routing tuples to the partition. */
  59.     ExecInitRoutingInfo(mtstate, estate, proute, partrel, partidx);
  60.   }
  61.   /*
  62.    * Make it look like we are inserting into the partition.
  63.    */
  64.   estate->es_result_relation_info = partrel;
  65.   /* Get the heap tuple out of the given slot. */
  66.   tuple = ExecMaterializeSlot(slot);
  67.   /*
  68.    * If we're capturing transition tuples, we might need to convert from the
  69.    * partition rowtype to parent rowtype.
  70.    */
  71.   if (mtstate->mt_transition_capture != NULL)
  72.   {
  73.     if (partrel->ri_TrigDesc &&
  74.       partrel->ri_TrigDesc->trig_insert_before_row)
  75.     {
  76.       /*
  77.        * If there are any BEFORE triggers on the partition, we'll have
  78.        * to be ready to convert their result back to tuplestore format.
  79.        */
  80.       mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
  81.       mtstate->mt_transition_capture->tcs_map =
  82.         TupConvMapForLeaf(proute, targetRelInfo, partidx);
  83.     }
  84.     else
  85.     {
  86.       /*
  87.        * Otherwise, just remember the original unconverted tuple, to
  88.        * avoid a needless round trip conversion.
  89.        */
  90.       mtstate->mt_transition_capture->tcs_original_insert_tuple = tuple;
  91.       mtstate->mt_transition_capture->tcs_map = NULL;
  92.     }
  93.   }
  94.   if (mtstate->mt_oc_transition_capture != NULL)
  95.   {
  96.     mtstate->mt_oc_transition_capture->tcs_map =
  97.       TupConvMapForLeaf(proute, targetRelInfo, partidx);
  98.   }
  99.   /*
  100.    * Convert the tuple, if necessary.
  101.    */
  102.   ConvertPartitionTupleSlot(proute->parent_child_tupconv_maps[partidx],
  103.                 tuple,
  104.                 proute->partition_tuple_slot,
  105.                 &slot);
  106.   /* Initialize information needed to handle ON CONFLICT DO UPDATE. */
  107.   Assert(mtstate != NULL);
  108.   node = (ModifyTable *) mtstate->ps.plan;
  109.   if (node->onConflictAction == ONCONFLICT_UPDATE)
  110.   {
  111.     Assert(mtstate->mt_existing != NULL);
  112.     ExecSetSlotDescriptor(mtstate->mt_existing,
  113.                 RelationGetDescr(partrel->ri_RelationDesc));
  114.     Assert(mtstate->mt_conflproj != NULL);
  115.     ExecSetSlotDescriptor(mtstate->mt_conflproj,
  116.                 partrel->ri_onConflict->oc_ProjTupdesc);
  117.   }
  118.   return slot;
  119. }
复制代码
4.4 分区表删除

分区表的删除即为先删除其分区,然后团体删除。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表