跳转至

Vertica Schema 组织与管理最佳实践

作者:JiangChong | 撰写时间:2026年06月

适用场景:当集群中 Schema 数量膨胀、表归属混乱、多业务线共享数据库需要隔离、或需要系统性地规划 Schema 的组织结构时,使用本文建立 Schema 管理体系。

关联文章Vertica Schema DiskQuota 配置指南 | Vertica 多租户实现最佳实践 | Vertica 访问策略最佳实践 | Vertica 资源池最佳实践

理解全文脉络

本文围绕 Schema 的四个管理维度组织:组织、存储、权限、多租户。第 1 节解释 Schema 在 Vertica 中的本质和工作原理;第 2 节从全局监控视角摸底现有 Schema 状态;第 3 节按优先级诊断常见 Schema 管理问题;第 4 节给出分层解决方案(清理、拆分、权限重整、配额管控);第 5–6 节通过虚构和真实案例串联全部知识;第 7–8 节提供可粘贴的 SQL 工具箱和最佳实践清单。

  • 如果你不知道从哪里开始管理 Schema,建议从第 2 节全局摸底 + 第 3 节诊断开始。
  • 如果你已经有明确的 Schema 拆分需求,直接跳到第 4.3 节拆分策略。
  • 如果你在搭建多租户环境,从第 4.2 节多租户模型开始,配合 Vertica 多租户实现最佳实践
  • 如果你在排查权限问题,跳到第 3.4 节权限排查 + 第 4.4 节权限管理。

第 1 节:原理理解

1.1 Schema 在 Vertica 中的角色

Schema 是 Vertica 中表、视图、投影、函数等数据库对象的第一层逻辑容器。它和传统关系型数据库(如 PostgreSQL)的 Schema 概念一致:

  • 每个表必然属于某个 Schema(默认 public
  • 不同 Schema 可以有同名表,互不冲突
  • Schema 下的表共享该 Schema 的配额、权限和备份策略

Schema 就像文件系统中的目录——你可以把表(文件)随意扔进 public(根目录),但表多了就会混乱。Schema 给你提供了一层命名空间隔离和权限边界。

1.2 Schema 搜索路径(search_path)

当用户执行 SELECT * FROM orders 而不带 Schema 前缀时,Vertica 按 search_path 中定义的顺序依次查找:

-- 查看当前用户的 search_path
SELECT user_name, search_path FROM v_catalog.users WHERE user_name = CURRENT_USER();
-- 默认值通常为:"$user", public, v_catalog, v_monitor, v_internal

search_path 中的 "$user" 是一个特殊占位符——Vertica 首先查找与当前用户名同名的 Schema。如果找到则使用该 Schema 中的对象,否则依次尝试 publicv_catalogv_monitor

这就是为什么不带 Schema 前缀的 SQL 有时会「迷路」——你以为在查 analytics.orders,但 search_path 先找到了 public.orders(如果有同名表的话)。

关键结论:多 Schema 环境下,强烈建议 SQL 中始终使用完全限定表名schema.table),不依赖 search_path。search_path 只做 fallback,不应依赖它来控制数据访问安全。

1.3 Eon 模式下的对象层次:Namespace → Schema → 表

在 Vertica Eon 模式中,Schema 之上还有一个更高层级的容器:Namespace(v24.1.x 引入)。三者的关系是严格的树状结构:

Namespace (default_namespace)          ← 顶层:定义 Shard 数量
├── Schema (analytics)                 ← 二层:组织表、视图、权限
│   ├── orders                         ← 三层:实际数据表
│   └── customers
├── Schema (finance)
│   └── transactions
└── ...

Namespace (airport)                    ← 另一个 Namespace,可设不同 Shard 数
├── Schema (airline)
│   └── flights
└── ...

关键事实(来源:Vertica 26.2.x 官方文档):

  • Namespace 是 Schema 的父容器。每个 Schema 属于且仅属于一个 Namespace,使用三部分命名:namespace.schema.table
  • Namespace 定义 Shard 数量。创建 Namespace 时指定 SHARD COUNT n,该 Namespace 下的所有表和投影都按此数量分片存储。
  • 不同 Namespace 可以有不同 Shard 数。大表+复杂查询适用更多 Shard,小表适用较少 Shard。建议 Shard 数为节点数的倍数或因子以保证负载均衡,推荐不超过节点数的 2 倍(最大 3:1)。
  • 数据库默认有一个 default_namespace。不带 Namespace 前缀的 CREATE SCHEMA / CREATE TABLE 自动归属到 default_namespace
  • 不同 Namespace 下可以有同名 Schema,互不冲突。

1.3.1 创建和查看 Namespace

-- 创建 Namespace(需指定 Shard 数)
CREATE NAMESPACE airport SHARD COUNT 12;

-- 查看所有 Namespace
SELECT namespace_name, is_default, default_shard_count FROM NAMESPACES;
  namespace_name   | is_default | default_shard_count
-------------------+------------+---------------------
 default_namespace | t          |                   6
 airport           | f          |                   12
(2 rows)

1.3.2 在指定 Namespace 下创建 Schema 和表

-- 在 default_namespace 下创建(省略 Namespace 前缀)
CREATE SCHEMA airline;
CREATE TABLE airline.flights (id INT, route VARCHAR);

-- 在 airport Namespace 下创建(三部分命名)
CREATE SCHEMA airport.airline;
CREATE TABLE airport.airline.flights (id INT, route VARCHAR);

注意ALTER TABLE ... SET SCHEMA 只能在同一 Namespace 内移动表,不能跨 Namespace 移动。

1.3.3 Shard 与 Subscription 机制

理解 Namespace 必须理解它下面的物理层——ShardSubscription

概念 是什么 关键点
Shard 公共存储中的数据分片,Namespace 定义其数量 每个 Namespace 还有一个 Replica Shard(存储 UNSEGMENTED 投影元数据),存在于所有节点
Primary Subscriber 每个 Shard 有一个主订阅节点,负责规划该 Shard 的 Tuple Mover 操作 可委托其他节点执行,不一定要自己跑 TM
Subscription 节点对 Shard 的订阅关系 K-safety≥1 时每个 Shard 在每个 Subcluster 中有多个订阅节点
Critical Node 某个 Shard 的唯一订阅节点 该节点宕机 → 数据库进入 READONLY 模式

