跳转至

Vertica 表分区策略选择指南

作者:JiangChong | 发布时间:2026-05-27

适用场景:当遇到以下任一情况时,本文可提供系统性的决策框架和操作指南——开发团队不确定某张表是否应该分区、现有的分区方案导致 ROS 容器数逼近上限、需要从非分区表迁移为分区表以支持数据生命周期管理。

关联文章

理解全文脉络

本文按「判断 → 设计 → 实施 → 优化」的逻辑组织。第 1 节阐释分区的底层工作原理和成本模型,帮你建立直觉;第 2-3 节提供从监控到定位的完整排查路径,告诉你如何评估当前分区策略的健康状况;第 4 节给出从快速修复到根本治理的解决方案,包括从非分区表迁移到分区表的完整步骤;第 5-6 节通过案例和演练串联所有知识点;第 7 节是可复用的 SQL 工具箱。如果你只想知道「某张表该不该分区」,可以直接跳到第 3.1 节。


1. 原理理解

1.1 分区在 Vertica 中到底做了什么

Vertica 的分区不是把数据放到不同文件系统中的目录(不像 PostgreSQL 的表分区那样创建子表),而是在逻辑层面将同一张表不同分区的数据分配到不同的 ROS 容器(ROS Container)中。分区键的值决定了每一行数据进入哪个 ROS 容器。

可以这样理解:非分区表就像一个巨大的文件柜,所有文件都塞在一起;分区表则像把文件柜按年份贴上标签——2008 年的文件在一个抽屉,2009 年的在另一个抽屉。标签(分区键)让你能直接找到想要的抽屉,而不需要翻遍整个柜子。

Vertica 分区有两个核心优势:

  1. 存储裁剪(Storage Pruning):查询优化器读取每个 ROS 容器的分区键 min/max 值,在查询规划阶段直接跳过不相关的容器。如果查询谓词包含了分区键,Vertica 可以跳过绝大部分数据块,大幅减少 I/O。
  2. 数据生命周期管理:分区让你能以分区为单位执行 DROP_PARTITIONS(秒级完成,纯 catalog 操作;旧函数 DROP_PARTITION 自 Vertica 9.0 起废弃)、MOVE_PARTITIONS_TO_TABLE(将冷数据移到归档表)、或通过存储策略将冷分区迁到廉价存储。

1.2 分区与分段是两个正交的概念

这是初学者最容易混淆的地方。分段(Segmentation)决定数据在集群节点间如何分布,分区(Partitioning)决定在每个节点内部数据如何组织。

维度 分段(Segmentation) 分区(Partitioning)
作用域 跨节点 单节点内部
目的 利用 MPP 并行计算 减少 I/O,简化数据管理
典型表达式 SEGMENTED BY HASH(id) ALL NODES PARTITION BY EXTRACT(year FROM date_col)
对查询的影响 决定并行度 决定存储裁剪效率

一张大表通常同时需要分段和分区。例如,按 HASH(trans_id) 分段保证数据均匀分布到所有节点,按 EXTRACT(month FROM trans_date) 分区保证查询能裁剪到目标月份。

1.3 活跃分区与非活跃分区的关键区别

最后创建的分区称为活跃分区(Active Partition),默认为 1 个,由配置参数 ActivePartitionCount 控制。活跃分区和非活跃分区在 Tuple Mover 的合并策略上存在本质差异:

  • 非活跃分区:Tuple Mover 会将该分区内所有 ROS 容器合并为1 个 ROS 容器(每个节点每个投影)。这就是为什么按天分区保留 3 年会产生 365 × 3 = 1095 个 ROS 容器——每个非活跃分区的每一天都会变成 1 个独立的 ROS 容器。
  • 活跃分区:Tuple Mover 使用 strata 算法进行分层合并——不同大小的 ROS 容器被分组到不同的 stratum,按比例合并。活跃分区的 ROS 容器数量不固定,取决于加载频率和数据量。

这个区别直接决定了分区粒度的取舍:分区太细(如按天),非活跃分区的 ROS 容器数量会线性增长,逼近 1024 的上限;分区太粗(如按年),虽然 ROS 容器数很少,但存储裁剪的优势也大幅减弱。

1.4 分区粒度的成本模型

每个节点每个投影最多 1024 个 ROS 容器。总 ROS 容器数的构成如下:

每个节点每个投影的 ROS 容器总数 = 非活跃分区数×1 + 活跃分区的 ROS 容器数

解释:每个非活跃分区经过 mergeout 后固定贡献 1 个 ROS 容器(无论分区内有多少行数据),而活跃分区使用 strata 分层算法,可能产生 1~50+ 个 ROS 容器(取决于数据加载频率)。因此分区数越多,ROS 容器数就线性增长——这也是为什么按天分区很危险。

不同分区粒度的非活跃分区数对比(以保留 3 年数据为例):

分区粒度 3 年分区数 非活跃 ROS 容器数 风险等级
按天 ~1095 ~1094 🔴 已超过 1024 上限
按周 ~156 ~155 🟢 安全
按月 ~36 ~35 🟢 安全
按季度 ~12 ~11 🟢 安全
按年 3 2 🟢 安全

超过 365 个分区(约上限的 1/3)时,就应该密切监控 ROS 容器数。当接近 1024 时,新数据加载会因为 ROS Pushback 而失败。

突破困境:分层分区(Hierarchical Partitioning) 按天分区裁剪精准但 ROS 容器爆炸,按月分区 ROS 安全但裁剪粗糙。CALENDAR_HIERARCHY_DAY 用分层结构同时解决两个问题——近期数据按天裁剪,旧数据按月/按年合并。详细用法见第 4.4 节。

1.5 所有可能的来源/原因总结

场景 是否需要分区 原因
大事实表(> 1 亿行),需要按时间维度删除历史数据 ✅ 强烈建议 DROP_PARTITIONS 秒级完成,比 DELETE 高效数个量级
大事实表,查询总带日期范围过滤 ✅ 建议 存储裁剪可跳过 90%+ 的 ROS 容器
中大型表,查询不按时间过滤 ⚠️ 谨慎 分区不会加速查询,反而增加 ROS 容器数
小表(< 100 万行) ❌ 不建议 ROS 容器开销远大于裁剪收益
维度表 ❌ 不建议 表本身很小,分区只会增加目录大小
临时/Staging 表 ❌ 不需要 生命周期短,TRUNCATE 即可

2. 系统级监控(从宏观入手)

2.1 检查整体分区使用情况

