这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

分区表

数据分区定义为表属性,并在该表的所有投影上实施。在所有加载、刷新和恢复操作中,Vertica Tuple Mover 会将数据自动分区到不同的 ROS 容器中。每个 ROS 容器包含单个分区或分区组的数据;根据空间要求,一个分区或分区组可以跨多个 ROS 容器。

例如,按时间段对数据进行分区很常见。如果表包含数十年的数据,则可以按年份对其进行分区。如果表仅包含一年的数据,则可以按月份对其进行分区。

数据的逻辑划分可以显著改善查询执行。例如,如果对表的 partition 子句中的列查询表,则查询优化器可以快速隔离相关的 ROS 容器(请参阅分区修剪)。

分区还可以促进 DML 操作。例如,给定一个按月份分区的表,便可以在新月份开始时删除最旧月份的所有数据。在这种情况下,Vertica 可以轻松识别存储要删除的分区数据的 ROS 容器。有关详细信息,请参阅管理分区

1 - 定义分区

可以为新表和现有表指定分区:

1.1 - 对新表进行分区

使用 CREATE TABLE 对新表进行分区,由 PARTITION BY 子句指定:

CREATE TABLE table-name... PARTITION BY partition‑expression [ GROUP BY group‑expression ] [ REORGANIZE ];

以下语句创建 store_orders 表并将数据加载到其中。CREATE TABLE 语句包含一个简单的分区子句,指定按年份对数据进行分区:

=> CREATE TABLE public.store_orders
(
    order_no int,
    order_date timestamp NOT NULL,
    shipper varchar(20),
    ship_date date
)
UNSEGMENTED ALL NODES
PARTITION BY YEAR(order_date);
CREATE TABLE
=> COPY store_orders FROM '/home/dbadmin/export_store_orders_data.txt';
41834

当 COPY 将新表数据加载到 ROS 存储中时,Tuple Mover 会将每年的订单划分为单独的分区,且将这些分区合并到 ROS 容器中,以执行表的分区子句。

在这种情况下,Tuple Mover 为加载的数据创建四个分区键(2017、2016、2015 和 2014),并将数据相应划分到单独的 ROS 容器中:

=> SELECT dump_table_partition_keys('store_orders');
... Partition keys on node v_vmart_node0001
  Projection 'store_orders_super'
   Storage [ROS container]
     No of partition keys: 1
     Partition keys: 2017
   Storage [ROS container]
     No of partition keys: 1
     Partition keys: 2016
   Storage [ROS container]
     No of partition keys: 1
     Partition keys: 2015
   Storage [ROS container]
     No of partition keys: 1
     Partition keys: 2014

 Partition keys on node v_vmart_node0002
  Projection 'store_orders_super'
   Storage [ROS container]
     No of partition keys: 1
     Partition keys: 2017
...

(1 row)

当新数据加载到 store_orders 中时,Tuple Mover 会将其合并到适当的分区中,以此根据需要创建新的分区键。

1.2 - 对现有表数据进行分区

使用 ALTER TABLE 对现有表进行分区或重新分区,由 PARTITION BY 子句指定:

ALTER TABLE table-name PARTITION BY partition‑expression [ GROUP BY group‑expression ] [ REORGANIZE ];

例如,可以对前面定义store_orders 表进行重新分区。下面的 ALTER TABLE 将每年的所有 store_orders 数据划分为每月分区,每个分区键标识订单日期的年份和月份:

=> ALTER TABLE store_orders
     PARTITION BY EXTRACT(YEAR FROM order_date)*100 + EXTRACT(MONTH FROM order_date)
         GROUP BY EXTRACT(YEAR from order_date)*100 + EXTRACT(MONTH FROM order_date);
NOTICE 8364:  The new partitioning scheme will produce partitions in 42 physical storage containers per projection
WARNING 6100:  Using PARTITION expression that returns a Numeric value
HINT:  This PARTITION expression may cause too many data partitions.  Use of an expression that returns a more accurate value, such as a regular VARCHAR or INT, is encouraged
WARNING 4493:  Queries using table "store_orders" may not perform optimally since the data may not be repartitioned in accordance with the new partition expression
HINT:  Use "ALTER TABLE public.store_orders REORGANIZE;" to repartition the data

执行此语句后,Vertica 会删除现有的分区键。但是,partition 子句会省略 REORGANIZE,以便现有数据仍然根据前一个 partition 子句进行存储。这可能会使表分区处于不一致状态,并对查询性能、DROP_PARTITIONS 和节点恢复产生负面影响。在这种情况下,必须通过以下方式之一显式请求 Vertica 将现有数据重新组织到新分区中:

  • 发出 ALTER TABLE...REORGANIZE:

    ALTER TABLE table-name REORGANIZE;
    
  • 调用 Vertica 元函数 PARTITION_TABLE

例如:

=> ALTER TABLE store_orders REORGANIZE;
NOTICE 4785:  Started background repartition table task
ALTER TABLE

ALTER TABLE...REORGANIZE 和 PARTITION_TABLE 的运行方式相同:两者都会拆分分区键与新 partition 子句不相符的任何 ROS 容器。在执行下一次合并时,Tuple Mover 会将分区合并到相应的 ROS 容器中。

1.3 - 分区分组