故障行为

  • 主订阅节点宕机 → Vertica 自动从备用订阅节点选出新的 Primary Subscriber
  • 某个 Shard 的全部订阅节点宕机 → 数据库进入 READONLY 模式以保护数据完整性

1.3.4 Namespace 的限制

  • RESHARD_DATABASE() 只能重分片 default_namespace。存在非默认 Namespace 时操作失败。
  • Flex Table 只能创建在 default_namespace
  • 第三方工具如果不支持三部分命名(namespace.schema.table),使用非默认 Namespace 会导致查询失败。如果工作负载严重依赖此类工具,建议避免使用非默认 Namespace。
  • VBR 备份恢复中,目标 Namespace 必须与源 Namespace 有相同的 Shard 数量和节点订阅关系。

Enterprise 模式没有 Namespace 概念——上述内容仅适用于 Eon 模式。Enterprise 模式下,Schema 直接位于数据库之下,没有这一中间层。

来源:Vertica 26.2.x 官方文档 Shards and Subscriptions + Managing Namespaces;系统表字段来源 NAMESPACES(v24.1.x 新增)。

1.4 Schema 级别权限:两种机制,别混用

Vertica 中 Schema 级别的权限管理有两个独立机制,容易被混淆:

机制 语法 作用于 对未来新建表
批量授权 GRANT SELECT ON ALL TABLES IN SCHEMA s TO role 仅 Schema 中当前已有的表 ❌ 不适用
权限继承 ALTER SCHEMA s DEFAULT INCLUDE PRIVILEGES Schema 上已有的 GRANT 自动传递给新创建的表 ✅ 核心作用

批量授权不等于权限继承。如果你只执行 GRANT SELECT ON ALL TABLES IN SCHEMA,之后新建的表不会自动获得 SELECT 权限——必须再次执行一遍。

1.4.1 权限继承的三层开关

权限继承需要三个层级都就绪,缺一不可:

数据库级: ALTER DATABASE SET DisableInheritedPrivileges = 0   ← 默认 ON
    └── Schema级: ALTER SCHEMA s DEFAULT INCLUDE PRIVILEGES    ← 默认 OFF
            └── 对象级: 新表自动 INCLUDE / 旧表手动 ALTER TABLE INCLUDE SCHEMA PRIVILEGES

关键默认值(来源:Vertica 26.2.x 官方文档):

  • 数据库级:默认 DisableInheritedPrivileges = 0(继承已启用)
  • Schema 级:默认关闭。必须显式执行 ALTER SCHEMA ... DEFAULT INCLUDE PRIVILEGES
  • 对象级:Schema 启用继承后,新表自动继承;Schema 启用前已有的旧表需逐个执行 ALTER TABLE t INCLUDE SCHEMA PRIVILEGES

1.4.2 完整操作流程

-- 第 1 步:确认数据库级继承已启用(默认就开着,检查即可)
-- 无直接查询方式,但可通过行为验证:Schema 级启用若报 WARNING 则 DB 级未开

-- 第 2 步:授予 Schema 级别的权限
GRANT USAGE, CREATE ON SCHEMA analytics TO analyst_role;
GRANT SELECT ON SCHEMA analytics TO analyst_role;

-- 第 3 步:对已有表逐个启用继承(只影响这一步之前创建的表)
ALTER TABLE analytics.orders INCLUDE SCHEMA PRIVILEGES;
ALTER TABLE analytics.customers INCLUDE SCHEMA PRIVILEGES;

-- 第 4 步:启用 Schema 默认继承(此后新建的表自动获得 Schema 上的 GRANT)
ALTER SCHEMA analytics DEFAULT INCLUDE PRIVILEGES;

-- 至此:已有表通过第 3 步获得继承,未来新建表通过第 4 步自动获得

1.4.3 排除特定表

如果 Schema 启用了默认继承,但某张表不想继承(如敏感表),创建时或事后排除:

CREATE TABLE analytics.secret_data (...) EXCLUDE SCHEMA PRIVILEGES;
-- 或事后:
ALTER TABLE analytics.secret_data EXCLUDE SCHEMA PRIVILEGES;

1.4.4 常见误区

  • 误区 1:执行 GRANT SELECT ON ALL TABLES IN SCHEMA 后新建表自动有权限 → ❌ 不会,只对当前已有表生效
  • 误区 2:执行 ALTER SCHEMA DEFAULT INCLUDE PRIVILEGES 后已有表自动获得 Schema 权限 → ❌ 不会,只对之后新建表生效。已有表需 ALTER TABLE ... INCLUDE SCHEMA PRIVILEGES
  • 误区 3GRANT SELECT ON SCHEMA 本身就够 → ❌ 仅 Grant 不加 INCLUDE SCHEMA PRIVILEGES,表级权限不会自动建立

来源:Vertica 26.2.x 官方文档 Inherited Privileges 及四个子页面。

1.5 Schema 问题来源总览

来源 典型表现 影响
Schema 数量过多 catalog 膨胀,全局锁持有时间变长 DDL/DML 延迟增加
单 Schema 表数过多(> 1000) 配额检查开销累积、权限管理困难 DML 性能下降
search_path 配置不当 查询访问了错误的表 数据结果异常、安全风险
Schema 权限未分层设置 用户越权访问或连接报错 安全问题、操作受阻
Schema 未配置 DiskQuota 单 Schema 无限制增长 挤占集群存储空间
升级后 Schema 中存在系统表副本 查询系统表报字段不存在 升级后数据库异常
Eon 模式 Namespace Shard 数设置不合理 大表 Shard 太少导致并行度不够,或小表 Shard 太多浪费订阅开销 查询性能下降、TM 操作效率低

第 2 节:系统级监控(从宏观入手)

2.1 查看所有 Schema 及其基础信息

v_catalog.schemata 列出数据库中所有 Schema:

SELECT
    schema_name,
    schema_owner,
    schema_id,
    schema_namespace_name   -- Eon 模式:Schema 所属的 Namespace;Enterprise 模式下为空字符串(无 Namespace 概念)
FROM v_catalog.schemata
ORDER BY schema_name;