首先从全局视角了解数据库中分区表的数量和分布:

-- 两层聚合:内层按 (schema, 表, 分区键) 消除 buddy + 节点重复,外层按 schema 汇总
SELECT table_schema,
       COUNT(DISTINCT anchor_table_name) AS partitioned_tables,
       COUNT(*) AS total_partitions,  -- 内层已按表区分,COUNT(*) = 各表分区数之和,不跨表去重
       SUM(adj_row_count) AS total_rows,
       SUM(adj_size_bytes) / (1024*1024*1024) AS total_size_gb
FROM (
    SELECT p.table_schema, pr.anchor_table_name, p.partition_key,
           CASE WHEN BOOL_AND(pr.is_segmented)
                THEN SUM(p.ros_row_count)                     -- 分段表:跨节点汇总 = 分区总行数
                ELSE SUM(p.ros_row_count) / COUNT(DISTINCT p.node_name)  -- 非分段表:除以节点数
           END AS adj_row_count,
           CASE WHEN BOOL_AND(pr.is_segmented)
                THEN SUM(p.ros_size_bytes)
                ELSE SUM(p.ros_size_bytes) / COUNT(DISTINCT p.node_name)
           END AS adj_size_bytes
    FROM v_monitor.partitions p
    JOIN (
        -- 每表只保留 MIN(projection_id) 对应的一个投影,消除 buddy + 避免混合类型导致 BOOL_AND 失效
        -- DISTINCT 必须:非分段投影在 v_catalog.projections 中每节点一行,同一 projection_id 重复
        SELECT DISTINCT projection_id, anchor_table_name, is_segmented
        FROM v_catalog.projections
        WHERE projection_id IN (
            SELECT MIN(projection_id) FROM v_catalog.projections
            GROUP BY projection_schema, anchor_table_name
        )
    ) pr ON p.projection_id = pr.projection_id
    GROUP BY p.table_schema, pr.anchor_table_name, p.partition_key
) sub
GROUP BY table_schema
ORDER BY total_size_gb DESC;

如何解读结果

  • 关注 total_partitions 超过 365 的 schema/表:这些表存在 ROS 容器数增长的长期风险。
  • total_rowstotal_size_gb 已通过两层聚合同时消除重复:① GROUP BY projection_schema, anchor_table_name 每表只保留一个投影,彻底杜绝 buddy 和混合投影类型干扰 BOOL_AND;② CASE WHEN is_segmented 对非分段表除以节点数。分段/非分段表均准确。
  • 如果 total_size_gb 很大但 partitioned_tables 很少,说明可能存在大表未分区的情况——这正是第 3.1 节要排查的。
  • 对比各 schema 的分区表与非分区表比例,识别分区覆盖率偏低的 schema。

2.2 按投影查看 ROS 容器分布

这是最有价值的监控查询。它直接展示哪些表的 ROS 容器数已接近 1024 上限:

SELECT
    s.node_name,
    COALESCE(p.table_schema, s.schema_name) AS table_schema,
    s.projection_name,
    COUNT(DISTINCT s.storage_oid) AS storage_container_count,
    COUNT(DISTINCT p.partition_key) AS partition_count,
    COUNT(r.rosid) AS ros_file_count
FROM storage_containers s
LEFT JOIN v_monitor.partitions p
    ON s.storage_oid = p.ros_id
JOIN vs_ros r
    ON r.delid = s.storage_oid
WHERE s.node_name = (SELECT local_node_name())
GROUP BY 1, 2, 3
ORDER BY storage_container_count DESC
LIMIT 30;

如何解读结果

ROS 容器数 风险等级 行动建议
< 300 🟢 健康 无需干预
300-500 🟡 关注 每月检查一次趋势
500-800 🟠 警告 评估分区粒度是否需要调整
800-1024 🔴 危险 立即制定降容方案,随时可能触发 ROS Pushback

如果 partition_countstorage_container_count 接近(比例 > 0.8),说明 ROS 容器数主要由分区数决定——这是分区粒度过细的典型特征。

2.3 检查分区重组状态

ALTER TABLE ... PARTITION BY ... 执行后(不带 REORGANIZE 关键字),已有数据的分区键信息会被清除,但 Tuple Mover 需要在后台重新组织数据才能重建分区键。在此期间,这些 ROS 容器不参与 Mergeout,可能导致额外的 ROS 累积。

SELECT
    table_schema,
    table_name,
    projection_name,
    partition_reorganize_percent
FROM v_monitor.partition_status
WHERE partition_reorganize_percent < 100
ORDER BY partition_reorganize_percent ASC;

partition_reorganize_percent 表示该投影中已按当前分区表达式完成分区的数据比例。值小于 100 表示仍有数据在等待重组。如果这个值长时间停滞不增长,应检查 PARTITION_REORGANIZE_ERRORS 表排查错误。

2.4 识别未分区的大表

要找到数据库中哪些大表尚未分区,需要结合系统表的大小信息:

WITH proj_rows AS (
    SELECT
        ps.anchor_table_id,
        ps.projection_id,
        CASE WHEN pr.is_segmented
             THEN SUM(ps.row_count)    -- segmented: 跨节点汇总 = 该投影总行数
             ELSE MAX(ps.row_count)    -- unsegmented: 每节点全量副本,取任意一个即可
        END AS proj_total_rows
    FROM v_monitor.projection_storage ps
    JOIN v_catalog.projections pr ON ps.projection_id = pr.projection_id
    GROUP BY ps.anchor_table_id, ps.projection_id, pr.is_segmented
)
SELECT
    t.table_schema,
    t.table_name,
    SUM(ps.used_bytes) / (1024*1024*1024) AS total_size_gb,
    AVG(pr.proj_total_rows)::INT AS total_rows,
    t.partition_expression
FROM v_catalog.tables t
JOIN v_monitor.projection_storage ps ON t.table_id = ps.anchor_table_id
JOIN proj_rows pr ON t.table_id = pr.anchor_table_id AND ps.projection_id = pr.projection_id
WHERE t.partition_expression = ''
GROUP BY 1, 2, 5
  HAVING SUM(ps.used_bytes) > 50 * 1024*1024*1024  -- 总存储 > 50 GB
ORDER BY total_size_gb DESC;

计算口径说明: - total_size_gbSUM(used_bytes) 不加除——包含所有 buddy 投影、所有节点的存储总和,反映实际磁盘占用 - total_rows:CTE 在投影级别根据 is_segmented 区分计算方式,再 AVG 跨投影消除 buddy 重复——分段表和非分段表均准确 - 分段表:每节点存数据分片,跨节点 SUM = 该投影总行数 - 非分段表:每节点存全量副本,MAX 取任意节点 = 全量行数

