Vertica 表使用情况分析与资源优化¶
作者:JiangChong | 发布时间:2026-05-20
适用场景框: 当你需要清理数据库中不再使用的表以释放存储空间、减少 Catalog 膨胀、降低 Tuple Mover mergeout 开销,或者当你收到「磁盘空间不足」告警却不知道哪些表可以被安全清理时——本文提供从诊断到执行的全流程方法。
关联文章:
- Vertica 冷热数据管理与成本优化 — 冷热数据分层归档策略,补充本文的「不删但归档」方案
- Vertica 数据删除最佳实践 — DELETE / PURGE / DROP_PARTITIONS 等数据删除机制详解
- Vertica 表分区策略选择指南 — 分区键选择与分区表迁移,减少按分区清理的复杂度
- Vertica 大表统计信息维护最佳实践 — 统计信息收集策略,与表使用分析的统计覆盖率检查联动
- Vertica 资源拒绝排查与资源池调优 — 存储配额告警到达上限时的应急处理
理解全文脉络: 文章从「为什么 Vertica 需要主动管理表生命周期」的原理讲起(第 1 节),然后从全局存储监控切入(第 2 节),接着按「表访问频率 → 存储占用 → 删除残留」的顺序逐步定位问题表(第 3 节),再给出从快速见效到根本治理的分层解决方案(第 4 节),最后通过多个案例和第 6 节的完整演练串联全部知识点。如果你只想快速查 SQL,直接跳到第 7 节的工具箱表格。
第 1 节:原理理解¶
1.1 Vertica 为什么需要关注表使用情况?¶
在传统数据库中,表通常只是「数据容器」——创建了就一直在那里,偶尔删几张旧表也不会对性能产生显著影响。但在 Vertica 中,每张表不只是数据,它还会生成投影(projection)、在 Catalog 中注册元数据、在 Tuple Mover mergeout 时参与 ROS 容器的合并读写。
这意味着:一张创建后从未被查询的表,仍然实实在在地影响着数据库:
- 存储成本:数据在主存储(NVMe/SSD)中占位,Eon 模式虽然数据在 S3 上,但 Depot 缓存仍可能被意外驱逐
- Catalog 膨胀:每张表在 Catalog 中都有对应的元数据条目,Catalog 越大,数据库启动、备份和系统表查询就越慢
- Mergeout 干扰:Tuple Mover(TM)不会区分表的新旧——即使表已经几年没被查询,TM 仍然在 mergeout 时处理它的 ROS 容器
- 统计信息负担:每张表的列都参与
ANALYZE_STATISTICS或ANALYZE_ROW_COUNT的收集范围
打个比方: 表就像仓库里的货架。即使某个货架上的货物(数据)已经没人需要了,仓库管理员(Tuple Mover)还是要定期整理它,仓库登记簿(Catalog)上也要记着它的位置。货架越多,管理员跑断腿。
1.2 Vertica 如何记录表的访问历史?¶
Vertica 通过 Data Collector(DC) 组件持续记录系统活动。与表使用分析直接相关的有两个关键视角:
| 视角 | 系统表 | 记录粒度 | 保留周期(默认) | 用途 |
|---|---|---|---|---|
| 投影使用 | v_monitor.projection_usage |
每个查询×每个投影 | 取决于 QueryExecutions DC 组件 |
查询到哪些表 |
| 查询记录 | v_monitor.query_requests |
每个查询 | 取决于 QueryExecutions DC 组件 |
从 SQL 文本提取表名 |
推荐优先使用 projection_usage,因为它是结构化的投影→锚定表关系,不需要解析 SQL 文本。query_requests 作为补充——当你需要知道哪些表被 DML(INSERT/COPY/UPDATE/DELETE)写入时,因为 projection_usage 只记录查询中实际读取的投影。
重要前提:DC 组件的保留周期限制了你能回溯的时间范围。
QueryExecutions组件的默认DISK_SIZE_KB = 20000(约 20MB),INTERVAL_SET = false(不限制时间间隔,但受磁盘配额限制)。在典型生产负载下这通常是 7-30 天。如果 DC 只保留了 7 天的数据,你就只能分析最近 7 天的表使用情况——这是本文所有 SQL 的前提约束,后面会教你怎么检查和调整这个周期。
1.3 表使用分析的三维框架¶
本文的诊断逻辑按以下三维推进:
| 维度 | 回答的问题 | 核心系统表 |
|---|---|---|
| 存储占用 | 哪些表占空间最大? | projection_storage |
| 访问频率 | 哪些表最近被查询过?哪些从未被查? | projection_usage |
| 删除残留 | 哪些表有大量已删除但未回收的空间? | delete_vectors, storage_containers |
第 2 节:系统级监控——从宏观入手¶
在定位具体的问题表之前,先从全局了解存储压力和数据保留范围。
2.1 全局磁盘使用概览¶
SELECT node_name,
storage_usage,
storage_status,
disk_space_used_mb,
disk_space_free_mb,
ROUND(disk_space_free_mb * 100.0 / NULLIF(disk_space_free_mb + disk_space_used_mb, 0), 1) AS free_pct
FROM v_monitor.disk_storage
ORDER BY node_name, storage_usage;
如何解读结果:
free_pct < 20%:存储压力较高,清理未使用表可以释放空间storage_usage = 'CATALOG'vs'DATA,TEMP':Catalog 路径通常较小(此处为 4.5GB),DATA/TEMP 路径是主要的数据存储位置- 各节点不一致:如果节点 1 的
free_pct显著低于其他节点(如此处的 node0001 DATA=13% 而 node0002=26%),说明可能存在数据倾斜或该节点承载了更多的投影副本
2.2 检查 DC 组件保留周期¶
这是最容易跳过但最关键的一步。 如果 QueryExecutions 组件只保留了 3 天的数据,那么「从未被查询」的结论只对最近 3 天有效——无法排除表在 4 天前被大量使用。
SELECT component, description, table_name,
disk_size_kb, interval_set, interval_time,
memory_buffer_size_kb,
TO_CHAR(first_time, 'YYYY-MM-DD HH24:MI') AS first_time,
TO_CHAR(last_time, 'YYYY-MM-DD HH24:MI') AS last_time,
kb_per_day
FROM v_monitor.data_collector
WHERE component = 'QueryExecutions'
ORDER BY node_name;
如何解读结果:
first_time/last_time:直接告诉你 DC 数据的历史范围。如果first_time是 3 天前,那你的表使用分析最多只能覆盖 3 天interval_set = false:表示不限制时间间隔,由disk_size_kb磁盘配额控制保留量kb_per_day:每天产生的数据量,除以disk_size_kb可以估算保留天数。例如kb_per_day = 5000,disk_size_kb = 20000,大概保留 4 天
如果保留周期太短,需要延长:
-- 将 QueryExecutions 的磁盘配额扩大到 100MB(约 4-8 周数据,取决于查询量)
-- 注意:这会消耗更多磁盘空间
SELECT SET_DATA_COLLECTOR_POLICY('QueryExecutions', '100000', false);
⚠️ 注意:增大
disk_size_kb会立即生效,但之前的数据已经丢失无法恢复。所以建议在开始定期分析之前就先扩大保留周期。
2.3 全局表存储排名¶
-- 注意:used_bytes 含所有投影副本(K-safe buddy + 跨节点),代表实际磁盘占用
-- approx_row_count 区分了分段表(SUM/投影数)和非分段表(每节点全副本,取一节点)
SELECT ps.anchor_table_schema,
ps.anchor_table_name,
SUM(ps.used_bytes) AS total_bytes,
ROUND(SUM(ps.used_bytes) / 1073741824.0, 2) AS total_gb,
CASE WHEN BOOL_AND(p.is_segmented)
THEN SUM(ps.row_count) / COUNT(DISTINCT ps.projection_name)
ELSE MAX(ps.row_count)
END AS approx_row_count,
SUM(ps.ros_count) AS total_ros,
COUNT(DISTINCT ps.projection_name) AS proj_count
FROM v_monitor.projection_storage ps
JOIN (SELECT DISTINCT projection_id, projection_name, is_segmented FROM v_catalog.projections) p
ON ps.projection_id = p.projection_id
AND ps.projection_name = p.projection_name
GROUP BY ps.anchor_table_schema, ps.anchor_table_name
ORDER BY total_bytes DESC
LIMIT 20;
如何解读结果:
total_bytes含所有投影副本(K-safe buddy projection + 跨节点分段),代表该表的实际磁盘占用。例如 K-safe=1 + 3 节点 = 6 份副本的字节总和。删表时释放的就是这个量approx_row_count自动处理两种投影类型:分段表(is_segmented=true)用SUM / 投影数消去 buddy 翻倍;非分段表(unsegmented/replicated)每节点存全量,用MAX取一节点即可total_ros很高(如 > 500):该表的 ROS 容器碎片化严重,Tuple Mover 处理负担重,即使表不大也建议关注total_gb占比过大(单表 > 总存储的 20%):大表,清理前务必确认业务影响proj_count:记录了该表有多少个投影。proj_count = 2意味着有超投影及其 buddy projection(K-safe=1 集群),正常。如果proj_count > 4说明该表有额外的自定义投影
第 3 节:逐步定位根因——从宏观到微观¶
步骤 1:识别「从未被查询」的完全未使用表¶
做什么: 列出 v_catalog.tables 中存在,但 v_monitor.projection_usage 中没有任何记录的表。这些表在 DC 保留周期内从未出现在任何查询的投影访问中。
-- 找出 DC 保留周期内从未被查询的表
SELECT t.table_schema,
t.table_name,
t.create_time,
t.owner_name,
COALESCE(ps_sum.total_bytes, 0) AS storage_bytes,
ROUND(COALESCE(ps_sum.total_bytes, 0) / 1073741824.0, 2) AS storage_gb,
COALESCE(ps_sum.total_rows, 0) AS row_count
FROM v_catalog.tables t
LEFT JOIN (
SELECT ps2.anchor_table_schema, ps2.anchor_table_name,
SUM(ps2.used_bytes) AS total_bytes,
CASE WHEN BOOL_AND(p2.is_segmented)
THEN SUM(ps2.row_count) / COUNT(DISTINCT ps2.projection_name)
ELSE MAX(ps2.row_count)
END AS total_rows
FROM v_monitor.projection_storage ps2
JOIN (SELECT DISTINCT projection_id, projection_name, is_segmented FROM v_catalog.projections) p2
ON ps2.projection_id = p2.projection_id
AND ps2.projection_name = p2.projection_name
GROUP BY ps2.anchor_table_schema, ps2.anchor_table_name
) ps_sum
ON t.table_schema = ps_sum.anchor_table_schema
AND t.table_name = ps_sum.anchor_table_name
WHERE t.table_schema NOT IN ('v_catalog', 'v_monitor', 'v_internal')
AND NOT t.is_system_table
AND (t.table_schema, t.table_name) NOT IN (
SELECT DISTINCT pu.anchor_table_schema, pu.anchor_table_name
FROM v_monitor.projection_usage pu
)
ORDER BY storage_bytes DESC;
如何解读:
- 结果非空且有数 GB 的表:这些是优先清理目标,释放空间效果立竿见影
storage_bytes = 0但表存在:表可能只有元数据(空表或只有投影定义但无数据),删除它们可以减少 Catalog 条目create_time距今很久(如 > 6 个月):长期未使用的表,清理决策风险较低
如果不是,进入步骤 2: 如果所有表都有访问记录,说明没有完全未使用的表。接下来找「低频使用」的表。
步骤 2:识别「低频使用」表——按最后访问时间排序¶
做什么: 找出最近访问时间最久远的表,即使它们偶尔被查询。
-- 按最后访问时间排序,找出低频使用表
SELECT pu.anchor_table_schema,
pu.anchor_table_name,
MAX(pu.query_start_timestamp) AS last_accessed,
COUNT(DISTINCT pu.request_id) AS query_count,
COALESCE(ps_sum.total_bytes, 0) AS storage_bytes,
ROUND(COALESCE(ps_sum.total_bytes, 0) / 1073741824.0, 2) AS storage_gb
FROM v_monitor.projection_usage pu
LEFT JOIN (
SELECT anchor_table_schema, anchor_table_name,
SUM(used_bytes) AS total_bytes
FROM v_monitor.projection_storage
GROUP BY anchor_table_schema, anchor_table_name
) ps_sum
ON pu.anchor_table_schema = ps_sum.anchor_table_schema
AND pu.anchor_table_name = ps_sum.anchor_table_name
GROUP BY pu.anchor_table_schema, pu.anchor_table_name, ps_sum.total_bytes
ORDER BY last_accessed ASC
LIMIT 20;
如何解读:
last_accessed距今 > 30 天:该表可能有归档价值。结合storage_bytes看——如果存储大 + 访问少 = 高优先级清理/归档query_count很低(如 < 5)但last_accessed很近:可能有定时任务偶尔全表扫描。需要确认业务方是否还需要query_count高但last_accessed久远:说明之前曾高频使用但最近停了——可能是已下线的业务
如果不是,进入步骤 3: 如果所有表近期都有访问,说明表利用率良好。接下来检查是否有删除残留浪费。
步骤 3:识别「删除残留」浪费空间¶
做什么: Vertica 使用 delete vector(DV)标记已删除行,物理空间在 mergeout 完成后才回收。大量 DV 意味着存储空间被「占着不用」。
-- 检查 delete vectors 的空间占用
SELECT schema_name,
projection_name,
SUM(deleted_row_count) AS total_deleted_rows,
SUM(used_bytes) AS dv_bytes,
ROUND(SUM(used_bytes) / 1073741824.0, 2) AS dv_gb,
COUNT(*) AS dv_count
FROM v_monitor.delete_vectors
GROUP BY schema_name, projection_name
ORDER BY dv_bytes DESC
LIMIT 20;
如何解读:
dv_bytes很大(如 > 1GB),但表本身很小:说明该表执行了大量 DELETE 但 mergeout 尚未完成——SELECT PURGE_TABLE('schema.table_name');可以强制回收dv_count很高(如 > 100):碎片化的 delete vector,可能需要调整 Tuple Mover 的 mergeout 策略- 结果为空:测试环境或少量删除的正常状态
步骤 4:检查 ROS 容器碎片化¶
-- 识别 ROS 容器过多的表
-- 注意:approx_row_count 区分分段/非分段,与 projection_storage 同理
SELECT sc.schema_name,
sc.projection_name,
CASE WHEN BOOL_AND(p.is_segmented)
THEN SUM(sc.total_row_count)
ELSE SUM(sc.total_row_count) / COUNT(DISTINCT sc.node_name)
END AS approx_row_count,
SUM(sc.deleted_row_count) AS deleted_rows,
SUM(sc.used_bytes) AS total_used_bytes,
ROUND(SUM(sc.used_bytes) / 1073741824.0, 2) AS total_gb,
COUNT(*) AS container_count
FROM v_monitor.storage_containers sc
JOIN (SELECT DISTINCT projection_id, projection_name, is_segmented FROM v_catalog.projections) p
ON sc.projection_id = p.projection_id
AND sc.projection_name = p.projection_name
GROUP BY sc.schema_name, sc.projection_name
HAVING COUNT(*) > 10
ORDER BY container_count DESC
LIMIT 20;
如何解读:
container_count > 50:ROS 容器碎片化严重。每个容器在 mergeout 时都需要被读取和重写,增加 I/O 开销deleted_rows / approx_row_count > 0.5:一半以上的行已被删除但未回收——这是最严重的空间浪费approx_row_count = 0但used_bytes > 0:空表但占空间(全被 delete vector 占据),执行PURGE_TABLE
第 4 节:解决方案——从快速见效到根本治理¶
4.1 立即措施(当天可执行)¶
方案 A:DROP 完全未使用的表¶
适用条件: 步骤 1 的结果,且已与业务方确认该表可删除。
-- 注意:DROP TABLE 是不可逆操作。执行前建议先备份或导出 DDL
-- 获取表 DDL(用于恢复)
SELECT EXPORT_OBJECTS('', 'schema.table_name');
-- 检查是否有外键依赖
SELECT * FROM v_catalog.foreign_keys
WHERE table_schema = 'schema_name' AND table_name = 'table_name';
-- 删除表及其所有投影(包括 buddy projection)
DROP TABLE schema.table_name CASCADE;
为什么用 CASCADE: DROP TABLE 默认已删除表及其所有投影。CASCADE 额外删除依赖该表的视图(View)和外键约束,避免残留依赖对象报错。
方案 B:PURGE 有大量删除残留的表¶
适用条件: 步骤 3 的结果显示 dv_bytes 很高。
为什么不是自动的: Tuple Mover 的 mergeout 在达到一定阈值后才触发。如果删除量不够大但表很多,delete vector 可能长期「挂」在那里。手动 PURGE_TABLE 是快速回收的捷径。
方案 C:移除不必要的数据加载¶
适用条件: 步骤 1/2 中发现的低频表,如果对应着持续运行的 ETL 任务。
与其每天加载数据到一张没人查的表,不如停掉 ETL。Vertica 中每一条 COPY 语句、每一次 INSERT 都会生成新的 ROS 容器并增加 Tuple Mover 负担。
4.2 短期优化(当周执行)¶
方案 D:管理冷表的 Depot 缓存(Eon 模式)¶
Eon 模式下,Depot(本地 SSD 缓存)默认按 LRU 自动淘汰冷数据。如果表曾通过 SET_DEPOT_PIN_POLICY_TABLE 被 pin 住,需要解除以释放 Depot 空间:
-- 查看当前 Depot pin 策略(Eon 模式)
SELECT * FROM v_monitor.depot_pin_policies;
-- 默认情况下所有表都没有 pin,LRU 自动淘汰冷数据即可
-- 如果表曾被 pin 过,解除 pin 使其可被正常淘汰:
SELECT CLEAR_DEPOT_PIN_POLICY_TABLE('schema.cold_table');
-- 如果只需淘汰部分历史分区,标记为优先驱逐:
SELECT SET_DEPOT_ANTI_PIN_POLICY_PARTITION('schema.cold_table', '', '2024-12-31');
方案 E:按分区清理历史数据(替代全表删除)¶
如果表仍在使用但历史分区不需要,使用 DROP_PARTITIONS 比 DELETE + PURGE 高效得多:
-- 删除 2024 年及以前的所有分区(比 DELETE 快很多,且不产生 delete vectors)
SELECT DROP_PARTITIONS(
'schema.table_name',
'', -- 最小分区键值(空 = 从头开始)
'2025-01-01' -- 最大分区键值(不包含这一天)
);
为什么推荐 DROP_PARTITIONS 而不是 DELETE: DELETE 会产生 delete vectors,需要等 mergeout 才能回收空间。DROP_PARTITIONS 直接删除 ROS 容器,物理空间立即释放。
4.3 根本治理——建立表生命周期管理机制¶
定期监控脚本:全量表生命周期报告¶
将以下 SQL 配置为每周定时任务,输出所有用户表的完整生命周期报告——覆盖存储占用、访问频率、删除残留、使用状态分级四个维度,按存储从大到小排列,大表未使用或低频使用会排在最前面。
-- 全量表生命周期报告(建议每周执行)
WITH
-- CTE 1:所有用户表的基本信息
-- v_catalog.tables 包含系统表,需要用 is_system_table 过滤
all_tables AS (
SELECT t.table_schema, t.table_name, t.create_time, t.owner_name, t.table_id
FROM v_catalog.tables t
WHERE t.table_schema NOT IN ('v_catalog', 'v_monitor', 'v_internal')
AND NOT t.is_system_table
),
-- CTE 2:每张表的存储统计
-- SUM(used_bytes) 含 buddy projection + 跨节点副本 = 实际磁盘占用(删表释放量)
-- approx_row_count 自动区分分段/非分段表,消除 row_count 翻倍
table_storage AS (
SELECT ps.anchor_table_schema, ps.anchor_table_name,
SUM(ps.used_bytes) AS total_bytes,
CASE WHEN BOOL_AND(p.is_segmented)
THEN SUM(ps.row_count) / COUNT(DISTINCT ps.projection_name)
ELSE MAX(ps.row_count)
END AS approx_row_count,
SUM(ps.ros_count) AS total_ros,
COUNT(DISTINCT ps.projection_name) AS proj_count
FROM v_monitor.projection_storage ps
JOIN (SELECT DISTINCT projection_id, projection_name, is_segmented
FROM v_catalog.projections) p
ON ps.projection_id = p.projection_id
AND ps.projection_name = p.projection_name
GROUP BY ps.anchor_table_schema, ps.anchor_table_name
),
-- CTE 3:每张表的访问统计(基于 DC 保留周期内的数据)
-- last_accessed = NULL 表示该表在 DC 周期内从未被查询
-- query_count 是 DISTINCT request_id 计数,不是行数
table_access AS (
SELECT pu.anchor_table_schema, pu.anchor_table_name,
MAX(pu.query_start_timestamp) AS last_accessed,
COUNT(DISTINCT pu.request_id) AS query_count
FROM v_monitor.projection_usage pu
GROUP BY pu.anchor_table_schema, pu.anchor_table_name
),
-- CTE 4:每张表的 delete vector 残留(已删除行占用的未回收空间)
-- 注意:delete_vectors 用 projection_name 标识投影(如 product_dimension_DBD_4_rep_first),
-- 不能直接 JOIN table_name。需通过 v_catalog.projections 桥接回 anchor table
table_dv AS (
SELECT p.anchor_table_name, p.projection_schema AS anchor_table_schema,
SUM(dv.deleted_row_count) AS deleted_rows,
SUM(dv.used_bytes) AS dv_bytes
FROM v_monitor.delete_vectors dv
JOIN (SELECT DISTINCT projection_schema, projection_name, anchor_table_name
FROM v_catalog.projections) p
ON dv.schema_name = p.projection_schema
AND dv.projection_name = p.projection_name
GROUP BY p.projection_schema, p.anchor_table_name
)
-- 主查询:串联以上四个维度,输出全量表报告
SELECT a.table_schema,
a.table_name,
a.create_time,
a.owner_name,
-- 存储维度
COALESCE(s.total_bytes, 0) AS storage_bytes,
ROUND(COALESCE(s.total_bytes, 0) / 1073741824.0, 2) AS storage_gb,
COALESCE(s.approx_row_count, 0) AS row_count,
COALESCE(s.total_ros, 0) AS total_ros,
COALESCE(s.proj_count, 0) AS proj_count,
-- 访问维度
acl.last_accessed,
acl.query_count,
-- 删除残留维度
COALESCE(dv.deleted_rows, 0) AS deleted_rows,
ROUND(COALESCE(dv.dv_bytes, 0) / 1073741824.0, 2) AS dv_gb,
-- 使用状态分级:基于 DC 保留周期内的最后访问时间
CASE
WHEN acl.last_accessed IS NULL
THEN '未使用'
WHEN acl.last_accessed < CURRENT_TIMESTAMP - INTERVAL '30 days'
THEN '低频(>30天)'
WHEN acl.last_accessed < CURRENT_TIMESTAMP - INTERVAL '7 days'
THEN '低频(>7天)'
ELSE '活跃'
END AS usage_status
FROM all_tables a
LEFT JOIN table_storage s
ON a.table_schema = s.anchor_table_schema
AND a.table_name = s.anchor_table_name
LEFT JOIN table_access acl
ON a.table_schema = acl.anchor_table_schema
AND a.table_name = acl.anchor_table_name
LEFT JOIN table_dv dv
ON a.table_schema = dv.anchor_table_schema
AND a.table_name = dv.anchor_table_name
ORDER BY COALESCE(s.total_bytes, 0) DESC;
输出:
table_schema | table_name | create_time | owner_name | storage_bytes | storage_gb | row_count | total_ros | proj_count | last_accessed | query_count | deleted_rows | dv_gb | usage_status
--------------+-----------------------------------+-------------------------------+------------+---------------+-------------------+----------------------------+-----------+------------+-------------------------------+-------------+--------------+-------------------+--------------
online_sales | online_sales_fact | 2025-08-30 14:13:16.669305+08 | dbadmin | 193795920 | 0.180000000000000 | 5000000.000000000000000000 | 6 | 2 | 2026-06-06 08:29:55.041027+08 | 12 | 0 | 0.000000000000000 | 活跃
store | store_sales_fact | 2025-08-30 14:13:16.627045+08 | dbadmin | 167064330 | 0.160000000000000 | 5000000.000000000000000000 | 6 | 2 | 2026-06-01 15:00:19.205483+08 | 12 | 0 | 0.000000000000000 | 活跃
store | store_orders_fact | 2025-08-30 14:13:16.64386+08 | dbadmin | 12261744 | 0.010000000000000 | 300000.000000000000000000 | 6 | 2 | 2026-06-01 15:00:19.603346+08 | 12 | 0 | 0.000000000000000 | 活跃
public | product_dimension | 2025-08-30 14:13:14.498106+08 | dbadmin | 4947552 | 0.000000000000000 | 60000.000000000000000000 | 3 | 1 | 2026-06-06 12:14:08.168764+08 | 9 | 0 | 0.000000000000000 | 活跃
public | customer_dimension | 2025-08-30 14:13:14.434014+08 | dbadmin | 4356879 | 0.000000000000000 | 50000.000000000000000000 | 3 | 1 | 2026-06-01 15:00:18.281505+08 | 8 | 0 | 0.000000000000000 | 活跃
public | inventory_fact | 2025-08-30 14:13:16.607218+08 | dbadmin | 2252116 | 0.000000000000000 | 300000.000000000000000000 | 6 | 2 | 2026-06-01 15:00:18.683354+08 | 11 | 0 | 0.000000000000000 | 活跃
public | employee_dimension | 2025-08-30 14:13:14.881694+08 | dbadmin | 871566 | 0.000000000000000 | 10000.000000000000000000 | 12 | 1 | 2026-06-06 13:41:01.015406+08 | 11 | 0 | 0.000000000000000 | 活跃
public | promotion_dimension | 2025-08-30 14:13:14.507036+08 | dbadmin | 55260 | 0.000000000000000 | 1000.000000000000000000 | 3 | 1 | 2026-06-01 15:00:18.483738+08 | 8 | 0 | 0.000000000000000 | 活跃
public | date_dimension | 2025-08-30 14:13:14.513635+08 | dbadmin | 49458 | 0.000000000000000 | 1826.000000000000000000 | 3 | 1 | 2026-06-06 12:18:09.904429+08 | 8 | 0 | 0.000000000000000 | 活跃
store | store_dimension | 2025-08-30 14:13:16.621421+08 | dbadmin | 26301 | 0.000000000000000 | 250.000000000000000000 | 3 | 1 | 2026-06-01 15:00:18.811249+08 | 8 | 0 | 0.000000000000000 | 活跃
online_sales | call_center_dimension | 2025-08-30 14:13:16.66313+08 | dbadmin | 17193 | 0.000000000000000 | 200.000000000000000000 | 3 | 1 | 2026-06-01 15:00:19.745743+08 | 8 | 0 | 0.000000000000000 | 活跃
online_sales | online_page_dimension | 2025-08-30 14:13:16.657524+08 | dbadmin | 14898 | 0.000000000000000 | 1000.000000000000000000 | 3 | 1 | 2026-06-01 15:00:19.725607+08 | 8 | 0 | 0.000000000000000 | 活跃
public | warehouse_dimension | 2025-08-30 14:13:16.523159+08 | dbadmin | 6762 | 0.000000000000000 | 100.000000000000000000 | 3 | 1 | 2026-06-01 15:00:18.642482+08 | 8 | 0 | 0.000000000000000 | 活跃
public | vendor_dimension | 2025-08-30 14:13:14.876262+08 | dbadmin | 5445 | 0.000000000000000 | 50.000000000000000000 | 3 | 1 | 2026-06-01 15:00:18.548064+08 | 8 | 0 | 0.000000000000000 | 活跃
public | shipping_dimension | 2025-08-30 14:13:16.517765+08 | dbadmin | 2613 | 0.000000000000000 | 100.000000000000000000 | 3 | 1 | 2026-06-01 15:00:18.6275+08 | 8 | 0 | 0.000000000000000 | 活跃
public | t | 2025-08-30 13:03:45.480904+08 | dbadmin | 1926 | 0.000000000000000 | 3.000000000000000000 | 3 | 1 | 2026-06-01 15:00:18.017922+08 | 8 | 0 | 0.000000000000000 | 活跃
public | rep | 2025-08-30 13:04:03.856729+08 | dbadmin | 1926 | 0.000000000000000 | 3.000000000000000000 | 3 | 1 | 2026-06-01 15:00:18.126652+08 | 9 | 0 | 0.000000000000000 | 活跃
public | a | 2026-06-04 16:20:12.137149+08 | dbadmin | 216 | 0.000000000000000 | 2.000000000000000000 | 8 | 2 | 2026-06-04 16:30:34.736375+08 | 14 | 0 | 0.000000000000000 | 活跃
vsbak | cpu_usage | 2026-05-11 13:21:31.088616+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | | | 0 | 0.000000000000000 | 未使用
vsbak | dc_resource_pool_status_by_hour | 2026-05-11 13:21:31.359414+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | | | 0 | 0.000000000000000 | 未使用
vsbak | dc_resource_pool_status_by_minute | 2026-05-11 13:21:31.450017+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | | | 0 | 0.000000000000000 | 未使用
vsbak | memory_usage | 2026-05-11 13:21:31.130998+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | | | 0 | 0.000000000000000 | 未使用
vsbak | network_usage | 2026-05-11 13:21:31.143478+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | | | 0 | 0.000000000000000 | 未使用
vsbak | query_consumption | 2026-05-11 13:21:31.161448+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | 2026-05-21 17:59:29.098583+08 | 2 | 0 | 0.000000000000000 | 低频(>7天)
vsbak | resource_acquisitions | 2026-05-11 13:21:31.310233+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | 2026-05-21 17:59:41.305821+08 | 4 | 0 | 0.000000000000000 | 低频(>7天)
vsbak | query_requests | 2026-05-11 13:21:31.524956+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | 2026-05-21 17:59:41.260062+08 | 5 | 0 | 0.000000000000000 | 低频(>7天)
vsbak | query_profiles | 2026-05-11 13:21:31.540551+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | | | 0 | 0.000000000000000 | 未使用
vsbak | projection_usage | 2026-05-11 13:21:31.555842+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | 2026-05-21 17:59:25.704393+08 | 3 | 0 | 0.000000000000000 | 低频(>7天)
vsbak | dc_depot_fetches | 2026-05-11 13:21:31.56865+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 0 | 0 | | | 0 | 0.000000000000000 | 未使用
vsbak | dc_depot_uploads | 2026-05-11 13:21:31.59988+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 0 | 0 | | | 0 | 0.000000000000000 | 未使用
vsbak | projection_storage | 2026-05-11 13:21:31.619916+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | 2026-05-21 17:59:19.322252+08 | 6 | 0 | 0.000000000000000 | 低频(>7天)
vsbak | system_resource_usage | 2026-05-11 13:21:31.509564+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | 2026-05-21 17:59:24.904859+08 | 3 | 0 | 0.000000000000000 | 低频(>7天)
vsbak | user_sessions | 2026-05-11 13:21:31.183189+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | | | 0 | 0.000000000000000 | 未使用
vsbak | dc_lock_attempts | 2026-05-11 13:21:31.497515+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | | | 0 | 0.000000000000000 | 未使用
vsbak | tables | 2026-05-11 13:21:31.637046+08 | dbadmin | 0 | 0.000000000000000 | 0.000000000000000000 | 6 | 2 | | | 0 | 0.000000000000000 | 未使用
(35 rows)
如何解读这份报告:
| 关注点 | 解读方法 |
|---|---|
| 高优先级清理目标 | usage_status = '未使用' + storage_gb > 1 → 联系业务确认后直接 DROP |
| 候选归档表 | usage_status = '低频(>30天)' + storage_gb > 0.5 → 按分区归档或 DROP_PARTITIONS |
| 删除残留堆积 | dv_gb > 0.5 + storage_gb 不大 → 执行 PURGE_TABLE 立即回收 |
| ROS 碎片化 | total_ros > 50 + storage_gb < 1 → 小表但容器碎片化严重,Tuple Mover 负担 |
| Catalog 膨胀 | storage_gb = 0 + create_time < 30 天前 → 空表占 Catalog 条目,可清理 |
| 报告覆盖范围 | 所有判断基于 DC 保留周期(默认 7-30 天)。如果 query_count = 0,只说明该周期内未被查——需结合业务确认 |
⚠️ 注意事项:
delete_vectors的 JOIN 已通过v_catalog.projections桥接,将投影名映射回锚定表名,确保精确匹配usage_status的 7 天/30 天阈值是可调参数,根据业务节奏调整
建立表生命周期文档¶
为每个表记录以下信息(可集成到 Vertica 的 comment 或外部治理系统):
- 创建日期和业务负责人
- 预期保留期(如「保留 13 个月」)
- 归档策略(如「过期后归档到 S3 存储桶」)
在 Vertica 中为表添加注释:
第 5 节:深入案例¶
📝 虚构案例 1:历史归档表无人清理¶
场景: 某电商平台,3 节点集群,Vertica Enterprise 模式。DBA 发现磁盘使用率已达 87%,收到存储告警。
诊断过程:
-- 先看全局
SELECT node_name, storage_usage,
disk_space_free_mb, disk_space_used_mb,
ROUND(disk_space_free_mb * 100.0 / NULLIF(disk_space_free_mb + disk_space_used_mb, 0), 1) AS free_pct
FROM v_monitor.disk_storage;
输出:
node001 / DATA 2880 / 25000 = 11.5%
node002 / DATA 3150 / 24730 = 12.7%
node003 / DATA 3010 / 24870 = 12.1%
全部节点只剩 12% 左右,紧急。
-- 找最大的表(虚构案例,数据为模拟)
SELECT ps.anchor_table_schema, ps.anchor_table_name,
SUM(ps.used_bytes) AS total_bytes,
ROUND(SUM(ps.used_bytes) / 1073741824.0, 2) AS total_gb,
CASE WHEN BOOL_AND(p.is_segmented)
THEN SUM(ps.row_count) / COUNT(DISTINCT ps.projection_name)
ELSE MAX(ps.row_count)
END AS approx_row_count
FROM v_monitor.projection_storage ps
JOIN (SELECT DISTINCT projection_id, projection_name, is_segmented FROM v_catalog.projections) p
ON ps.projection_id = p.projection_id AND ps.projection_name = p.projection_name
GROUP BY anchor_table_schema, anchor_table_name
ORDER BY total_bytes DESC
LIMIT 10;
输出(虚构数据):
ods.order_history_2019_2022 98GB 520亿行
ods.order_history_2023_2024 52GB 260亿行
dwd.user_behavior_log 34GB 180亿行
...
-- 检查 ods.order_history_2019_2022 的最后访问时间
SELECT MAX(query_start_timestamp) AS last_query
FROM v_monitor.projection_usage
WHERE anchor_table_name = 'order_history_2019_2022';
输出:2024-01-15(距今超过 2 年,近 2 年没有任何查询!)
根因分析: 2019-2022 的订单历史数据在 2023 年迁移到新表结构后,旧表被遗忘。ODS 层仍然每天 COPY 新数据到该表,但因为下游报表早已切换到新表,没有任何查询访问它。表持续增长到 98GB,占了集群总存储的 40%。
修复方案:
- 联系业务负责人确认该表已废弃
- 导出表 DDL(
EXPORT_OBJECTS)以备不时之需 DROP TABLE ods.order_history_2019_2022 CASCADE
效果: 磁盘使用率从 87% 降至 54%。释放 98GB 空间。
📝 虚构案例 2:ETL 中间表堆积¶
场景: 某运营商经分系统,5 节点集群。系统慢得异常——不是查询慢,而是系统表查询和数据库日常运维操作(如备份)变得很慢。
诊断过程:
输出:8347 张用户表。对于一个 5 节点分析集群来说异常高。
-- 找「没有数据但有投影」的表(占 Catalog 条目但不占存储)
SELECT t.table_schema, t.table_name, t.create_time
FROM v_catalog.tables t
WHERE t.table_schema NOT IN ('v_catalog', 'v_monitor', 'v_internal')
AND NOT t.is_system_table
AND t.table_id NOT IN (
SELECT DISTINCT anchor_table_id
FROM v_monitor.projection_storage
WHERE row_count > 0
)
ORDER BY t.create_time DESC
LIMIT 20;
输出:4700+ 张空表,表名模式为 tmp_20250401_*、tmp_20250402_*……显然是无 ETL 清理机制留下的临时表。
-- 再检查最近被查询过的表有多少
SELECT COUNT(DISTINCT anchor_table_schema || '.' || anchor_table_name) AS tables_used_recently
FROM v_monitor.projection_usage;
输出:162。也就是说 8347 张用户表中,只有 162 张在 DC 保留周期内被查询过。
根因分析: ETL 脚本使用 CREATE TABLE AS SELECT(CTAS)生成每日中间表,格式为 tmp_YYYYMMDD_xxx,但从未清理旧表。日积月累 8000+ 张表的 Catalog 元数据导致 v_catalog.tables 扫描变慢、数据库启动变慢、备份耗时增加。
修复方案:
- 修改 ETL 脚本,增加每日清理逻辑(保留最近 3 天即可)
- 批量删除历史临时表。由于表太多,用一个生成脚本批量执行:
-- 生成 DROP 语句(先审查再执行!)
SELECT 'DROP TABLE IF EXISTS ' || table_schema || '."' || table_name || '" CASCADE;' as drop_table_sql
FROM v_catalog.tables
WHERE table_schema = 'temp_schema'
AND table_name LIKE 'tmp_2024%'
AND NOT is_system_table
ORDER BY table_name;
效果: Catalog 元数据条目减少 60%,系统表查询速度恢复,备份时间从 35 分钟降至 6 分钟。
📋 真实案例 1:某运营商系统表快照堆积¶
来源: vault 中 vsbak schema 下的表——在本次验证环境(v26.1.0-2)中,vsbak schema 包含 15 张表(如 cpu_usage, query_requests, projection_usage, system_resource_usage 等),它们是通过某种监控脚本将系统表数据定期快照保存的备份表,但所有表的 storage_bytes = 0(空表)。
诊断发现: 这 15 张备份表中,11 张在 projection_usage 中没有任何访问记录——它们被创建后从未被查询过。
-- 已验证的发现:vsbak schema 的表完全没有查询记录
SELECT t.table_schema, t.table_name, t.create_time
FROM v_catalog.tables t
WHERE t.table_schema = 'vsbak'
AND NOT t.is_system_table
AND (t.table_schema, t.table_name) NOT IN (
SELECT DISTINCT anchor_table_schema, anchor_table_name
FROM v_monitor.projection_usage
);
输出确认了 11 张完全未被使用但仍在占用 Catalog 元数据的表。
启示: 监控/备份脚本创建的表,如果脚本后来被弃用,这些表就成为「僵尸表」。它们不占数据空间,但占 Catalog 条目并可能在未来数据加载后被意外查询。
第 6 节:完整诊断流程实战¶
📝 虚构场景 · 完整演练
背景: 某金融机构的数据仓库 DBA 收到告警——节点 node01 磁盘使用率超过 90%。集群为 4 节点 Enterprise 模式,K-safe=1。
时间线:
09:15 — 确认全局状态
SELECT node_name, storage_usage,
disk_space_used_mb, disk_space_free_mb,
ROUND(disk_space_free_mb * 100.0 / NULLIF(disk_space_free_mb + disk_space_used_mb, 0), 1) AS free_pct
FROM v_monitor.disk_storage
WHERE storage_usage = 'DATA,TEMP';
输出:
node01 DATA 32500 / 3601 = 9.9% ← 告警源
node02 DATA 18500 / 17600 = 48.7%
node03 DATA 19200 / 16900 = 46.8%
node04 DATA 19000 / 17100 = 47.3%
判断: node01 异常高(比其他节点多占了 14GB),说明数据分布不均或该节点有额外负担。
09:20 — 定位 node01 上的大表
SELECT anchor_table_schema, anchor_table_name, projection_name,
SUM(used_bytes) AS total_bytes,
ROUND(SUM(used_bytes) / 1073741824.0, 2) AS total_gb
FROM v_monitor.projection_storage
WHERE node_name = 'node01'
GROUP BY anchor_table_schema, anchor_table_name, projection_name
ORDER BY total_bytes DESC
LIMIT 5;
输出(虚构):
risk.var_calculation_history 8.2GB
risk.var_calculation_history 8.2GB ← buddy projection
dwd.trade_detail_2023 3.1GB
dwd.trade_detail_2024 2.8GB
...
判断: risk.var_calculation_history 一张表就占了 node01 的 8.2GB。K-safe=1 时,该表有两个投影(super + buddy),每个投影在 node01 上都有一个数据分段,所以 node01 上显示了两条各 8.2GB 的记录。这张表在 node01 上的两个分段合计约 16.4GB。
09:25 — 检查这张表的使用频率
SELECT MAX(query_start_timestamp) AS last_query,
COUNT(DISTINCT request_id) AS query_count
FROM v_monitor.projection_usage
WHERE anchor_table_schema = 'risk'
AND anchor_table_name = 'var_calculation_history';
输出:
09:30 — 联系风险管理部门确认
发现:VaR(Value at Risk)计算在 2025 年 11 月已迁移到新模型,不再使用这张历史计算表。该表保留仅因「忘了删」。
09:35 — 检查是否有 delete vectors
SELECT schema_name, SUM(deleted_row_count) AS total_deleted,
SUM(used_bytes) AS dv_bytes
FROM v_monitor.delete_vectors
WHERE schema_name = 'risk'
AND projection_name LIKE '%var_calculation_history%'
GROUP BY schema_name;
输出:0 rows(无 delete vector,单纯就是数据大)
09:40 — 确认可删除后执行
SELECT EXPORT_OBJECTS('', 'risk.var_calculation_history');
-- 保存 DDL 到运维文档
DROP TABLE risk.var_calculation_history CASCADE;
10:00 — 验证效果
SELECT node_name, disk_space_free_mb,
ROUND(disk_space_free_mb * 100.0 / NULLIF(disk_space_free_mb + disk_space_used_mb, 0), 1) AS free_pct
FROM v_monitor.disk_storage
WHERE storage_usage = 'DATA,TEMP';
输出:
结论: 删除一张 8.2GB 的未使用表(含 buddy projection = 16.4GB),node01 磁盘使用率从 90.1% 降至 63.7%。后续建议每周运行一次未使用表扫描脚本。
第 7 节:快速诊断 SQL 工具箱¶
| 诊断目标 | SQL | 说明 |
|---|---|---|
| 全局磁盘使用 | v_monitor.disk_storage 查询 free_pct |
先看全局压力 |
| DC 保留周期 | v_monitor.data_collector WHERE component='QueryExecutions' |
确认分析时间范围 |
| 表存储排名 TOP 20 | v_monitor.projection_storage GROUP BY 表 → ORDER BY total_bytes DESC |
找存储大户 |
| 全量表生命周期报告 | 4.3 节完整 CTE(all_tables + table_storage + table_access + table_dv) | 一键输出存储/访问/残留/状态四维报告 |
| 完全未使用的表 | v_catalog.tables LEFT JOIN v_monitor.projection_usage → WHERE last_accessed IS NULL |
快速版,找僵尸表 |
| 低频使用表 | v_monitor.projection_usage GROUP BY 表 → ORDER BY last_accessed ASC |
找候选归档表 |
| Delete Vector 堆积 | v_monitor.delete_vectors GROUP BY 表 → ORDER BY dv_bytes DESC |
找删除残留 |
| ROS 容器碎片化 | v_monitor.storage_containers GROUP BY 投影 → HAVING COUNT(*) > 10 |
找碎片化表 |
| 表总数统计 | SELECT COUNT(*) FROM v_catalog.tables WHERE NOT is_system_table |
Catalog 膨胀检查 |
| 无数据空表 | v_catalog.tables WHERE table_id NOT IN (SELECT anchor_table_id FROM projection_storage WHERE row_count > 0) |
占 Catalog 不占空间 |
| 导出表 DDL | SELECT EXPORT_OBJECTS('', 'schema.table_name') |
删除前备份 |
| 强制回收删除空间 | SELECT PURGE_TABLE('schema.table_name') |
无需等 mergeout |
| 按分区清理 | SELECT DROP_PARTITIONS('schema.table', 'min', 'max') |
比 DELETE 更高效 |
| 解除 Depot pin | SELECT CLEAR_DEPOT_PIN_POLICY_TABLE('schema.table') |
Eon 模式不再缓存 |
| 添加表注释 | COMMENT ON TABLE schema.table IS '...' |
建立表生命周期文档 |
第 8 节:最佳实践清单¶
按投入产出比排序:
- 先扩大 DC 保留周期再分析(投入:1 分钟,产出:准确的分析基础)
如果
QueryExecutions只保留了 3 天的数据,「从未使用」的结论就毫无意义。建议至少保留 30 天。执行SET_DATA_COLLECTOR_POLICY扩大disk_size_kb - 每月跑一次「完全未使用表」扫描(投入:5 分钟,产出:避免存储浪费累积) 使用步骤 1 的 SQL,发现僵尸表及时清理。越是大的集群,越容易有被遗忘的表
- 删除前先
EXPORT_OBJECTS备份 DDL(投入:10 秒/表,产出:可恢复性保障) DROP 是不可逆的。保留 DDL 让恢复只需要重新加载数据,而无需从零建表 - 优先清理有大量 delete vector 的表(投入:5 分钟,产出:立即释放空间)
PURGE_TABLE是纯 I/O 操作,不需要改业务代码或停服务。比 DROP 风险低得多 - 用
DROP_PARTITIONS替代 DELETE + PURGE(投入:改 ETL 代码,产出:无 delete vector,空间即时回收) 如果你的清理逻辑是「删除 30 天前的数据」,而表恰好有分区,用DROP_PARTITIONS效率高几个数量级 - 将
projection_usage分析与 ETL 上线流程集成(投入:1 天开发,产出:防止新僵尸表) 在新表上线 checklist 中加入一项:「确认旧表是否有下游依赖,如有则在 N 天后清理旧表」 - Eon 模式检查 Depot pin 状态(投入:1 分钟,产出:避免冷表 pin 浪费 Depot 空间)
默认 LRU 会自动淘汰冷数据。但如果表曾被
SET_DEPOT_PIN_POLICY_TABLEpin 住,用CLEAR_DEPOT_PIN_POLICY_TABLE解除,释放被占用的 Depot 缓存 - 为所有用户表添加 COMMENT(投入:2 分钟/表,产出:告别「这张表是谁建的」之问)
COMMENT ON TABLE记录业务负责人、预期保留期和归档策略。半年后你不用猜 - 关注 ROS 容器数 > 50 的表(投入:每月 5 分钟,产出:减少 mergeout I/O 竞争) 容器数过高说明该表碎片化严重或加载模式有问题。及早发现可以避免演变成性能问题
- 区分「不查询」和「不写入」(投入:理解本文的概念,产出:精准的操作决策)
一张表可能
projection_usage中没有记录(没人查),但 ETL 仍然每天写入——这说明下游报表已下线但上游加载未停。清理时不仅要删表,还要停掉对应的 ETL,否则下次又会重新加载数据
扩展阅读¶
- Vertica 冷热数据管理与成本优化 — 冷热数据分层归档策略
- Vertica 表分区策略选择指南 — 分区键选择与按分区清理
- Vertica 数据删除最佳实践 — DELETE / PURGE / DROP_PARTITIONS 机制详解
- Vertica 大表统计信息维护最佳实践 — 统计信息收集与表使用分析联动