如何解读结果

  • schema_owner:Schema 的所有者。新 Schema 的默认所有者是创建者。
  • schema_namespace_name:Eon 模式下显示 Schema 归属的 Namespace(见 1.3 节「Eon 模式下的对象层次:Namespace → Schema → 表」);Enterprise 模式下为空字符串(无 Namespace 概念)。
  • 系统 Schema(v_catalogv_monitorv_internalv_txtindex)由 dbadmin 拥有,不应修改。
  • 关注用户创建的 Schema 数量。如果总数超过 50 个且还在增长,需要评估是否需要整合。

2.2 查看各 Schema 的表数量

SELECT
    table_schema,
    COUNT(*) AS table_count,
    SUM(CASE WHEN LENGTH(partition_expression) > 0 THEN 1 ELSE 0 END) AS partitioned_tables,
    SUM(CASE WHEN LENGTH(partition_expression) = 0 THEN 1 ELSE 0 END) AS unpartitioned_tables
FROM v_catalog.tables
WHERE table_definition = ''
GROUP BY table_schema
ORDER BY table_count DESC;

如何解读结果

指标 含义 阈值
table_count Schema 下的表总数 > 1000 需关注。大量表共享一个 Schema 会导致配额检查开销累积、权限管理困难。来源:Vertica Schema DiskQuota 配置指南 3.4 节 — catalog_schema_diskquota_table_count: 1000 巡检阈值
partitioned_tables 使用分区的表数量 占比低说明大部分表无法通过 DROP_PARTITIONS 高效清理

2.3 查看各 Schema 的存储占用

v_monitor.projection_storageanchor_table_schema 聚合可直接得到每 Schema 的物理磁盘占用:

SELECT
    ps.anchor_table_schema AS schema_name,
    COUNT(DISTINCT ps.anchor_table_name) AS table_count,
    SUM(ps.used_bytes) // 1024^3 AS total_used_gb,
    COUNT(DISTINCT ps.projection_name) AS proj_count
FROM v_monitor.projection_storage ps
GROUP BY ps.anchor_table_schema
ORDER BY total_used_gb DESC;

如何解读结果

  • total_used_gb 是物理磁盘占用(含 buddy 副本),代表该 Schema 实际占用的磁盘空间。
  • 如果某 Schema 占集群总容量的 50% 以上,应评估是否拆分或设置 DiskQuota。
  • table_count 远小于 2.2 中的表数 → 说明大量表没有物理数据(空表待清理,或为外部表——外部表无本地投影,不在此视图出现)。

2.4 查看所有 Schema 级别的 GRANT 权限

-- 注意:SCHEMA 类型的 grants,object_name 列存 Schema 名,object_schema 列为 NULL
SELECT
    object_name AS schema_name,
    grantee,
    object_type,
    privileges_description
FROM v_catalog.grants
WHERE object_type = 'SCHEMA'
  AND object_name NOT IN ('v_catalog', 'v_monitor', 'v_internal', 'v_txtindex')
ORDER BY object_name, grantee;

如何解读结果

  • object_type = 'SCHEMA' 表示这是对 Schema 本身(而非表)的权限。注意:Schema 级别的 GRANT 将 Schema 名存储在 object_name 列,object_schema 为空(与 TABLE 级别相反)。
  • privileges_description 列出授予的具体权限(如 USAGECREATE*)。
  • 如果某个 Schema 的 grantee 列表为空,表示只有 Schema owner 和 superuser 可以访问——对于多团队共享的 Schema,这可能意味着有人在报权限错误但你没注意到。

2.5 查看 Schema 权限继承

SELECT
    principal,
    object_schema,
    privileges_description
FROM v_catalog.inherited_privileges
ORDER BY object_schema, principal;

如何解读结果

  • inherited_privileges 展示的是通过 Schema 级别 GRANT 自动继承到子对象的权限
  • 如果这里列出了某个用户/角色,意味着该主体对该 Schema 下的所有表都有指定权限。
  • 安全审查要点:检查是否有不应该访问该 Schema 的用户通过继承获得了权限。

第 3 节:逐步定位根因(从宏观到微观)

步骤 1:确认 Schema 规模和是否存在膨胀风险

做什么:快速判断数据库的 Schema 分布是否合理。

SQL

SELECT
    table_schema,
    COUNT(*) AS table_count
FROM v_catalog.tables
WHERE table_definition = ''
GROUP BY table_schema
ORDER BY table_count DESC;

如何解读

  • public Schema 中表数量超过 500 → Schema 拆分不足的直接信号。大量表堆在 public 中,没有任何组织层次。
  • Schema 平均表数 < 20 → 可能存在过度拆分,Schema 数量过多反而增加 catalog 开销。
  • 合理的状态:Schema 数量 = 业务子模块数量,单 Schema 表数控制在 100–500 之间。

如果不是则进入下一步:Schema 数量和表分布合理,但仍有问题。

步骤 2:检查 search_path 是否导致对象访问混乱

做什么:排查用户是否因为 search_path 访问了错误的表。

SQL

SELECT
    user_name,
    search_path,
    resource_pool
FROM v_catalog.users
WHERE user_name NOT IN ('dbadmin', 'pseudosuperuser')
ORDER BY user_name;

如何解读

  • search_path"$user" 开头(默认行为)→ 不同用户有同名 Schema 时可能误入。
  • search_pathpublic 排在业务 Schema 前面 → 如果 public 下有同名表,会先匹配到 public 中的表。
  • search_path 与默认值不一致(如 dbadmin 被改为 vsbak, public, ...)→ 说明有人手动 ALTER USER ... SEARCH_PATH 改过。用户 Schema 排在前面且不带 "$user" 占位符时,该用户所有不带前缀的查询都会优先命中那个 Schema,容易造成行为与其他用户不一致。排查时追问谁改的、为什么改、改完之后新加的业务 Schema 是否被遗忘。
  • 在多 Schema 环境中,始终使用 schema.table 完全限定表名,不依赖 search_path 的顺序来区分同名表。调整 search_path 顺序是治标——换个用户、换个工具、换次登录就可能失效。

验证方法:以目标用户身份执行:

SHOW SEARCH_PATH;
-- 或
SELECT CURRENT_SCHEMA();

如果不是则进入下一步:search_path 配置正常。

步骤 3:排查 Schema 级别的权限问题

做什么:某用户/角色无法访问某个 Schema 下的表时,诊断权限链。