如何解读结果:此查询筛选总存储 > 50 GB 的未分区表(约 25 GB 逻辑数据)。这是一个初筛门槛,不是分区标准。 结果中的表需按第 3.1 节的决策树逐一评估——有时间维度删除需求或日期范围查询优先分区,单纯数据量大且无以上需求的,参考 100 GB 逻辑数据阈值再决定。

2.5 检查 Catalog 大小

分区过多导致的 ROS 容器膨胀最终会体现在 catalog 大小上。catalog 超过 15 GB 就需要关注。两种检查方式:

方式一:数据库内查询(推荐,无需登录服务器) — 出自 理解 Vertica 的分区

SELECT
    node_name,
    MAX(catalog_size_in_MB) AS catalog_size_in_MB
FROM (
    SELECT
        node_name,
        SUM((total_memory_max_value - free_memory_min_value)) / (1024*1024) AS catalog_size_in_MB
    FROM dc_allocation_pool_statistics_by_second
    GROUP BY 1, TRUNC(("time")::TIMESTAMP, 'SS')
) foo
GROUP BY 1
ORDER BY 1;

dc_allocation_pool_statistics_by_second.total_memory_max_value - free_memory_min_value 是 catalog 内存池的使用量,等同于 catalog 占用的内存大小。超过 15 GB(约 15,000 MB)就需要关注。

方式二:Linux 层面直接检查

# 查看 Vertica catalog 目录大小(需替换为实际 catalog 路径)
du -sh /path/to/vertica_catalog/Catalog/

注意:catalog 的实际路径因安装配置而异。Enterprise 模式通常在 /vertica/data/<dbname>/v_<dbname>_node*_catalog/Catalog/,Eon 模式下 catalog 路径需先通过 v_monitor.disk_storage 查询获取。


3. 逐步定位根因(从宏观到微观)

3.1 Step 1:判断某张表是否需要分区

这是最基础的决策问题。按以下决策树判断:

决策逻辑

  1. 表是维度表还是事实表?

    • 维度表(按业务角色判断,通常是 lookup/reference 表)→ 不需要分区,分区只会增加 ROS 容器数
    • 事实表 → 继续判断
  2. 是否有按时间维度删除历史数据的需求?

    • 是 → 强烈建议分区
    • 否 → 继续判断
  3. 查询是否经常带日期/时间范围的谓词?

    • 是 → 建议分区(存储裁剪收益)
    • 否 → 继续判断
  4. 表大小是否超过 100 GB?

    • 是 → 可考虑分区以优化数据管理
    • 否 → 分区带来的 ROS 容器开销可能超过收益

关键原则:只对大事实表分区,不要对小表或维度表分区。 对小表分区会增加大量 ROS 容器,导致 catalog 膨胀,查询性能反而下降。详见 理解 Vertica 的分区

3.2 Step 2:评估当前分区方案是否合理

如果表已分区,需要评估分区粒度是否与数据增长和查询模式匹配。

检查当前分区粒度

SELECT
    p.table_schema,
    pr.anchor_table_name AS table_name,
    COUNT(DISTINCT p.partition_key) AS partition_count,
    MIN(p.partition_key) AS oldest_partition,
    MAX(p.partition_key) AS newest_partition
FROM v_monitor.partitions p
JOIN v_catalog.projections pr
    ON p.projection_id = pr.projection_id
WHERE p.table_schema = 'public'   -- 替换为目标 schema
  AND pr.anchor_table_name = 'your_table' -- 替换为目标表名
  AND p.node_name = (SELECT local_node_name())
GROUP BY 1, 2;

评估标准

指标 健康值 警告值 行动
partition_count < 365 > 500 考虑改粗分区粒度
分区时间跨度 < 3 年 > 5 年 考虑归档旧分区
每分区平均行数 > 100 万 < 10 万 分区粒度可能太细

3.3 Step 3:检查存储裁剪是否生效

分区的主要查询性能价值在于存储裁剪。如果查询没利用到分区裁剪,分区的收益就只剩数据管理。以下查询出自 理解 Vertica 的分区

SELECT
    node_name,
    event_details,
    transaction_id,
    statement_id
FROM v_monitor.query_events
WHERE event_type = 'PARTITIONS_ELIMINATED'
  AND event_timestamp > NOW() - INTERVAL '1 day'
ORDER BY event_timestamp DESC
LIMIT 20;

如何解读结果

  • Using only 1 stores out of 10 for projection xxx 表示 10 个存储容器中只用了 1 个,裁剪率 90%——这是优秀的结果。
  • 如果某张分区表的查询在 QUERY_EVENTS 中没有 PARTITIONS_ELIMINATED 事件,说明查询谓词和分区键不匹配,存储裁剪完全没生效

诊断存储裁剪失效的方法

PROFILE SELECT * FROM your_table WHERE date_col BETWEEN '2025-01-01' AND '2025-01-31';
-- 注意 PROFILE 输出的 transaction_id 和 statement_id

然后查询:

SELECT node_name, event_details
FROM v_monitor.query_events
WHERE event_type = 'PARTITIONS_ELIMINATED'
  AND transaction_id = :t_id       -- 替换为 PROFILE 输出的 transaction_id
  AND statement_id = :s_id;        -- 替换为 PROFILE 输出的 statement_id

3.4 Step 4:排查 ROS Pushback 风险

ROS Pushback 是新数据无法加载的直接信号。它发生在某投影的 ROS 容器数达到 1024 时。如果你已经到了这一步,说明前面的监控有盲区,需要立即处理。

检查最接近上限的投影

SELECT
    s.node_name,
    s.projection_name,
    COUNT(DISTINCT s.storage_oid) AS ros_count,
    1024 - COUNT(DISTINCT s.storage_oid) AS remaining_slots
FROM v_monitor.storage_containers s
GROUP BY 1, 2
HAVING COUNT(DISTINCT s.storage_oid) > 700
ORDER BY remaining_slots ASC;

如果 remaining_slots < 100,必须立即行动:

  1. 评估是否可以对旧分区执行 MOVE_PARTITIONS_TO_TABLE 释放槽位
  2. 评估是否可以将按天分区改为按月或使用分层分区(见 4.4)——既保留天级裁剪,又不爆 ROS 容器
  3. 如果数据不再需要,执行 DROP_PARTITIONS 直接删除