分区组将分区合并为逻辑子集,从而最大限度减少 ROS 存储的使用。减少存储分区数据的 ROS 容器数有助于促进 DELETEUPDATE 等 DML 操作,并避免 ROS 推回。例如,可以按年份对日期分区进行分组。通过这样做,Tuple Mover 会为每个年份组分配 ROS 容器,并相应地将各个分区合并到这些 ROS 容器中。

创建分区组

可以通过使用 GROUP BY 子句限定 PARTITION BY 子句来创建分区组:

ALTER TABLE table-name PARTITION BY partition‑expression [ GROUP BY group‑expression ]

GROUP BY 子句指定如何将分区键合并到组中,其中每个组由唯一的分区组键标识。例如,以下 ALTER TABLE 语句指定按订单日期对 store_orders 表进行重新分区(如对新表进行分区中所示),按年份对分区键进行分组。组表达式 DATE_TRUNC('year', (order_date)::DATE) 使用分区表达式 order_date::DATE 生成分区组键:

=> ALTER TABLE store_orders
     PARTITION BY order_date::DATE GROUP BY DATE_TRUNC('year', (order_date)::DATE) REORGANIZE;
NOTICE 8364:  The new partitioning scheme will produce partitions in 4 physical storage containers per projection
NOTICE 4785:  Started background repartition table task

在这种情况下,order_date 列的日期跨越四年。Tuple Mover 会创建四个分区组键,并相应地将 store_orders 分区合并到组特定的 ROS 存储容器中:


=> SELECT DUMP_TABLE_PARTITION_KEYS('store_orders');
 ...
 Partition keys on node v_vmart_node0001
  Projection 'store_orders_super'
   Storage [ROS container]
     No of partition keys: 173
     Partition keys: 2017-01-02 2017-01-03 2017-01-04 ... 2017-09-25 2017-09-26 2017-09-27
   Storage [ROS container]
     No of partition keys: 212
     Partition keys: 2016-01-01 2016-01-04 2016-01-05 ... 2016-11-23 2016-11-24 2016-11-25
   Storage [ROS container]
     No of partition keys: 213
     Partition keys: 2015-01-01 2015-01-02 2015-01-05 ... 2015-11-23 2015-11-24 2015-11-25
2015-11-26 2015-11-27
   Storage [ROS container]
     No of partition keys: 211
     Partition keys: 2014-01-01 2014-01-02 2014-01-03 ... 2014-11-25 2014-11-26 2014-11-27
  Projection 'store_orders_super'
   Storage [ROS container]
     No of partition keys: 173
...

管理组内的分区

可以使用各种分区管理函数(例如 DROP_PARTITIONSMOVE_PARTITIONS_TO_TABLE),来定位给定分区组内或跨多个分区组的订单日期范围。在前面的示例中,每个组都包含给定年份内不同日期的分区键。可以使用 DROP_PARTITIONS 删除跨越两年(2014 年和 2015 年)的订单日期:

=> SELECT DROP_PARTITIONS('store_orders', '2014-05-30', '2015-01-15', 'true');

2 - 分层分区

元函数 CALENDAR_HIERARCHY_DAY 利用分区分组。您可以将此函数指定为分区 GROUP BY 表达式。CALENDAR_HIERARCHY_DAY 将表的日期分区组织成组的层次结构:最早的日期分区按年分组,较新的分区按月分组,最近的日期分区保持未分组状态。分组动态进行:随着最近的数据老化,Tuple Mover 将它们的分区合并到月份组中,并最终合并到年份组中。

管理带时间戳的数据

分区合并策略对于带时间戳的数据管理尤其重要,因为分区数量可能会迅速升级并面临 ROS 推回风险。例如,以下语句创建 store_orders 表并将数据加载到其中。CREATE TABLE 语句包含一个简单的 partition 子句,它指定按日期对数据进行分区:

=> DROP TABLE IF EXISTS public.store_orders CASCADE;
=> CREATE TABLE public.store_orders
(
    order_no int,
    order_date timestamp NOT NULL,
    shipper varchar(20),
    ship_date date
)
UNSEGMENTED ALL NODES PARTITION BY order_date::DATE;
CREATE TABLE
=> COPY store_orders FROM '/home/dbadmin/export_store_orders_data.txt';
41834
(1 row)

当 COPY 将新表数据加载到 ROS 存储中时,它通过将每日订单划分为不同的分区(在本例中为 809 个分区)来执行该表的 partition 子句,其中每个分区都需要有自己的 ROS 容器:

=> SELECT COUNT (DISTINCT ros_id) NumROS, node_name FROM PARTITIONS
    WHERE projection_name ilike '%store_orders_super%' GROUP BY node_name ORDER BY node_name;
 NumROS |    node_name
--------+------------------
    809 | v_vmart_node0001
    809 | v_vmart_node0002
    809 | v_vmart_node0003
(3 rows)

这远远高于每个投影最多 50 个分区的建议值。这一数字也接近每个投影 1024 个 ROS 容器的默认系统限制,从而面临不久的将来发生 ROS 推回的风险。

您可以通过多种方式解决此问题:

  • 考虑将表数据合并到更大的分区中,例如,按月而不是按天进行分区。但是,在此级别对数据进行分区可能会限制分区管理函数的有效使用。

  • 定期存档旧分区,从而最大限度地减少累积分区的数量。但是,这需要多一层数据管理,并且还要禁止访问历史数据。