SQL

-- 1. 检查用户被授予了哪些角色
SELECT user_name, all_roles, default_roles
FROM v_catalog.users
WHERE user_name = 'target_user';

-- 2. 检查目标 Schema 上授予了哪些权限(SCHEMA 类型的 object_schema 为 NULL,Schema 名在 object_name)
SELECT grantee, privileges_description
FROM v_catalog.grants
WHERE object_name = 'target_schema' AND object_type = 'SCHEMA';

-- 3. 检查目标 Schema 下表的权限(TABLE 类型使用 object_schema)
SELECT object_name, grantee, privileges_description
FROM v_catalog.grants
WHERE object_schema = 'target_schema' AND object_type = 'TABLE'
ORDER BY object_name, grantee;

如何解读

  • 用户缺乏 USAGE ON SCHEMA → 用户根本无法看到该 Schema 中的任何对象。这是最常见的权限问题根因。
  • 用户有 Schema 级别 SELECT 但无表级别 SELECT → 检查 GRANT SELECT ON ALL TABLES IN SCHEMA 是否已执行。
  • 用户通过角色继承权限但 default_roles 中不包含该角色 → 用户登录后需手动 SET ROLE rolename 激活。

快速修复

-- 授予 Schema 使用权限(前置条件)
GRANT USAGE ON SCHEMA target_schema TO analyst_role;
-- 授予 Schema 下所有表的 SELECT
GRANT SELECT ON ALL TABLES IN SCHEMA target_schema TO analyst_role;

来源:分区表上设置访问策略导致增删改报权限错误 — 真实案例中 GRANT all on schema public to broker + GRANT all on all tables in schema public to broker 解决权限错误。

如果不是则进入下一步:权限配置正确但仍无法访问。

步骤 4:检查 Schema 是否存在系统表污染

做什么:排查是否有用户在 Schema 下创建了 Vertica 系统表的副本,导致升级或查询异常。

SQL

-- 已验证:Vertica v26.1.0-2
-- 第 1 步:检查用户 Schema 下是否有与系统表同名的表
SELECT
    t.table_schema,
    t.table_name,
    '可能与系统表 ' || s.table_schema || '.' || s.table_name || ' 冲突' AS conflict
FROM v_catalog.tables t
JOIN v_catalog.system_tables s ON t.table_name = s.table_name
ORDER BY t.table_schema, t.table_name;
-- 已验证:Vertica v26.1.0-2
-- 第 2 步:检查这些同名表所在 Schema 是否在某用户的 search_path 中(更危险)
SELECT
    t.table_schema,
    t.table_name,
    s.table_schema AS sys_schema,
    LISTAGG(DISTINCT u.user_name) AS affected_users,
    '用户不带 Schema 前缀时将命中用户表而非系统表' AS risk
FROM v_catalog.tables t
JOIN v_catalog.system_tables s ON t.table_name = s.table_name
JOIN v_catalog.users u
  ON INSTR(',' || REPLACE(u.search_path, ' ', '') || ',',
           ',' || t.table_schema || ',') > 0
GROUP BY t.table_schema, t.table_name, s.table_schema
ORDER BY t.table_schema, t.table_name;

如何解读

  • 第 1 步结果非空 → 高危。用户 Schema 下存在与系统表同名的表(如 public.nodespublic.projections),这些副本在升级后可能因缺少新版本字段导致报错。
  • 第 2 步结果非空 → 紧急。同名表所在 Schema 出现在某些用户的 search_path 中(见 affected_users 列),这意味着这些用户不带 Schema 前缀执行 SELECT * FROM nodes 时,不会命中 v_catalog.nodes,而是命中这个过期的用户表副本。这比升级报错更隐蔽——不会有 error,但查询结果来自错误的数据源。
  • 典型案例:public.nodes 是旧版本的 v_catalog.nodes 备份表,升级到 12.0.4 后因缺少 sandbox 字段导致停库报错(来源:从11.1.1升级到12.0.4版本后停库报sandbox错误)。

来源:从11.1.1升级到12.0.4版本后停库报sandbox错误 — 从 11.1.1 升级到 12.0.4 后,public.nodes 备份表缺少新版本 sandbox 字段导致停库报错。


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

4.1 立即措施(当天可执行)

方案 A:清理 Schema 下的系统表副本

适用场景:发现用户 Schema 下存在 nodesprojections 等系统表副本。

-- 重命名(而非删除)以便回退
ALTER TABLE public.nodes RENAME TO public.nodes_backup_legacy;
-- 确认无报错后可删除
DROP TABLE public.nodes_backup_legacy;

为什么重命名而非直接删除CREATE TABLE ... AS SELECT * FROM ... 创建的表是用户表,删除不会影响系统功能。但保留一段时间(1 周)以便确认无业务依赖后彻底清除。

方案 B:修正 search_path 配置

适用场景:用户因 search_path 顺序问题无法找到目标表。

-- 为单个用户设置 search_path(推荐:业务 Schema 在前,public 在后)
ALTER USER analyst_user SEARCH_PATH analytics, public, v_catalog, v_monitor;

-- 验证
SELECT user_name, search_path FROM v_catalog.users WHERE user_name = 'analyst_user';

为什么业务 Schema 在前:保证 SELECT * FROM orders 优先命中 analytics.orders,不会因 public 中存在同名表而导致静默错误。

方案 C:快速修复 Schema 权限

适用场景:用户报 permission denied for schema xxx 错误。

-- 最小权限原则:按需授予
GRANT USAGE ON SCHEMA target_schema TO role_name;            -- 能看到 Schema
GRANT SELECT ON ALL TABLES IN SCHEMA target_schema TO role_name;  -- 能查询所有表
GRANT CREATE ON SCHEMA target_schema TO developer_role;       -- 能建表(开发角色)

注意USAGE ON SCHEMA 是访问 Schema 下任何对象的前置条件。缺少此权限时,即使表级有 SELECT 权限也会报错。

4.2 多租户 Schema 设计(当周规划)

选择合适的租户隔离模型。Vertica 多租户实现最佳实践 详述了 5 种方案——其中 3 种通用方案(Enterprise / Eon 均适用),2 种 Eon 模式专属方案(利用子集群机制)。本文仅提供关键决策框架。

通用方案对比(3 种)