3.5 Step 5:检查重组操作的完成情况

如果最近执行了 ALTER TABLE ... PARTITION BY ... REORGANIZE,或者修改了分区表达式,需要确认重组是否正常运行。

-- 检查未完成的重组
SELECT
    table_schema,
    table_name,
    projection_name,
    partition_reorganize_percent
FROM v_monitor.partition_status
WHERE partition_reorganize_percent > 0
  AND partition_reorganize_percent < 100;

-- 检查重组过程中的错误
SELECT
    node_name,
    table_name,
    projection_name,
    message,
    hint
FROM v_monitor.partition_reorganize_errors
ORDER BY session_id DESC;

4. 解决方案(从快速见效到根本治理)

4.1 立即措施:为已有非分区大表添加分区

如果通过第 3.1 节的决策树确认某张表需要分区(优先看时间删除需求和查询谓词模式,而非单一大小数值),应尽快执行分区操作。

完整操作流程

-- 步骤 1:分析表中可用的时间列
-- 优先选择 DATE/TIMESTAMP 类型、NOT NULL、且经常作为查询谓词的列
SELECT column_name, data_type, is_nullable
FROM columns
WHERE table_schema = 'public'    -- 替换为实际 schema
  AND table_name = 'your_table'  -- 替换为实际表名
  AND data_type IN ('date', 'timestamp', 'timestamptz', 'int', 'integer')
ORDER BY ordinal_position;

-- 步骤 2:按日期列添加分区,并按月 GROUP BY 以减少 ROS 容器数
ALTER TABLE your_table
  PARTITION BY date_col::DATE
  GROUP BY DATE_TRUNC('month', date_col::DATE)
  REORGANIZE;
-- ALTER TABLE ... PARTITION BY ... REORGANIZE 是一步完成的原子操作:
-- 先设置分区表达式,然后立即启动后台重组。
-- 注意:此操作需要排他锁,建议在维护窗口执行。

⚠️ TIMESTAMPTZ 陷阱:如果分区列是 TIMESTAMPTZ 类型,直接用 ::DATE 会报错 ROLLBACK 2552: Cannot use meta function or non-deterministic function in PARTITION BY expression。原因是 TIMESTAMPTZ::DATE 依赖会话时区,被 Vertica 归类为 non-immutable。必须用 AT TIME ZONE 'UTC' 锚定时区:

-- ❌ TIMESTAMPTZ 列上不可用
ALTER TABLE your_table PARTITION BY timestamptz_col::DATE ...;
-- ✅ 正确写法
ALTER TABLE your_table PARTITION BY (timestamptz_col AT TIME ZONE 'UTC')::DATE ...;
ALTER TABLE your_table PARTITION BY DATE_TRUNC('month', timestamptz_col AT TIME ZONE 'UTC')::DATE ...;

为什么用 GROUP BY DATE_TRUNC('month', ...) 而不是直接按天分区? GROUP BY 子句指定了 Tuple Mover 合并 ROS 容器时的分组粒度。即使分区表达式按 date_col::DATE 区分每个日期,GROUP BY DATE_TRUNC('month', ...) 会告诉 Tuple Mover 将同月的不同日期的 ROS 容器合并在一起,大幅减少实际产生的 ROS 容器数。

4.2 立即措施:处理 ROS 容器数逼近上限

当 ROS 容器数超过 800 时,需要紧急降容(否则新数据加载会失败):

方案 A:归档旧分区(推荐,不丢数据)

-- 创建归档表(结构与原表相同,含 projection);同样需要 schema 前缀
CREATE TABLE schema.your_table_archive
  LIKE schema.your_table INCLUDING PROJECTIONS;

-- 将旧分区的数据搬到归档表
-- MOVE_PARTITIONS_TO_TABLE 是纯 catalog 操作(在分区内所有 ROS 容器已合并的前提下),速度极快
SELECT MOVE_PARTITIONS_TO_TABLE(
    'schema.your_table',          -- 必须带 schema 前缀,否则报错 table does not exist
    '20230101',                   -- 起始分区键值(替换为实际值)
    '20230630',                   -- 结束分区键值(替换为实际值)
    'schema.your_table_archive'
);

MOVE_PARTITIONS_TO_TABLE 的原理:它不实际移动数据,而是修改 catalog 中的元数据,将 ROS 容器的所有权从原表转移到归档表,因此即使 TB 级分区也是秒级完成。

方案 B:调整分区粒度(彻底根治)

-- 从按天分区改为按月分区
ALTER TABLE your_table
  PARTITION BY DATE_TRUNC('month', date_col)::DATE
  REORGANIZE;

注意:这一操作需要重写所有 ROS 容器的分区键,对 TB 级表可能需要数小时甚至更久。务必在维护窗口执行,并监控 PARTITION_STATUS 获取进度。

4.3 短期优化:为分区表创建分区范围投影

如果不想改变分区粒度,但需要控制特定投影的 ROS 容器数,可以使用分区范围投影(Partition Range Projection)。这种投影只包含指定分区范围内的数据,详见 分区范围投影 (Partition Range Projections)

-- 创建仅包含当年数据的投影(动态范围)
CREATE PROJECTION ytd_sales AS
  SELECT * FROM sales
  ORDER BY sale_date
  ON PARTITION RANGE BETWEEN DATE_TRUNC('year', NOW())::DATE AND NULL;

SELECT REFRESH();

为什么这样有效? ON PARTITION RANGE 限制了该投影只存储符合范围条件的分区数据。以上示例中,ytd_sales 投影只包含从今年 1 月 1 日至今的数据,ROS 容器数大幅减少。对于只查询近期数据的报表,查询优化器会自动选择这个 ROS 容器更少的投影。

4.4 根治方案:分层分区(Hierarchical Partitioning)

第 1.4 节暴露了一个根本矛盾:按天分区裁剪精准但 ROS 容器数线性增长,按月分区安全但裁剪粗糙。分层分区通过 CALENDAR_HIERARCHY_DAY 同时兼顾两者

-- 分区键按天,但 Tuple Mover 分层合并:
-- 近 2 个月保留天级 → 远 2-24 个月合并到月 → 2 年以上合并到年
ALTER TABLE your_table
  PARTITION BY date_col::DATE
  GROUP BY CALENDAR_HIERARCHY_DAY(date_col::DATE, 2, 2)
  REORGANIZE;

CALENDAR_HIERARCHY_DAY(col, active_months, active_days) 的参数含义

  • active_months(第一参数):多少个月内保留天级粒度(活跃期),超过后合并到月
  • active_days(第二参数):活跃期内多少天保持独立 ROS 容器,超过后合并到月