或者,可以使用 CALENDAR_HIERARCHY_DAY 将分区自动合并到基于日期的分区组层次结构中。每个分区组都存储在其自己的一组 ROS 容器中,与其他组区别开来。您可以在表 partition 子句中指定此函数,如下所示:

PARTITION BY partition‑expression
  GROUP BY CALENDAR_HIERARCHY_DAY( partition‑expression [, active‑months[, active‑years] ] )

例如,给定上一个表,您可以按如下方式对其进行重新分区:


=> ALTER TABLE public.store_orders
      PARTITION BY order_date::DATE
      GROUP BY CALENDAR_HIERARCHY_DAY(order_date::DATE, 2, 2) REORGANIZE;

按层次结构对 DATE 数据进行分组

CALENDAR_HIERARCHY_DAY 会创建分区组的层次结构,并将分区合并到相应的组中。为此,它使用以下算法计算每个表行的分区表达式,来确定其分区组键:

GROUP BY (
CASE WHEN DATEDIFF('YEAR', partition-expression, NOW()::TIMESTAMPTZ(6)) >= active-years
       THEN DATE_TRUNC('YEAR', partition-expression::DATE)
     WHEN DATEDIFF('MONTH', partition-expression, NOW()::TIMESTAMPTZ(6)) >= active-months
       THEN DATE_TRUNC('MONTH', partition-expression::DATE)
     ELSE DATE_TRUNC('DAY', partition-expression::DATE) END);

在此示例中,算法将每个 store_orders 行中的 order_date 与当前日期进行比较,如下所示:

  1. 确定 order_date 是否处于非活动年份。

    如果 order_date 处于非活动年份,则该行的分区组键解析为该年份。该行将被合并到该年份的 ROS 容器中。

  2. 如果 order_date 处于活动年份,则 CALENDAR_HIERARCHY_DAY 会计算 order_date 以确定它是否处于非活动月份。

    如果 order_date 处于非活动月份,则该行的分区组键解析为该月份。该行将被合并到该月份的 ROS 容器中。

  3. 如果 order_date 处于活动月份,则该行的分区组键解析为 order_date 日期。该行将被合并到该日期的 ROS 容器中。order_date 是未来日期的任何行都将以相同的方式处理。

例如,如果当前日期是 2017-09-26,则 CALENDAR_HIERARCHY_DAY 会将 active-yearsactive-months 解析为以下日期范围:

  • active‑years:2016-01-01 到 2017-12-31。处于活动年份的分区将被分组到每月的 ROS 容器中,或合并到每日的 ROS 容器中。早期年份的分区被认为是非活动年份,合并到年度 ROS 容器中。

  • active‑months:2017/8/1 到 2017/9/30。处于活动月份的分区将被合并到每日的 ROS 容器中。

现在,每个投影的 ROS 容器总数减少到 40 个:

=> SELECT COUNT (DISTINCT ros_id) NumROS, node_name FROM PARTITIONS
    WHERE projection_name ilike '%store_orders_super%' GROUP BY node_name ORDER BY node_name;
 NumROS |    node_name
--------+------------------
     40 | v_vmart_node0001
     40 | v_vmart_node0002
     40 | v_vmart_node0003
(3 rows)

动态重新分组

如前所述,CALENDAR_HIERARCHY_DAY 在创建分区组键和合并分区时引用当前日期。随着日历向前推进,Tuple Mover 会重新计算使用此函数分区的表的分区组键,并根据需要将分区移动到不同的 ROS 容器中。

因此,给定前面的示例,在 2017-10-01,Tuple Mover 会为 8 月分区创建一个月度 ROS 容器。2017-08-01 和 2017-08-31 之间的所有分区键都将合并到新的 ROS 容器 2017-08 中:

同样,在 2018-01-01,Tuple Mover 会为 2016 年分区创建一个 ROS 容器。之前按月份分组的 2016-01-01 和 2016-12-31 之间的所有分区键都将合并到新的年度 ROS 容器中:

自定义分区组层次结构

Vertica 提供了一个函数 CALENDAR_HIERARCHY_DAY,以方便分层分区。Vertica 将 GROUP BY 子句存储为 CASE 语句,您可以对其进行编辑以满足自己的要求。

例如,Vertica 按如下所示存储 store_orders partition 子句:

=> ALTER TABLE public.store_orders
      PARTITION BY order_date::DATE
      GROUP BY CALENDAR_HIERARCHY_DAY(order_date::DATE, 2, 2);
=> select export_tables('','store_orders');
...
CREATE TABLE public.store_orders ( ... )

PARTITION BY ((store_orders.order_date)::date)
GROUP BY (
CASE WHEN ("datediff"('year', (store_orders.order_date)::date, ((now())::timestamptz(6))::date) >= 2)
       THEN (date_trunc('year', (store_orders.order_date)::date))::date
     WHEN ("datediff"('month', (store_orders.order_date)::date, ((now())::timestamptz(6))::date) >= 2)
       THEN (date_trunc('month', (store_orders.order_date)::date))::date
     ELSE (store_orders.order_date)::date END);

您可以修改 CASE 语句,以自定义分区组的层次结构。例如,以下 CASE 语句创建月、日和小时的层次结构:

=> ALTER TABLE store_orders
PARTITION BY (store_orders.order_date)
GROUP BY (
CASE WHEN DATEDIFF('MONTH', store_orders.order_date, NOW()::TIMESTAMPTZ(6)) >= 2
       THEN DATE_TRUNC('MONTH', store_orders.order_date::DATE)
     WHEN DATEDIFF('DAY', store_orders.order_date, NOW()::TIMESTAMPTZ(6)) >= 2
       THEN DATE_TRUNC('DAY', store_orders.order_date::DATE)
     ELSE DATE_TRUNC('hour', store_orders.order_date::DATE) END);

3 - 分区和分段

在 Vertica 中,分区和分段是不同的概念,可实现不同的数据本地化目标:

  • 分段 是指跨群集节点组织和分发数据,以便实现快速数据清除和查询性能。分段的目的是使数据在多个数据库节点上均匀分布,以便所有节点均参与查询执行。可通过 CREATE PROJECTION 语句的哈希分段子句来指定分段。

  • 分区 指定如何在各个节点内组织数据以进行分布式计算。通过节点分区,可以轻松地标识要删除的数据和帮助回收磁盘空间。可以通过 CREATE TABLE 语句的 PARTITION BY 子句来指定分区。

例如:按年对数据进行分区对于保留和删除年度数据意义重大。然而,按年对相同数据分段则效率低下,因为与其他节点相比,包含本年数据的节点所要响应的查询可能要多得多。

下图显示了四节点数据库群集的分段和分区流:

  1. 示例表数据

  2. HASH(order_id) 分段的数据

  3. 按哈希跨四个节点分段的数据

  4. 按年在单个节点上分区的数据

尽管分区发生在所有四个节点上,但为了简单起见,示意图仅显示一个节点上的分区数据。

数据分区与分段

另请参阅

4 - 管理分区

可以通过以下操作管理分区:

4.1 - 删除分区

使用 DROP_PARTITIONS 函数删除给定表的一个或多个分区键。可以指定单个分区键或分区键范围。

例如,对新表进行分区中显示的表按列 order_date 进行分区:

=> CREATE TABLE public.store_orders
(
    order_no int,
    order_date timestamp NOT NULL,
    shipper varchar(20),
    ship_date date
)
PARTITION BY YEAR(order_date);

鉴于此表定义,Vertica 会为每个唯一的 order_date 年份(在本例中为 2017、2016、2015 和 2014 年)创建一个分区键,并相应地将数据划分到不同的 ROS 容器中。

以下 DROP_PARTITIONS 语句从表 store_orders 中删除与分区键 2014 关联的所有订单记录:

=> SELECT DROP_PARTITIONS ('store_orders', 2014, 2014);
Partition dropped

拆分分区组

如果表 partition 子句包括 GROUP BY 子句,则分区将按其分区组键在 ROS 中合并。 DROP_PARTITIONS 随后可以指定给定分区组内或跨多个分区组的分区键范围。无论哪种情况,删除操作都要求 Vertica 拆分存储这些分区的 ROS 容器。为此,该函数的 force_split 参数必须设置为 true。

例如,上面显示的 store_orders 表可以使用 GROUP BY 子句重新分区,如下所示:

=> ALTER TABLE store_orders
     PARTITION BY order_date::DATE GROUP BY DATE_TRUNC('year', (order_date)::DATE) REORGANIZE;

由于之前删除了 2014 年的所有订单记录,order_date 值现在跨越三个年份(2017 年、2016 年和 2015 年)。因此,Tuple Mover 为每年创建三个分区组键,并为每个组指定一个或多个 ROS 容器。然后,它将 store_orders 分区合并到相应的组中。

以下 DROP_PARTITIONS 语句指定删除跨越两个年份(2014 年和 2015 年)的订单日期:

=> SELECT DROP_PARTITIONS('store_orders', '2015-05-30', '2016-01-16', 'true');
Partition dropped

删除操作要求 Vertica 从两个分区组(2015 和 2016)删除分区。这些组至少跨越两个 ROS 容器,必须拆分这些容器才能移除目标分区。因此,将函数的 force_split 参数设置为 true。

调度分区删除

如果硬件具有固定的磁盘空间,则可能需要配置一个定期执行的进程,以便通过删除分区来迁出旧数据。

例如,如果空间只够将数据存储固定的天数,请将 Vertica 配置为删除最旧的分区键。为此,请创建一个基于时间的作业调度程序(例如 cron),以计划在低负载期间删除分区键。

如果数据输入速率具有峰值和谷值,则可以使用以下两种技术来管理分区键删除方式:

  • 设置一个定期(每天)检查磁盘空间的进程。如果已用磁盘空间百分比超出特定阈值(例如 80%),则删除最旧的分区键。

  • 在分区中添加一个可根据行计数等指标递增的假列。例如,每次行计数增加 100 行时,该列可能会递增。设置一个定期(每天)查询此列的进程。如果新列中的值超出特定阈值(例如 100),请删除最旧的分区键,并将该列的值设置回 0。

表锁定

DROP_PARTITIONS 会在目标表上获取一个独占的 O 锁,以阻止可能会影响表数据的任何 DML 操作(DELETEUPDATEINSERTCOPY)。该锁还会阻止在 SERIALIZABLE 隔离级别发出的 SELECT 语句。

如果操作无法在目标表上获取 O lock,Vertica 将尝试关闭该表上运行的任何内部 tuple mover 会话。如果成功,则可以继续操作。在用户会话中运行的显式 Tuple Mover 操作不会关闭。如果显式 Tuple Mover 操作在表上运行,则该操作仅在 Tuple Mover 操作完成后继续。