维度 多 Schema(每租户一Schema) 单 Schema + tenant_id 多集群(每租户一集群)
隔离强度 🟡 中等(Schema 级别) 🔴 较弱(需行级策略保障) 🟢 最强(物理隔离)
跨租户分析 🔴 复杂(需跨 Schema) 🟢 简单(WHERE tenant_id=) 🔴 需联邦查询
Catalog 膨胀风险 🔴 租户数多时 catalog 大 🟢 catalog 小 🟢 各集群独立
备份灵活性 🟢 按 Schema 独立备份 🔴 表含所有租户数据 🟢 各集群独立
资源隔离 🟡 需资源池配合 🟡 需资源池配合 🟢 独占硬件
运维成本 🟢 中等 🟢 较低 🔴 高(多集群管理)
适用规模 10~50 个租户 100+ 3~10 个
模式要求 Enterprise / Eon Enterprise / Eon 均可

Eon 专属方案(2 种)

维度 Subcluster 隔离(每租户一子集群) Sandbox 隔离(数据+计算完全隔离)
计算隔离 ✅ 独立计算节点 ✅ 独立计算节点
存储隔离 ❌ 共享公共存储 ✅ 快照后独立
存储成本 低(不复制数据) 中(逐渐增长)
弹性伸缩 ✅ 子集群级启停 ✅ 子集群级启停
嘈杂邻居防护 ✅ 物理隔离 ✅ 物理隔离
适合租户数 5~20 3~10
模式要求 仅 Eon 仅 Eon

决策建议

  • Enterprise 模式:在多 Schema / 单 Schema+tenant_id / 多集群中三选一
  • Eon 模式优先评估 Subcluster 隔离方案——它在不增加存储成本的前提下提供计算物理隔离,是多租户场景下 Eon 架构的核心价值。Subcluster 隔离可与多 Schema 组合:Subcluster 提供计算隔离,Schema 提供数据隔离,Access Policy 提供行级安全——三层防护
  • Eon 模式 + 需要数据独立副本(开发/测试、版本升级验证、数据共享给外部团队)→ Sandbox 隔离
  • > 50 个小租户 + 需要跨租户分析 → 单 Schema + tenant_id
  • 详细选型决策树和配置示例见 Vertica 多租户实现最佳实践

4.3 Schema 拆分策略(根本治理)

当单个 Schema 的表数超过 1000 或数据集中度过高时,需要系统性拆分。以下拆分维度来自 Vertica Schema DiskQuota 配置指南 4.3 节的实战经验。

按数据生命周期分层(推荐维度)

-- 示例 Schema 架构
analytics_ods      -- 原始贴源层,短保留期(7天),配额 2TB
analytics_dwd      -- 明细宽表层,中保留期(90天),配额 5TB
analytics_dws      -- 汇总层,长保留期(1年),配额 3TB
analytics_dim      -- 维度表,永久保留,配额 500GB
analytics_archive  -- 归档区,外部表为主,无配额限制

为什么分层

  1. 故障隔离:ODS 层配额超限不影响 DWD/DWS 层写入
  2. 精细管控:不同保留周期的数据用不同配额,短保留周期的 ODS 层设较小配额
  3. 运维友好:ODS 到期数据可直接 DROP SCHEMA ... CASCADE(如果整个 Schema 都过期)或按分区批量清理
  4. 降低检查开销:每层表数控制在 200–500 以内,配额检查效率高

按业务子模块拆分

对于 ODS/DWD 层,如果单一模块表数超过 500,进一步按子模块拆分:

analytics_ods_billing    -- 计费模块
analytics_ods_traffic    -- 话务模块
analytics_ods_subscribe  -- 订阅模块

拆分的判断标准

  • 子模块之间有独立的 ETL 调度链 → 独立 Schema
  • 子模块之间数据完全独立,无 JOIN 需求 → 独立 Schema
  • 子模块之间需要频繁 JOIN → 保留在同一 Schema,不要因拆分引入跨 Schema JOIN 的复杂度

执行 Schema 拆分

-- 1. 创建新 Schema(带 DiskQuota)
CREATE SCHEMA IF NOT EXISTS analytics_ods_billing DISK_QUOTA '1T';

-- 2. 移动表到新 Schema(自动迁移投影和 IDENTITY 列)
ALTER TABLE original_schema.migrated_table SET SCHEMA analytics_ods_billing;

-- 3. 授权
GRANT USAGE ON SCHEMA analytics_ods_billing TO etl_role;
GRANT ALL ON ALL TABLES IN SCHEMA analytics_ods_billing TO etl_role;

注意SET SCHEMA 需要 USAGE on 源 Schema + CREATE on 目标 Schema 权限。Eon 模式下只能在同一 Namespace 内移动。

4.4 Schema 级别权限管理的最佳实践

权限分层模型

-- 第 1 层:角色定义
CREATE ROLE schema_reader;    -- 只读角色
CREATE ROLE schema_writer;    -- 读写角色
CREATE ROLE schema_admin;     -- 管理角色(DDL)

-- 第 2 层:Schema 级别授权(两步走:现有表 + 未来表)
GRANT USAGE ON SCHEMA analytics TO schema_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO schema_reader;   -- 当前已有表

GRANT USAGE ON SCHEMA analytics TO schema_writer;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA analytics TO schema_writer;

GRANT USAGE, CREATE ON SCHEMA analytics TO schema_admin;
GRANT ALL ON ALL TABLES IN SCHEMA analytics TO schema_admin;

-- 第 3 层:启用 Schema 默认继承(此后新建表自动获得上述 GRANT)
ALTER SCHEMA analytics DEFAULT INCLUDE PRIVILEGES;

-- 第 4 层:用户分配角色
GRANT schema_reader TO analyst_user;
ALTER USER analyst_user DEFAULT ROLE schema_reader;

为什么这样分层

  • 权限变更时只需改角色,不需要逐个用户修改
  • 新用户入职时直接 GRANT role TO user,权限即到位
  • 审计时只需看角色→Schema 的映射关系,不枚举每个用户
  • GRANT ON ALL TABLES IN SCHEMA 覆盖当前已有表DEFAULT INCLUDE PRIVILEGES 覆盖未来新建表(见 1.4 节「Schema 级别权限:两种机制,别混用」)
  • 配合 Access Policy(Vertica 访问策略最佳实践)实现行级/列级的精细控制