CALENDAR_HIERARCHY_DAY(date_col, 2, 2) 为例,Vertica 会根据当前日期自动将分区组织为三层:

近 2 个月的数据     → 按天独立存储(裁剪精确到天)
2 个月 ~ 2 年前的数据 → 同月合并为一个 ROS 容器(裁剪精确到月)
2 年以前的数据       → 同年合并为一个 ROS 容器(裁剪精确到年)

实际验证:以上 DDL 执行后,partition_group_expression 被 Vertica 展开为:

CASE
  WHEN datediff('year', date_col, now()) >= 2
    THEN date_trunc('year', date_col)::date       -- 2 年前 → 年级
  WHEN datediff('month', date_col, now()) >= 2
    THEN date_trunc('month', date_col)::date      -- 2 个月前 → 月级
  ELSE date_col                                    -- 近期 → 天级
END

为什么分层分区优于简单改粗粒度

方案 近期查询裁剪 远期 ROS 容器数 3 年总量
按天分区 天级 ✓ 1094 个 1095 🔴
按月分区 月级(30 天) 35 个 36 🟢
CALENDAR_HIERARCHY_DAY(, 2, 2) 天级 ✓ ~35 个(月+年级) ~40 🟢

分层分区让近期数据享有天级裁剪,同时通过将旧数据分层合并,保持了接近按月分区的 ROS 容器总量。

注意:分层分区依赖 Tuple Mover 在后台按 hierarchy 规则合并,首次执行 REORGANIZE 可能需要较长时间(与直接 ALTER TABLE 改粒度相当)。后续增量数据加载后会自动继承分层规则。

4.5 根本治理:分区键选择原则

选择分区键是分区设计中最关键的决策。以下是四个选择原则,按重要性排序:

原则 1:分区键必须匹配数据生命周期管理策略

如果你的保留策略是「保留 3 年,按月删除旧数据」,那么分区键必须是日期类型,粒度不应小于月。这是最核心的约束——如果分区键和数据保留策略不匹配,分区的最大优势(DROP_PARTITIONS)就完全失效。

原则 2:分区键应与最常见的查询谓词对齐

如果 80% 的查询都带 WHERE created_date BETWEEN ... AND ...,那么用 created_date 作为分区键可以让 80% 的查询都享受到存储裁剪。如果表同时有 created_dateupdated_date 两个常用时间字段,优先选择:

  • 数据加载时填充的字段(created_date):数据按加载时间自然聚集
  • 涉及数据删除策略的字段:支持 DROP_PARTITIONS 按此字段删除

原则 3:分区键的数据类型影响分区粒度控制

数据类型 分区表达式示例 典型粒度 适用场景
DATE date_col::DATE 每天数据量 > 1 亿行
DATE DATE_TRUNC('month', date_col)::DATE 每天数据量 < 1000 万行
TIMESTAMP date_col::DATE 与 DATE 相同,::DATE 截断时间部分
TIMESTAMPTZ (date_col AT TIME ZONE 'UTC')::DATE 必须用 AT TIME ZONE 'UTC' 锚定时区,否则报错 2552
INTEGER(YYYYMMDD) (date_int / 100)::INT 日期以整数存储的系统

原则 4:分区键列必须有 NOT NULL 约束

Vertica 要求分区键不能包含 NULL 值,且分区表达式必须使用 immutable 函数(不依赖会话状态、时区等可变因素)。如果分区列可能为 NULL,需在创建表时添加 NOT NULL 约束或在分区表达式中处理 NULL,否则数据加载会失败。TIMESTAMPTZ 列需要特别注意——直接用 ::DATEDATE_TRUNC 会因时区依赖被拒绝(错误 2552),必须用 AT TIME ZONE 'UTC' 锚定(详见上表)。

4.6 从非分区表迁移到分区表的完整操作指南

这是实践中最常见的场景。以下是经过验证的完整操作步骤:

方案 A:直接 ALTER TABLE(适用于 < 500 GB 的表)

-- 第 1 步:确认目标表大小(is_segmented 区分计算,分段/非分段表均准确;total_rows 已消除 buddy 重复)
WITH proj_rows AS (
    SELECT
        ps.projection_id,
        CASE WHEN pr.is_segmented
             THEN SUM(ps.row_count)       -- 分段表:跨节点汇总 = 该投影总行数
             ELSE MAX(ps.row_count)       -- 非分段表:每节点全量副本,取任一节点
        END AS proj_total_rows
    FROM v_monitor.projection_storage ps
    JOIN v_catalog.projections pr ON ps.projection_id = pr.projection_id
    WHERE ps.anchor_table_name = 'your_table'
    GROUP BY ps.projection_id, pr.is_segmented
)
SELECT
    SUM(ps.used_bytes) / (1024*1024*1024) AS size_gb,
    AVG(pr.proj_total_rows)::INT AS total_rows
FROM v_monitor.projection_storage ps
JOIN proj_rows pr ON ps.projection_id = pr.projection_id
WHERE ps.anchor_table_name = 'your_table';

-- 第 2 步:执行分区 + 重组(维护窗口内)
ALTER TABLE your_table
  PARTITION BY date_col::DATE
  GROUP BY DATE_TRUNC('month', date_col::DATE)
  REORGANIZE;

-- 第 3 步:监控重组进度
SELECT
    table_name,
    projection_name,
    partition_reorganize_percent
FROM v_monitor.partition_status
WHERE table_name = 'your_table'
ORDER BY partition_reorganize_percent ASC;

方案 B:创建新分区表 + 数据迁移(适用于 > 500 GB 的表)

对于超大表,直接 ALTER TABLE ... REORGANIZE 可能需要数天。更可控的方案是创建新表然后切换:

-- 第 1 步:创建结构相同的新表,加上分区定义
CREATE TABLE your_table_new
  LIKE your_table INCLUDING PROJECTIONS;

ALTER TABLE your_table_new
  PARTITION BY date_col::DATE
  GROUP BY DATE_TRUNC('month', date_col::DATE);

-- 第 2 步:分批迁移数据(可在业务低峰期多次执行)
INSERT /*+ DIRECT */ INTO your_table_new
  SELECT * FROM your_table
  WHERE date_col BETWEEN '2024-01-01' AND '2024-01-31';
-- 逐月重复,直到全部迁移完成

-- 第 3 步:切换表名
ALTER TABLE your_table RENAME TO your_table_old;
ALTER TABLE your_table_new RENAME TO your_table;