4.2 - 存档分区

可以使用 Vertica 函数 MOVE_PARTITIONS_TO_TABLE 在表之间移动分区。此函数对于在下列过程中存档旧分区非常有用:

  1. 标识要存档的分区,并使用 MOVE_PARTITIONS_TO_TABLE 将其移动到临时暂存表

  2. 备份临时表

  3. 删除临时表

可以随时还原已存档的分区

将分区移动到临时表

存档历史数据的方法是标识要从表中移除的分区。然后,将每个分区(或分区组)移动到临时表。

在调用 MOVE_PARTITIONS_TO_TABLE 之前:

  • 刷新所有过时投影。

以下建议适用于临时表:

  • 为了方便备份过程,为每个存档操作的临时表创建唯一架构。

  • 为临时表指定新名称。这可确保它们不包含先前移动操作中的分区。


    如果该表不存在,Vertica 将根据源表的定义,通过调用 CREATE TABLE 包含 LIKEINCLUDING PROJECTIONS 子句来创建一个表。该新表会从源表中继承所有权。有关详细信息,请参阅复制表

  • 使用能够让其他用户轻松标识分区内容的临时名称。例如,如果表按日期分区,则使用指定日期或日期范围的名称。

在以下示例中,MOVE_PARTITIONS_TO_TABLE 指定将单个分区移动到临时表 partn_backup.tradfes_200801

=> SELECT MOVE_PARTITIONS_TO_TABLE (
          'prod_trades',
          '200801',
          '200801',
          'partn_backup.trades_200801');
MOVE_PARTITIONS_TO_TABLE
-------------------------------------------------
 1 distinct partition values moved at epoch 15.
(1 row)

备份临时表。

在创建临时表后,可以使用 vbr 配置文件通过对象级备份来存档它。有关详细信息,请参阅备份和还原数据库

删除临时表

在备份完成后,可以按删除表中所述删除临时表。

还原已存档分区

您可以还原之前移至中间表、存档为对象级别备份后删除的分区。

还原已存档分区的步骤如下:

  1. 还原在移动一个或多个待存档分区时所保存的中间表的备份(请参阅存档分区)。

  2. 将还原后的分区从中间表移至原始表。

  3. 删除中间表。

4.3 - 交换分区

SWAP_PARTITIONS_BETWEEN_TABLESDROP_PARTITIONSMOVE_PARTITIONS_TO_TABLE 的操作合并为单个事务。如果定期将已分区数据从一个表加载到另一个表,并且需要刷新第二个表中的分区,则 SWAP_PARTITIONS_BETWEEN_TABLES 很有用。

例如,您可能具有一个按日期分区的收入表,而且经常将数据从临时表移动到该表中。临时表有时包含目标表中已有日期的数据。在这种情况下,首先必须从目标表中删除这些日期的分区,然后将其替换为临时表中的相应分区。可以通过调用一次 SWAP_PARTITIONS_BETWEEN_TABLES 来完成这两个任务。

通过将删除和移动操作包装在单个事务中,SWAP_PARTITIONS_BETWEEN_TABLES 可保持交换数据的完整性。如果交换操作中的任何任务失败,整个操作则会失败并回退。

示例

以下示例创建两个已分区表,然后在两者之间交换某些分区。

这两个表的定义相同,且具有不同 year 值的分区。交换 year = 2008 和 year = 2009 的分区。这两个表都至少有两行要交换。

  1. 创建 customer_info 表:

    => CREATE TABLE customer_info (
          customer_id INT NOT NULL,
          first_name VARCHAR(25),
          last_name VARCHAR(35),
          city VARCHAR(25),
          year INT NOT NULL)
          ORDER BY last_name
          PARTITION BY year;
    
  2. customer_info 表中插入数据:

    INSERT INTO customer_info VALUES
     (1,'Joe','Smith','Denver',2008),
     (2,'Bob','Jones','Boston',2008),
     (3,'Silke','Muller','Frankfurt',2007),
     (4,'Simone','Bernard','Paris',2014),
     (5,'Vijay','Kumar','New Delhi',2010);
     OUTPUT
    --------
          5
    (1 row)
    
    => COMMIT;
    
  3. 查看表数据:

    => SELECT * FROM customer_info ORDER BY year DESC;
     customer_id | first_name | last_name |   city    | year
    -------------+------------+-----------+-----------+------
               4 | Simone     | Bernard   | Paris     | 2014
               5 | Vijay      | Kumar     | New Delhi | 2010
               2 | Bob        | Jones     | Boston    | 2008
               1 | Joe        | Smith     | Denver    | 2008
               3 | Silke      | Muller    | Frankfurt | 2007
    (5 rows)
    
  4. 创建第二个表 member_info,其具有与 customer_info 相同的定义:

    => CREATE TABLE member_info LIKE customer_info INCLUDING PROJECTIONS;
    CREATE TABLE
    
  5. member_info 表中插入数据:

    => INSERT INTO member_info VALUES
    (1,'Jane','Doe','Miami',2001),
    (2,'Mike','Brown','Chicago',2014),
    (3,'Patrick','OMalley','Dublin',2008),
    (4,'Ana','Lopez','Madrid',2009),
    (5,'Mike','Green','New York',2008);
     OUTPUT
    --------
          5
    (1 row)
    => COMMIT;
    COMMIT
    
  6. 查看 member_info 表中的数据:

    => SELECT * FROM member_info ORDER BY year DESC;
     customer_id | first_name | last_name |   city   | year
    -------------+------------+-----------+----------+------
               2 | Mike       | Brown     | Chicago  | 2014
               4 | Ana        | Lopez     | Madrid   | 2009
               5 | Mike       | Green     | New York | 2008
               3 | Patrick    | OMalley   | Dublin   | 2008
               1 | Jane       | Doe       | Miami    | 2001
    (5 rows)
    
  7. 要交换分区,请运行 SWAP_PARTITIONS_BETWEEN_TABLES 函数:

    => SELECT SWAP_PARTITIONS_BETWEEN_TABLES('customer_info', 2008, 2009, 'member_info');
                                        SWAP_PARTITIONS_BETWEEN_TABLES
    ----------------------------------------------------------------------------------------------
     1 partition values from table customer_info and 2 partition values from table member_info are swapped at epoch 1045.
    
    (1 row)
    
  8. 查询这两个表以确认它们交换了各自的 2008 年和 2009 年记录:

    => SELECT * FROM customer_info ORDER BY year DESC;
     customer_id | first_name | last_name |   city    | year
    -------------+------------+-----------+-----------+------
               4 | Simone     | Bernard   | Paris     | 2014
               5 | Vijay      | Kumar     | New Delhi | 2010
               4 | Ana        | Lopez     | Madrid    | 2009
               3 | Patrick    | OMalley   | Dublin    | 2008
               5 | Mike       | Green     | New York  | 2008
               3 | Silke      | Muller    | Frankfurt | 2007
    (6 rows)
    
    => SELECT * FROM member_info ORDER BY year DESC;
     customer_id | first_name | last_name |  city   | year
    -------------+------------+-----------+---------+------
               2 | Mike       | Brown     | Chicago | 2014
               2 | Bob        | Jones     | Boston  | 2008
               1 | Joe        | Smith     | Denver  | 2008
               1 | Jane       | Doe       | Miami   | 2001
    (4 rows)
    