第 5 节:深入案例

📝 虚构案例 1:public Schema 高度膨胀导致权限管理失控

场景描述:某金融机构 Vertica 集群运行 3 年后,public Schema 下积累了 3200 张表,来自 8 个业务团队。DB 管理员使用统一的 dbadmin 账户操作,从未设置过细粒度权限。新入职数据分析师需要访问「风控模型结果表」时,无法确定表名(public 下有 20+ 张以 risk_ 开头的表),也无法确认自己是否有权限——因为没有建过任何独立用户。

诊断过程

-- 1. 摸底 public Schema 表数
SELECT table_schema, COUNT(*)
FROM v_catalog.tables
WHERE table_schema = 'public' AND table_definition = ''
GROUP BY table_schema;
-- 结果:public: 3200
-- 2. 按表名前缀分组,了解业务分布
SELECT
    CASE
        WHEN t.table_name LIKE 'risk_%' THEN '风控'
        WHEN t.table_name LIKE 'traffic_%' THEN '话务'
        WHEN t.table_name LIKE 'billing_%' THEN '计费'
        WHEN t.table_name LIKE 'sub_%' THEN '订阅'
        ELSE '其他'
    END AS biz_module,
    COUNT(*) AS table_count,
    SUM(COALESCE(ps.used_bytes, 0)) // 1024^3 AS total_gb
FROM v_catalog.tables t
LEFT JOIN v_monitor.projection_storage ps
  ON ps.anchor_table_name = t.table_name AND ps.anchor_table_schema = 'public'
WHERE t.table_schema = 'public' AND t.table_definition = ''
GROUP BY 1
ORDER BY table_count DESC;
-- 结果:话务 1200 张、计费 800 张、风控 500 张、订阅 400 张、其他 300 张

根因分析:所有团队 3 年来一直在 public Schema 下建表,没有做过任何 Schema 规划。3200 张表没有任何命名约定,表名混乱、归属不明、权限无法区分。