-- 第 4 步:验证后删除旧表
DROP TABLE your_table_old;

为什么大表推荐分批迁移? ALTER TABLE ... REORGANIZE 虽然自动,但对 TB 级表运行时可能持续数天,且一旦开始无法暂停。分批 INSERT /*+ DIRECT */ 允许你控制每次迁移的数据量和时间窗口,出现问题可随时中断。


5. 深入案例

5.1 虚构案例:非分区大表查询性能持续恶化

📝 虚构案例

场景描述:某电商平台的 order_detail 表存储 3 年订单明细数据,总计 15 亿行、约 800 GB。表未分区,查询始终扫描全表。随着数据累积,日报查询从 3 秒恶化到 45 秒。

诊断过程

-- 1. 确认表大小和分区状态
-- CTE 根据 is_segmented 区分计算,避免 buddy 投影和节点重复计数
WITH proj_rows AS (
    SELECT
        ps.projection_id,
        CASE WHEN pr.is_segmented THEN SUM(ps.row_count) ELSE MAX(ps.row_count) END AS total_rows
    FROM v_monitor.projection_storage ps
    JOIN v_catalog.projections pr ON ps.projection_id = pr.projection_id
    WHERE ps.anchor_table_name = 'order_detail'
    GROUP BY ps.projection_id, pr.is_segmented
)
SELECT
    ps.anchor_table_schema AS table_schema,
    ps.anchor_table_name AS table_name,
    t.partition_expression,
    AVG(pr.total_rows) / 1000000000.0 AS rows_billion,
    SUM(ps.used_bytes) / (1024*1024*1024) AS size_gb
FROM v_monitor.projection_storage ps
JOIN v_catalog.tables t ON ps.anchor_table_id = t.table_id
JOIN proj_rows pr ON ps.projection_id = pr.projection_id
WHERE ps.anchor_table_name = 'order_detail'
GROUP BY 1, 2, 3;
-- 输出:partition_expression = '' (空 = 未分区),rows = 1.5 billion, size = 800 GB

-- 2. 检查典型日报查询的存储裁剪情况
-- 先 PROFILE 慢查询
PROFILE SELECT region, SUM(amount)
FROM order_detail
WHERE order_date BETWEEN '2026-05-01' AND '2026-05-31'
GROUP BY region;

-- 在 QUERY_EVENTS 中搜索 PARTITIONS_ELIMINATED
-- 结果:无此事件 → 存储裁剪完全未生效,每次查询扫描全部 15 亿行

根因分析:表未分区,查询优化器无法跳过任何存储容器。即使 WHERE order_date 只查询 5 月份数据,Vertica 也必须读取所有 ROS 容器并逐行过滤。

修复方案:按订单日期添加分区

ALTER TABLE order_detail
  PARTITION BY order_date::DATE
  GROUP BY DATE_TRUNC('month', order_date::DATE)
  REORGANIZE;

效果对比

指标 分区前 分区后
日报查询响应时间 45 秒 2.3 秒
扫描数据量 800 GB(全表) ~22 GB(单月分区)
存储裁剪率 0% 97%(36 分区中跳过 35 个)
月度数据删除耗时 ~6 小时(Bulk DELETE) < 1 秒(DROP_PARTITIONS)

5.2 虚构案例:按天分区导致 ROS Pushback

📝 虚构案例

场景描述:某金融机构的 transaction_log 表按 trans_date::DATE 分区,保留 4 年数据。数据库已稳定运行 3 年多,某日夜间 ETL 加载失败,报错 Too many ROS containers for projection transaction_log_b0

诊断过程

-- 1. 快速确认 ROS 容器数和分区数
SELECT
    s.projection_name,
    COUNT(DISTINCT s.storage_oid) AS ros_count,
    COUNT(DISTINCT p.partition_key) AS partition_count
FROM storage_containers s
LEFT JOIN v_monitor.partitions p
    ON s.storage_oid = p.ros_id
WHERE s.projection_name LIKE 'transaction_log%'
  AND s.node_name = (SELECT local_node_name())
GROUP BY 1;
-- 输出:ros_count = 1024, partition_count = 1010

-- 2. 确认分区跨度
SELECT
    MIN(p.partition_key) AS oldest,
    MAX(p.partition_key) AS newest,
    COUNT(DISTINCT p.partition_key) AS total
FROM v_monitor.partitions p
JOIN v_catalog.projections pr
    ON p.projection_id = pr.projection_id
WHERE pr.anchor_table_name = 'transaction_log'
  AND p.node_name = (SELECT local_node_name());
-- 输出:oldest=20220301, newest=20260531, total=1010

根因分析:按天分区 × 4 年 ≈ 1460 个分区,远超 1024 上限。活跃分区自身还占用了额外的 14 个 ROS 容器槽位,导致总量抵达 1024 后无法加载新数据。

修复方案

-- 方案 A:将 2024 年之前的分区归档到历史表(快速释放槽位)
CREATE TABLE transaction_log_archive
  LIKE transaction_log INCLUDING PROJECTIONS;

SELECT MOVE_PARTITIONS_TO_TABLE(
    'public.transaction_log',
    '20220301',
    '20231231',
    'public.transaction_log_archive'
);

-- 方案 B:改为分层分区(根治,保留天级裁剪 + 控制 ROS 容器)
-- 也可直接用 DATE_TRUNC('month', ...) 改为纯按月分区
ALTER TABLE transaction_log
  PARTITION BY trans_date::DATE
  GROUP BY CALENDAR_HIERARCHY_DAY(trans_date::DATE, 2, 2)
  REORGANIZE;

效果对比

指标 修复前 修复后
ROS 容器数 1024(已达上限) 62(24 个月 + 活跃分区容器)
ETL 加载 失败(ROS Pushback) 正常
月度查询 裁剪 96.7%(29/30 月) 裁剪 95.8%(23/24 月)
未来 3 年 ROS 预估 N/A(已满) ~110(安全范围)

5.3 真实案例:分区数据管理避免宕机恢复延迟

📋 真实案例

场景摘要:某运营商的多租户 Vertica 集群中,多个租户数据库发生夯死问题。故障复盘总结中明确提出了一条与分区相关的关键建议。

关键发现:故障报告中指出,「避免对大表执行 Delete 操作」——节点宕机后若发生大量 delete 操作,将导致节点故障恢复时间变长。报告明确建议使用分区进行数据生命周期管理,用 DROP_PARTITIONS 替代 DELETE。