4.4 - 最小化分区

默认情况下,Vertica 最多支持 1024 个 ROS 容器来存储给定投影的分区(请参阅投影参数)。ROS 容器包含共享相同分区键或相同分区组键的数据。根据每个分区的数据量,一个分区或分区组可以跨多个 ROS 容器。

鉴于此限制,不建议按高度精细的数据(例如,按 TIMESTAMP 列)对表进行分区。这样做会生成非常多的分区。如果分区数量需要超过 1024 个 ROS 容器,Vertica 会发出 ROS 推回警告并拒绝加载更多表数据。大量 ROS 容器也会对 DML 操作(例如 DELETE)产生负面影响,这需要 Vertica 打开所有 ROS 容器。

在实践中,您不太可能接近此最大值。为了获得最佳性能,Vertica 建议未分组分区的数量介于 10 到 20 之间,并且不超过 50。此范围通常适用于大多数业务需求。

还可以通过对分区进行分组来减少 ROS 容器的数量。有关详细信息,请参阅分区分组分层分区

4.5 - 查看分区存储数据

Vertica 提供了多种方式来查看表分区的组织和存储方式:

  • 查询 PARTITIONS 系统表。

  • 转储分区键。

查询 PARTITIONS 表

下面的表和投影定义按订单日期对 store_order 数据进行分区,并将同一年份的分区分组在一起:

=> CREATE TABLE public.store_orders
  (order_no int, order_date timestamp NOT NULL, shipper varchar(20), ship_date date)
  PARTITION BY ((order_date)::date) GROUP BY (date_trunc('year', (order_date)::date));

=> CREATE PROJECTION public.store_orders_super
   AS SELECT order_no, order_date, shipper, ship_date FROM store_orders
   ORDER BY order_no, order_date, shipper, ship_date UNSEGMENTED ALL NODES;

=> COPY store_orders FROM '/home/dbadmin/export_store_orders_data.txt';

在将数据加载到此表后,可以查询 PARTITIONS 表,以确定所有节点上有多少个 ROS 容器存储投影 store_orders_unseg 的已分组分区。每个节点有八个 ROS 容器,每个容器存储一个分区组的分区:

=> SELECT COUNT (partition_key) NumPartitions, ros_id, node_name FROM PARTITIONS
       WHERE projection_name ilike 'store_orders%' GROUP BY ros_id, node_name ORDER BY node_name, NumPartitions;
 NumPartitions |      ros_id       |    node_name
---------------+-------------------+------------------
           173 | 45035996274562779 | v_vmart_node0001
           211 | 45035996274562791 | v_vmart_node0001
           212 | 45035996274562783 | v_vmart_node0001
           213 | 45035996274562787 | v_vmart_node0001
           173 | 49539595901916471 | v_vmart_node0002
           211 | 49539595901916483 | v_vmart_node0002
           212 | 49539595901916475 | v_vmart_node0002
           213 | 49539595901916479 | v_vmart_node0002
           173 | 54043195529286985 | v_vmart_node0003
           211 | 54043195529286997 | v_vmart_node0003
           212 | 54043195529286989 | v_vmart_node0003
           213 | 54043195529286993 | v_vmart_node0003
(12 rows)

转储分区键

Vertica 提供了多个函数,允许您在多个级别检查各个分区在群集上的存储方式:

给定前面的表和投影,DUMP_PROJECTION_PARTITION_KEYS 显示每个节点上四个 ROS 容器的内容:


=> SELECT DUMP_PROJECTION_PARTITION_KEYS('store_orders_super');
 ...
 Partition keys on node v_vmart_node0001
  Projection 'store_orders_super'
   Storage [ROS container]
     No of partition keys: 173
     Partition keys: 2017-01-02 2017-01-03 2017-01-04 2017-01-05 2017-01-06 2017-01-09 2017-01-10
2017-01-11 2017-01-12 2017-01-13 2017-01-16 2017-01-17 2017-01-18 2017-01-19 2017-01-20 2017-01-23
2017-01-24 2017-01-25 2017-01-26 2017-01-27 2017-02-01 2017-02-02 2017-02-03 2017-02-06 2017-02-07
2017-02-08 2017-02-09 2017-02-10 2017-02-13 2017-02-14 2017-02-15 2017-02-16 2017-02-17 2017-02-20
...
2017-09-01 2017-09-04 2017-09-05 2017-09-06 2017-09-07 2017-09-08 2017-09-11 2017-09-12 2017-09-13
2017-09-14 2017-09-15 2017-09-18 2017-09-19 2017-09-20 2017-09-21 2017-09-22 2017-09-25 2017-09-26 2017-09-27
   Storage [ROS container]
     No of partition keys: 212
     Partition keys: 2016-01-01 2016-01-04 2016-01-05 2016-01-06 2016-01-07 2016-01-08 2016-01-11
2016-01-12 2016-01-13 2016-01-14 2016-01-15 2016-01-18 2016-01-19 2016-01-20 2016-01-21 2016-01-22
2016-01-25 2016-01-26 2016-01-27 2016-02-01 2016-02-02 2016-02-03 2016-02-04 2016-02-05 2016-02-08
2016-02-09 2016-02-10 2016-02-11 2016-02-12 2016-02-15 2016-02-16 2016-02-17 2016-02-18 2016-02-19
...
2016-11-01 2016-11-02 2016-11-03 2016-11-04 2016-11-07 2016-11-08 2016-11-09 2016-11-10 2016-11-11
2016-11-14 2016-11-15 2016-11-16 2016-11-17 2016-11-18 2016-11-21 2016-11-22 2016-11-23 2016-11-24 2016-11-25
   Storage [ROS container]
     No of partition keys: 213
     Partition keys: 2015-01-01 2015-01-02 2015-01-05 2015-01-06 2015-01-07 2015-01-08 2015-01-09
2015-01-12 2015-01-13 2015-01-14 2015-01-15 2015-01-16 2015-01-19 2015-01-20 2015-01-21 2015-01-22
2015-01-23 2015-01-26 2015-01-27 2015-02-02 2015-02-03 2015-02-04 2015-02-05 2015-02-06 2015-02-09
2015-02-10 2015-02-11 2015-02-12 2015-02-13 2015-02-16 2015-02-17 2015-02-18 2015-02-19 2015-02-20
...
2015-11-02 2015-11-03 2015-11-04 2015-11-05 2015-11-06 2015-11-09 2015-11-10 2015-11-11 2015-11-12
2015-11-13 2015-11-16 2015-11-17 2015-11-18 2015-11-19 2015-11-20 2015-11-23 2015-11-24 2015-11-25
2015-11-26 2015-11-27
   Storage [ROS container]
     No of partition keys: 211
     Partition keys: 2014-01-01 2014-01-02 2014-01-03 2014-01-06 2014-01-07 2014-01-08 2014-01-09
2014-01-10 2014-01-13 2014-01-14 2014-01-15 2014-01-16 2014-01-17 2014-01-20 2014-01-21 2014-01-22
2014-01-23 2014-01-24 2014-01-27 2014-02-03 2014-02-04 2014-02-05 2014-02-06 2014-02-07 2014-02-10
2014-02-11 2014-02-12 2014-02-13 2014-02-14 2014-02-17 2014-02-18 2014-02-19 2014-02-20 2014-02-21
...
2014-11-04 2014-11-05 2014-11-06 2014-11-07 2014-11-10 2014-11-11 2014-11-12 2014-11-13 2014-11-14
2014-11-17 2014-11-18 2014-11-19 2014-11-20 2014-11-21 2014-11-24 2014-11-25 2014-11-26 2014-11-27
   Storage [ROS container]
     No of partition keys: 173
...

5 - 活动和非活动分区

Tuple Mover 假定分区表的所有加载和更新都是针对一个或多个标识为活动的分区。通常,具有最大分区键的分区(通常是最近创建的分区)均视为活动分区。随着分区的老化,其工作负载通常会缩小,并大多变为只读状态。

设置活动分区计数

您可以按优先级的升序指定两个级别的分区表的活动分区数:

  • 配置参数 ActivePartitionCount 将确定数据库中分区表的活动分区数。默认情况下,ActivePartitionCount 设置为 1。Tuple Mover 会将此设置应用于自身未设置活动分区计数的所有表。

  • 通过使用 CREATE TABLEALTER TABLE 设置各个表自身的活动分区计数,可以取代各个表的 ActivePartitionCount。

同一数据库中的分区表可能会受到更新和加载活动的不同分布的影响。当这些差异显著时,某些表设置自己的活动分区计数便可能有了意义。