修复方案

  1. 按业务模块创建 5 个 Schema(risktrafficbillingsubscribecommon
  2. 每 Schema 设 DiskQuota
  3. 按表名前缀将表迁移到对应 Schema(risk_*risk Schema)
  4. 为每个业务团队创建专用角色和用户,按 Schema 授权
  5. 禁用 public 的默认 CREATE 权限,强制新建表必须指定 Schema

效果对比

指标 修复前 修复后
public 表数 3200 50(仅跨模块共享表)
用户 Schema 数 0 5
权限管理方式 共享 dbadmin 团队独享角色 + Schema
新员工获得正确权限时间 无法(共用 dbadmin) 5 分钟(GRANT role)
catalog 锁相关的 DDL 延迟 基线 降低约 30%(对象更分散)

📝 虚构案例 2:search_path 错误导致生产报表数据静默错误

场景描述:某电商平台的 DBA 为数据分析团队创建了 analytics Schema,下放常用报表表。某日 BI 团队反馈「昨日 GMV 报表」数据与前日完全相同,怀疑数据加载失败。ETL 团队检查数据加载日志正常,analytics.daily_gmv 表数据已更新。但 BI 工具查询到的仍是旧数据。

诊断过程

-- 1. 检查 BI 用户的 search_path
SELECT user_name, search_path FROM v_catalog.users WHERE user_name = 'bi_reporter';
-- 结果:bi_reporter | "$user", public, v_catalog, v_monitor
-- 2. 检查 public 下是否有同名表
SELECT table_schema, table_name
FROM v_catalog.tables
WHERE table_name = 'daily_gmv' AND table_definition = '';
-- 结果:public | daily_gmv
--       analytics | daily_gmv
-- 存在两张同名表!
-- 3. 检查 public.daily_gmv 的最后更新时间
SELECT MAX(stat_date) FROM public.daily_gmv;
-- 结果:2026-06-10(3 天前的数据,报表使用的正是这张表)
-- 4. 检查 analytics.daily_gmv 的最后更新时间
SELECT MAX(stat_date) FROM analytics.daily_gmv;
-- 结果:2026-06-14(最新的数据,从未被查询到)

根因分析:BI 用户 bi_reporter 的 search_path 以 "$user" 开头,虽然无 bi_reporter 同名 Schema,但下一个是 public。而 public 下存在一张早期创建的 daily_gmv 表(ETL 迁移到 analytics 后忘记删除),导致 SELECT * FROM daily_gmv 始终命中 public.daily_gmv新表 analytics.daily_gmv 从未被查询到,报表数据实际停留在 3 天前,但无任何错误提示

修复方案

  1. 立即:删除 public.daily_gmv(确认无其他依赖后)
  2. 短期:修改 BI 用户的 search_path,将 analytics 放在 public 前面
  3. 长期:强制 BI 工具使用完全限定表名 analytics.daily_gmv,不依赖 search_path
  4. 检查:扫描所有 Schema 下是否存在同名表(见第 3.2 节 SQL)

效果对比

指标 修复前 修复后
BI 报表数据滞后 3 天(静默) 0(实时)
public 遗留同名表 1 张 0 张
SQL 使用完全限定表名 0% 100%

📋 真实案例 · Schema 污染导致升级后停库报错

来源:从11.1.1升级到12.0.4版本后停库报sandbox错误

客户场景:某运营商的 Vertica 集群从旧版本升级到 12.0.4。升级完成后尝试停库(admintools -t stop_db)时,操作报错退出,日志显示 Sandbox 相关字段查询失败。

诊断过程

-- 升级后 admintools 内部 SQL 查询 v_catalog.nodes 时触发错误
-- 错误信息指向 sandbox 字段不存在

进一步排查发现:public Schema 下存在一张名为 nodes 的表,是升级前某次巡检脚本通过 CREATE TABLE public.nodes AS SELECT * FROM nodes 创建的备份表。旧版本 v_catalog.nodes 系统表没有 sandbox 字段,升级到 12.0.4 后,admintools 的内部查询在某些路径下匹配到了 public.nodes(而非 v_catalog.nodes),因缺少新字段而报错。

修复

ALTER TABLE public.nodes RENAME TO public.nodes_backup_deprecated;

改名后停库操作恢复成功。

启示

  • 绝不要在用户 Schema(尤其是 public)下创建系统表的备份副本。使用文本导出(EXPORT TO PARQUET 或 CSV)而非 CREATE TABLE AS SELECT
  • 升级前应扫描所有用户 Schema,检查是否存在与系统表同名的表(见第 3.4 节)。
  • 这一原则不仅适用于升级——即使不升级,同名表也可能在涉及系统表的查询中产生歧义。

📋 真实案例 · 分区表 Access Policy 权限问题

来源:分区表上设置访问策略导致增删改报权限错误

客户场景:在 customers_tablebroker_info 两张分区表上配置了行级 Access Policy 后,非 manager 角色用户(user1,属于 broker 角色)无法执行 INSERT/UPDATE/DELETE 操作,报权限错误。

诊断过程

-- 检查 broker 角色在 public Schema 上的权限
SELECT grantee, privileges_description
FROM v_catalog.grants
WHERE object_name = 'public' AND object_type = 'SCHEMA';
-- 结果:broker 角色没有 public Schema 级别的任何权限

根因:Access Policy 只能限制 SELECT 看到的数据行,但不影响 DML 权限broker 角色缺少 Schema 级别的 INSERT/UPDATE/DELETE 权限,导致虽然能通过 Access Policy 看到数据,但无法修改。

修复

GRANT ALL ON SCHEMA public TO broker;
GRANT ALL ON ALL TABLES IN SCHEMA public TO broker;

启示:Access Policy 和 Schema 权限是两层独立的安全机制——Access Policy 控制「能看到什么」,Schema 权限控制「能做什么操作」。两者必须同时配置正确,缺一不可。


第 6 节:完整诊断流程实战

📝 虚构场景 · 完整演练

背景:某大型零售企业 Vertica 集群,3 节点 Enterprise 模式,使用 3 年。最近 DB 团队收到多个问题反馈:「查询越来越慢」「新建的表找不到」「有人能在我们的表里写数据」。决定系统性地诊断 Schema 管理状况。

时间线

T+0min — 全局摸底

-- 一览 Schema 全貌
SELECT
    table_schema,
    COUNT(*) AS table_count
FROM v_catalog.tables
WHERE table_definition = ''
GROUP BY table_schema
ORDER BY table_count DESC;

输出(截取):

table_schema         | table_count
public               | 1856
analytics            | 342
marketing            | 87
finance              | 23
logistics            | 15
...

判断public Schema 占了 1856 张表,占总量的 80%。这是典型的 Schema 规划缺失——大部分业务数据堆在 public 中。

T+10min — 分析 public 的表分布

-- 按表名前缀分组
SELECT
    SPLIT_PART(table_name, '_', 1) AS prefix,
    COUNT(*) AS table_count
FROM v_catalog.tables
WHERE table_schema = 'public' AND table_definition = ''
GROUP BY 1
HAVING COUNT(*) > 5
ORDER BY 2 DESC;

输出:sales_(423)、inv_(312)、cust_(198)、log_(176)、report_(155)、tmp_(89)……

判断

  • 至少 5 个业务模块混在 public
  • tmp_* 有 89 张表 → 大量临时表未清理

T+20min — 检查权限状态

SELECT grantee, privileges_description
FROM v_catalog.grants
WHERE object_name = 'public' AND object_type = 'SCHEMA';

输出:仅 dbadmin 有权限。

判断所有用户都在用 dbadmin 账户操作,无任何权限分层。这与「有人能在我们的表里写数据」的反馈吻合——因为根本没有权限隔离。

T+30min — 检查 search_path 配置

SELECT user_name, search_path FROM v_catalog.users;

输出:所有用户 search_path 均为默认 "$user", public, v_catalog, v_monitor

判断:所有用户查询不带 Schema 前缀时都先搜 public,这是「新建的表找不到」的原因——用户建了表但可能建到了其他位置或记错了表名。

T+40min — 制定修复计划

步骤 操作 预期效果
1 创建 5 个 Schema:salesinventorycustomerlogsreports,各设 DiskQuota 为每模块建立独立容器
2 按前缀迁移 public 中的表到对应 Schema 1856 张表从 public 清空
3 清理 tmp_* 临时表(89 张,确认无业务依赖) 减少 catalog 占用
4 创建 5 个角色 + 15 个用户,按 Schema 分配权限 实现权限隔离
5 修改 search_path,各团队默认搜自己的 Schema 避免跨模块同名表冲突
6 设立命名规范:新表必须用完全限定名 schema.table 从源头防止命名混乱

T+50min — 执行步骤 1-3(Schema 拆分 + 清理)

-- 创建 5 个 Schema
CREATE SCHEMA sales DISK_QUOTA '3T';
CREATE SCHEMA inventory DISK_QUOTA '2T';
CREATE SCHEMA customer DISK_QUOTA '1T';
CREATE SCHEMA logs DISK_QUOTA '2T';
CREATE SCHEMA reports DISK_QUOTA '1T';

T+90min — 执行步骤 4-6(权限 + search_path)

-- 为每个 Schema 创建角色
CREATE ROLE sales_rw;  CREATE ROLE inventory_rw;
CREATE ROLE customer_rw; CREATE ROLE logs_rw; CREATE ROLE reports_rw;

-- 分配权限
GRANT USAGE ON SCHEMA sales TO sales_rw;
GRANT ALL ON ALL TABLES IN SCHEMA sales TO sales_rw;
-- ... 类似地处理其他 Schema

-- 设置 search_path
ALTER USER sales_analyst SEARCH_PATH sales, public, v_catalog, v_monitor;
ALTER USER inventory_analyst SEARCH_PATH inventory, public, v_catalog, v_monitor;

最终效果

指标 处理前 处理后
public Schema 表数 1856 50(保留跨模块共享表)
用户 Schema 数 4 9(5 个新增)
权限模型 所有用户共用 dbadmin 5 角色 × 15 用户,按 Schema 隔离
search_path 准确率 全部用默认值 每个团队指向自己的 Schema
「找不到表」反馈 每周 3-4 次 0
DDL 操作平均延迟 基线 降低约 25%

第 7 节:快速诊断 SQL 工具箱

诊断目标 SQL 说明
查看所有 Schema 及所有者 SELECT schema_name, schema_owner, schema_namespace_name FROM v_catalog.schemata ORDER BY schema_name; Eon 模式关注 namespace_name
查看各 Schema 表数量 SELECT table_schema, COUNT(*) FROM v_catalog.tables WHERE table_definition='' GROUP BY 1 ORDER BY 2 DESC; > 1000 需关注
查看各 Schema 存储占用 SELECT anchor_table_schema AS schema_name, SUM(used_bytes)//1024^3 AS used_gb FROM v_monitor.projection_storage GROUP BY 1 ORDER BY 2 DESC; 单 Schema >50% 集群需拆分
查看 Schema 级别权限 SELECT object_name AS schema_name, grantee, privileges_description FROM v_catalog.grants WHERE object_type='SCHEMA' AND object_name NOT IN ('v_catalog','v_monitor','v_internal','v_txtindex') ORDER BY 1,2; SCHEMA 类型 grants 的 object_schema 为 NULL
查看 Schema 权限继承 SELECT principal, object_schema, privileges_description FROM v_catalog.inherited_privileges ORDER BY 2,1; 检查不期望的继承权限
查看用户 search_path SELECT user_name, search_path FROM v_catalog.users WHERE user_name NOT IN ('dbadmin','pseudosuperuser') ORDER BY 1; 确保业务 Schema 在 public 前
查看当前 search_path SHOW SEARCH_PATH; 在目标用户会话中执行
检查 Schema 下同名系统表 SELECT t.table_schema, t.table_name FROM v_catalog.tables t JOIN v_catalog.system_tables s ON t.table_name=s.table_name ORDER BY 1,2; 升级前必须检查
同名系统表 + 命中用户 search_path SELECT t.table_schema, t.table_name, LISTAGG(DISTINCT u.user_name) AS affected_users FROM v_catalog.tables t JOIN v_catalog.system_tables s ON t.table_name=s.table_name JOIN v_catalog.users u ON INSTR(',' || REPLACE(u.search_path,' ','') || ',', ',' || t.table_schema || ',')>0 GROUP BY 1,2 ORDER BY 1,2; 命中用户不带前缀时将静默查错表
查看 Schema DiskQuota 状态 SELECT object_name, CASE WHEN is_schema THEN 'Schema' ELSE 'Table' END, disk_quota_in_bytes//1024^3 AS quota_gb, total_disk_usage_in_bytes//1024^3 AS used_gb, ROUND(total_disk_usage_in_bytes*100.0/disk_quota_in_bytes,1) AS pct FROM DISK_QUOTA_USAGES ORDER BY pct DESC; 来自 Vertica Schema DiskQuota 配置指南
查看 Schema 下空表 SELECT table_schema, table_name FROM v_catalog.tables WHERE table_definition='' AND table_name NOT IN (SELECT DISTINCT anchor_table_name FROM v_monitor.projection_storage) ORDER BY 1,2; 候选清理目标

第 8 节:最佳实践清单

  1. 新表始终使用完全限定名 schema.table。不依赖 search_path 保证 SQL 可读性和安全性。search_path 是 fallback 机制,不应作为数据访问控制手段。当多 Schema 有同名表时,依赖 search_path 会导致静默访问错误表——没有报错、没有告警、数据就是错的。
  2. 新建表指定 Schema,不往 public 里扔public Schema 的默认 CREATE 权限应被收紧(REVOKE CREATE ON SCHEMA public FROM public)。每个项目/模块应有自己的 Schema,public 只放少量跨模块共享的通用表。来源:虚构案例 1 中 3200 张表堆在 public 的典型反面教材。
  3. 单 Schema 表数控制在 500 以内。超过 500 张表共享一个 Schema 时,DiskQuota 检查开销、权限审计复杂度、catalog 锁影响都会累积。如果业务确实需要大量表,按子模块拆分。来源:Vertica Schema DiskQuota 配置指南 巡检阈值 catalog_schema_diskquota_table_count: 1000
  4. Schema 拆分按数据生命周期+业务模块两个维度。先按生命周期分层(ODS/DWD/DWS/DIM),层内按业务子模块再拆分。单层单模块表数仍超过 500 才进一步拆分。不要为了「整洁」而过度拆分——Schema 过多也会增加 catalog 管理成本。
  5. 权限按角色分层,不直接授予用户。创建 reader/writer/admin 三层角色,按 Schema 授权,用户通过角色获取权限。好处:新员工入职 5 分钟完成授权,离职 1 分钟回收。权限变更不需要改每个用户的配置。
  6. Eon 模式下优先评估 Subcluster 隔离 + 多 Schema 组合。Subcluster 提供计算物理隔离(防嘈杂邻居),多 Schema 提供数据隔离,成本远低于多集群方案。Enterprise 模式下,10-50 个租户、需要独立备份的场景首选多 Schema 模型;>50 个租户需要跨租户分析时选单 Schema + tenant_id 模型。来源:Vertica 多租户实现最佳实践(含 5 种方案完整对比和选型决策树)。
  7. 升级前扫描所有用户 Schema 下的系统表副本CREATE TABLE public.nodes AS SELECT * FROM nodes 这类操作在旧版本常见,但升级后因字段变化会引发各种异常。用第 3.4 节 SQL 提前扫描。来源:从11.1.1升级到12.0.4版本后停库报sandbox错误。
  8. Schema DiskQuota 设置为实际用量的 1.3–1.5 倍。配额卡在刚好等于日常用量时,任何一次加载批次稍大就触发 ERROR 10764。1.3× 保证单日峰值不超限。具体操作见 Vertica Schema DiskQuota 配置指南 第 8 节。
  9. Access Policy 和 Schema 权限要同时配置,缺一不可。Access Policy 控制「能看到什么数据」,Schema 权限控制「能做什么操作」。配置了行级 Access Policy 但没给 Schema 级别的 INSERT/UPDATE/DELETE,用户能看到数据但无法修改。来源:分区表上设置访问策略导致增删改报权限错误。
  10. 建立 Schema 命名规范。Schema 名应体现业务域_层级(如 billing_odsbilling_dwd),表名体现主题_粒度(如 call_detail_dailycustomer_summary_monthly)。命名规范是 Schema 管理的第一道防线——当表名自解释时,search_path 的歧义风险和「找不到表」的运维问题都会大幅减少。

扩展阅读