技术解释:DELETE 在 Vertica 中不真正删除数据,而是创建 delete vector。大量 delete vector 会延长恢复时的 replay delete 过程,因为系统需要逐一重放所有删除标记。相比之下,DROP_PARTITIONS 是纯 catalog 操作——它直接解除 ROS 容器的所有权引用,无需逐行重放

效果对比

操作类型 执行耗时(1 亿行数据) 对恢复的影响
DELETE FROM table WHERE date_col < '2023-01-01' 30-60 分钟 产生大量 delete vector,恢复时需逐条 replay
SELECT DROP_PARTITIONS('schema.table', '20220101', '20220101') < 1 秒 纯 catalog 操作,不影响恢复速度

6. 完整诊断流程实战

📝 虚构场景 · 完整演练

背景:某数据分析团队反馈「最近几周日报越来越慢,而且昨天凌晨的月度数据清理脚本跑了 4 小时还没完成」。你被要求排查并修复。

时间线

09:15 — 全局扫描,了解环境

-- 确认数据库版本
SELECT version();
-- 输出:Vertica Analytic Database v26.1.0-2

-- 找出最大的几张未分区表
WITH proj_rows AS (
    SELECT
        ps.anchor_table_id,
        ps.projection_id,
        CASE WHEN pr.is_segmented THEN SUM(ps.row_count) ELSE MAX(ps.row_count) END AS total_rows
    FROM v_monitor.projection_storage ps
    JOIN v_catalog.projections pr ON ps.projection_id = pr.projection_id
    GROUP BY ps.anchor_table_id, ps.projection_id, pr.is_segmented
)
SELECT
    t.table_schema,
    t.table_name,
    SUM(ps.used_bytes) / (1024*1024*1024) AS size_gb,
    AVG(pr.total_rows)::INT AS total_rows,
    t.partition_expression
FROM v_catalog.tables t
JOIN v_monitor.projection_storage ps ON t.table_id = ps.anchor_table_id
JOIN proj_rows pr ON t.table_id = pr.anchor_table_id AND ps.projection_id = pr.projection_id
WHERE t.partition_expression = ''
GROUP BY 1, 2, 5
  HAVING SUM(ps.used_bytes) > 50 * 1024*1024*1024
ORDER BY size_gb DESC;
-- 输出:daily_metrics, 2.3 TB, partition_expression = ''
--        event_log, 850 GB, partition_expression = ''
--        user_activity, 320 GB, partition_expression = ''

判断daily_metrics(2.3 TB)是最大的未分区表,且表名暗示是按天存储的指标数据——非常符合分区条件。

09:25 — 确认查询模式

-- 检查最近的慢查询,确认查询谓词模式
SELECT
    request,
    request_duration_ms / 1000.0 AS duration_sec
FROM v_monitor.query_requests
WHERE request ILIKE '%daily_metrics%'
  AND start_timestamp > NOW() - INTERVAL '7 days'
ORDER BY request_duration_ms DESC
LIMIT 5;
-- 输出:所有慢查询都包含 WHERE metric_date BETWEEN ... AND ...

判断:查询谓词模式与日期列强相关——分区对存储裁剪收益极高。

09:35 — 检查清理脚本

-- 检查昨晚的月度清理 SQL
SELECT
    request,
    request_duration_ms / 1000.0 / 3600 AS duration_hours
FROM v_monitor.query_requests
WHERE request ILIKE '%DELETE%daily_metrics%'
  AND start_timestamp > NOW() - INTERVAL '24 hours';
-- 输出:DELETE FROM daily_metrics WHERE metric_date < '2025-06-01'
--       duration_hours = 4.2 hours

判断:DELETE 在大表上执行极慢,且留下大量 delete vector。用 DROP_PARTITIONS 替代只需秒级。

10:00 — 执行修复

-- ALTER TABLE + REORGANIZE 一步完成
ALTER TABLE daily_metrics
  PARTITION BY metric_date::DATE
  GROUP BY DATE_TRUNC('month', metric_date::DATE)
  REORGANIZE;
-- 对于 2.3 TB 的表,REORGANIZE 预计需要 3-6 小时

-- 监控进度
SELECT table_name, projection_name, partition_reorganize_percent
FROM v_monitor.partition_status
WHERE table_name = 'daily_metrics';

16:30 — 验证结果

-- REORGANIZE 100% 完成,验证存储裁剪
PROFILE SELECT region, SUM(value)
FROM daily_metrics
WHERE metric_date BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY region;

-- 查询 PARTITIONS_ELIMINATED 事件
SELECT node_name, event_details
FROM v_monitor.query_events
WHERE event_type = 'PARTITIONS_ELIMINATED'
  AND transaction_id = :t_id;
-- 输出:Using only 1 stores out of 26 for projection daily_metrics_b0
-- 裁剪率:25/26 = 96.2%

-- 验证 DROP_PARTITIONS 替代 DELETE
SELECT DROP_PARTITIONS('public.daily_metrics', '20250501', '20250501');
-- DROP PARTITION 秒级完成!

最终效果

指标 修复前 修复后
日报查询时间 48 秒 2.1 秒
月度数据清理 4.2 小时(DELETE) < 1 秒(DROP_PARTITIONS)
存储裁剪率 0% 96%
月度 ROS 容器数 N/A 32(安全)

7. 快速诊断 SQL 工具箱