例如,表 store_orders 按月分区,并通过配置参数 ActivePartitionCount 获取其活动分区计数。如果该参数设置为 1,则 Tuple Mover 会将最近一个月(通常是当前月份)标识为表的活动分区。如果 store_orders 面临当前月份和上一个月的频繁数据活动,您可能希望该表取代此配置参数,将其活动分区计数设置为 2:

ALTER TABLE public.store_orders SET ACTIVEPARTITIONCOUNT 2;

识别活动分区

Tuple Mover 通常将活动分区标识为最近创建的分区。Vertica 使用以下算法来确定哪些分区比其他分区较旧:

  1. 如果分区 X 是在分区 Y 之前创建的,则分区 X 较旧。

  2. 如果分区 X 和 Y 是同时创建的,但分区 X 上次更新是在分区 Y 之前,则分区 X 较旧。

  3. 如果分区 X 和 Y 是同时创建的且上次更新时间相同,则具有较小键的分区较旧。

您可以通过联接系统表 PARTITIONSSTRATA 并查询其投影来获取表的活动分区。例如,以下查询获取投影 store_orders_super 的活动分区:

=> SELECT p.node_name, p.partition_key, p.ros_id, p.ros_size_bytes, p.ros_row_count, ROS_container_count
     FROM partitions p JOIN strata s ON p.partition_key = s.stratum_key AND p.node_name=s.node_name
     WHERE p.projection_name = 'store_orders_super' ORDER BY p.node_name, p.partition_key;
    node_name     | partition_key |      ros_id       | ros_size_bytes | ros_row_count | ROS_container_count
------------------+---------------+-------------------+----------------+---------------+---------------------
 v_vmart_node0001 | 2017-09-01    | 45035996279322851 |           6905 |           960 |                   1
 v_vmart_node0002 | 2017-09-01    | 49539595906590663 |           6905 |           960 |                   1
 v_vmart_node0003 | 2017-09-01    | 54043195533961159 |           6905 |           960 |                   1
(3 rows)

活动分区组

如果表的 partition 子句包括 GROUP BY 表达式,Vertica 会将该表的活动分区计数应用于其最大分区组键,并将该组中的所有分区视为活动分区。如果使用 Vertica 元函数 CALENDAR_HIERARCHY_DAY 对分区进行分组,则最近日期的分区也会按天分组。因此,最大分区组键和最大分区键相同。实际上,这意味着只有最近的分区是活动分区。

有关分区分组的详细信息,请参阅分区分组分层分区

6 - 分区修剪

如果查询谓词指定了分区表达式,则查询优化器会根据已分区数据的 ROS 容器计算谓词。每个 ROS 容器维护其分区键数据的最小值和最大值。查询优化器使用此元数据来确定执行查询所需的 ROS 容器,并从查询计划中省略或修剪其余容器。通过最大限度地减少它必须扫描的 ROS 容器的数量,查询优化器可以更快地执行查询。

例如,表可能按年份进行分区,如下所示:

=> CREATE TABLE ... PARTITION BY EXTRACT(year FROM date);

给定此表定义,其投影数据根据年份划分到 ROS 容器中,每个年份一个容器,在本例中为 2007 年、2008 年、2009 年。

以下查询指定分区表达式 date


=> SELECT ... WHERE date = '12-2-2009';

给定此查询,包含 2007 年和 2008 年数据的 ROS 容器不在所请求年份(2009 年)的范围内。在查询执行之前,查询优化器会从查询计划中修剪这些容器:

示例

假定表按时间分区,并且它将使用按时间限制数据的查询。

=> CREATE TABLE time ( tdate DATE NOT NULL, tnum INTEGER)
     PARTITION BY EXTRACT(year FROM tdate);
=> CREATE PROJECTION time_p (tdate, tnum) AS
=> SELECT * FROM time ORDER BY tdate, tnum UNSEGMENTED ALL NODES;
=> INSERT INTO time VALUES ('03/15/04' , 1);
=> INSERT INTO time VALUES ('03/15/05' , 2);
=> INSERT INTO time VALUES ('03/15/06' , 3);
=> INSERT INTO time VALUES ('03/15/06' , 4);

在前面的一系列命令中插入的数据将加载到三个 ROS 容器(每个年份一个容器),因为这就是数据的分区方式:

=> SELECT * FROM time ORDER BY tnum;
   tdate    | tnum
------------+------
 2004-03-15 |    1  --ROS1 (min 03/01/04, max 03/15/04)
 2005-03-15 |    2  --ROS2 (min 03/15/05, max 03/15/05)
 2006-03-15 |    3  --ROS3 (min 03/15/06, max 03/15/06)
 2006-03-15 |    4  --ROS3 (min 03/15/06, max 03/15/06)
(4 rows)

当查询 time 表时,将发生以下操作:

  • 在此查询中,Vertica 可以省略 ROS2 容器,因为它仅查找 2004 年:

    => SELECT COUNT(*) FROM time WHERE tdate = '05/07/2004';
    
  • 在下一个查询中,Vertica 可以省略两个容器 ROS1 和 ROS3:

    => SELECT COUNT(*) FROM time WHERE tdate = '10/07/2005';
    
  • 以下查询有一个针对 tnum 列的附加谓词,且该列中没有保留最小/最大值。此外,由于不支持使用逻辑运算符 OR,因此没有消除 ROS:

    => SELECT COUNT(*) FROM time WHERE tdate = '05/07/2004' OR tnum = 7;