诊断目标 SQL 说明
找出未分区的大表 WITH p AS (SELECT ps.anchor_table_id, ps.projection_id, CASE WHEN pr.is_segmented THEN SUM(ps.row_count) ELSE MAX(ps.row_count) END AS r FROM projection_storage ps JOIN v_catalog.projections pr ON ps.projection_id=pr.projection_id GROUP BY 1,2,pr.is_segmented) SELECT t.table_schema, t.table_name, SUM(ps.used_bytes)/(1024*1024*1024) AS size_gb, AVG(p.r)::INT AS total_rows FROM v_catalog.tables t JOIN projection_storage ps ON t.table_id=ps.anchor_table_id JOIN p ON t.table_id=p.anchor_table_id AND ps.projection_id=p.projection_id WHERE t.partition_expression='' GROUP BY 1,2 HAVING SUM(ps.used_bytes) > 50*1024*1024*1024 ORDER BY size_gb DESC 50 GB 为初筛门槛(含 buddy 投影,约 25 GB 逻辑数据),实际分区决策见 §3.1
查看分区表概况 SELECT s, COUNT(DISTINCT t) AS tables, COUNT(*) AS partitions, SUM(r) AS rows, SUM(b)/(1024*1024*1024) AS gb FROM (SELECT p.table_schema s, pr.anchor_table_name t, p.partition_key k, CASE WHEN BOOL_AND(pr.is_segmented) THEN SUM(p.ros_row_count) ELSE SUM(p.ros_row_count)/COUNT(DISTINCT p.node_name) END r, CASE WHEN BOOL_AND(pr.is_segmented) THEN SUM(p.ros_size_bytes) ELSE SUM(p.ros_size_bytes)/COUNT(DISTINCT p.node_name) END b FROM v_monitor.partitions p JOIN (SELECT DISTINCT projection_id, anchor_table_name, is_segmented FROM v_catalog.projections WHERE projection_id IN (SELECT MIN(projection_id) FROM v_catalog.projections GROUP BY projection_schema, anchor_table_name)) pr ON p.projection_id=pr.projection_id GROUP BY 1,2,3) foo GROUP BY 1 ORDER BY gb DESC partitions > 365 需关注;两层聚合:同时消除 buddy 重复 + 非分段表节点倍率
按投影查看 ROS 容器数 SELECT node_name, projection_name, COUNT(DISTINCT storage_oid) AS ros_count FROM v_monitor.storage_containers GROUP BY 1,2 HAVING COUNT(DISTINCT storage_oid) > 300 ORDER BY ros_count DESC 接近 1024 时立即行动
检查存储裁剪效果 SELECT node_name, event_details FROM v_monitor.query_events WHERE event_type='PARTITIONS_ELIMINATED' AND event_timestamp > NOW() - INTERVAL '1 day' ORDER BY event_timestamp DESC LIMIT 20 无此事件 → 裁剪未生效
监控重组进度 SELECT table_name, projection_name, partition_reorganize_percent FROM v_monitor.partition_status WHERE partition_reorganize_percent < 100 停滞不增需查错误
查看分区详细信息 SELECT p.table_schema, pr.anchor_table_name AS table_name, p.partition_key, p.ros_row_count, p.ros_size_bytes/(1024*1024) AS size_mb FROM v_monitor.partitions p JOIN v_catalog.projections pr ON p.projection_id=pr.projection_id WHERE pr.anchor_table_name=':table' AND p.node_name=(SELECT local_node_name()) ORDER BY p.partition_key 替换 :table 为目标表名
添加分区(含重组) ALTER TABLE :table PARTITION BY :col::DATE GROUP BY DATE_TRUNC('month', :col::DATE) REORGANIZE 维护窗口执行
添加分层分区(兼顾天级裁剪和月级合并) ALTER TABLE :table PARTITION BY :col::DATE GROUP BY CALENDAR_HIERARCHY_DAY(:col::DATE, 2, 2) REORGANIZE (2,2) 表示近 2 月天级、2 月~2 年月级、2 年+年级;参数可按需调整
删除分区(替代 DELETE) SELECT DROP_PARTITIONS(':schema.:table', ':min_key', ':max_key') 范围删除,秒级完成,纯 catalog 操作;单分区 min=max
归档旧分区 SELECT MOVE_PARTITIONS_TO_TABLE(':schema.:table', ':min_key', ':max_key', ':schema.:archive_table') 先创建归档表 LIKE ... INCLUDING PROJECTIONS;表名必须带 schema 前缀
检查 catalog 大小 SELECT node_name, MAX(catalog_size_in_MB) AS catalog_mb FROM (SELECT node_name, SUM((total_memory_max_value-free_memory_min_value))/(1024*1024) AS catalog_size_in_MB FROM dc_allocation_pool_statistics_by_second GROUP BY 1, TRUNC(("time")::TIMESTAMP,'SS')) foo GROUP BY 1 > 15 GB 需关注

8. 最佳实践清单

按投入产出比从高到低排列:

  1. 大事实表按时间字段分区是基本操作 — 如果表有时间维度的查询/删除需求,分区就是必须的,不要等到几百 GB 才行动。投入(一条 DDL 语句)远小于回报(查询加速 + 管理简化)。优先用 3.1 决策树判断,而非单一大小阈值。
  2. 分区粒度选月不选天,除非用分层分区 — 按月分区 ROI 最高。如果确实需要天级裁剪,用 CALENDAR_HIERARCHY_DAY(col, 2, 2) 实现「近期天级、远期月/年级」的分层合并,详见第 4.4 节。简单按天分区 × 3 年 = 1095 个容器(超限),分层分区 × 3 年 ≈ 40 个(安全)。
  3. 用 DROP_PARTITIONS 替代 DELETE 做数据清理 — DROP_PARTITIONS 是纯 catalog 操作,秒级完成,不产生 delete vector。DELETE 在 TB 级表上可能跑数小时,且留下的 delete vector 会增加恢复时间(详见第 5.3 节真实案例)。
  4. 使用 GROUP BY 子句控制合并粒度PARTITION BY date_col::DATE GROUP BY DATE_TRUNC('month', date_col::DATE) 让你既能按天查询裁剪,又不会为每一天创建一个独立的 ROS 容器。Tuple Mover 会在同一 GROUP 的日期之间合并 ROS 容器。
  5. 不要在维度表和小表上分区 — 小表分区增加的 ROS 容器开销远超裁剪收益。如果一张表的总 ROS 容器数只有个位数,分区反而会让它变成几十个,查询优化器需要扫描更多元数据,性能可能下降。
  6. 分区键必须 NOT NULL — NULL 值会导致数据加载失败。建议在列定义中加 NOT NULL 约束。
  7. 监控 ROS 容器数比监控分区数更重要 — 分区数只是中间指标,真正的硬限制是每投影每节点 1024 个 ROS 容器。当你拥有超过 365 个分区时,每周至少检查一次 ROS 容器数趋势。
  8. 执行 ALTER TABLE ... REORGANIZE 前评估时间窗口 — 对于 TB 级表,重组可能需要数小时。大表推荐用分批 INSERT 到新表的方案(见 4.6 方案 B),更可控且可中断。
  9. 创建 MOVE_PARTITIONS_TO_TABLE 的归档表时带上 INCLUDING PROJECTIONS — 否则归档表不会有预设的 projection,后续查询性能会很差。
  10. 分区键选择优先匹配数据生命周期策略 — 如果数据按「创建日期」保留 3 年,那么分区键应该用创建日期,即使大多数查询用的是「更新日期」。因为 DROP_PARTITIONS 必须按分区键删除,数据管理需求优先于查询优化。

扩展阅读