Skip to content

CREATE / DROP VIEW ​

视图(VIEW)是 PostgreSQL 里最轻量的抽象层:它把一个命名的 SELECT 查询保存进系统目录,每次引用时再展开执行,自身不存储任何数据。本节把视图的创建与删除语法、列别名、WITH 选项、依赖关系、权限模型、ALTER VIEW 以及调试手段一次讲清,帮你在可控成本内搭起「数据访问层」。

前置知识

阅读本节前,建议先了解:基础查询(SELECT)、模式(Schema)

基础概念 ​

视图是什么 ​

视图的定义只有一句话:一个被命名的查询(a named query)。它不保存结果行,只保存定义文本与解析后的查询树。

text
   SELECT * FROM my_view;
        │
        ▼
   优化器:把 my_view 的定义文本替换进来
        │
        ▼
   等价于直接执行 my_view 里那条 SELECT

因此有几个必然结论:

事实说明
视图不占数据空间只有 pg_class 中一个元数据条目,pg_relation_size() 返回 0(或 0 字节头)
视图永远读到最新数据每次查询都重新执行底层 SELECT
视图不会加速查询它只是 SQL 文本的封装,能不能快取决于底层表与索引
视图可以嵌套视图基于视图之上,但嵌套层次过深会影响可读性与优化
视图必须显式列出列列名来自 SELECT 的输出列别名

视图 vs 表 vs 物化视图 ​

对象是否存数据数据新鲜度relkind能否建索引
表(Table)是实时r是
视图(View)否实时(每次重算)v否
物化视图(Matview)是取决于刷新m是(这是关键差异)

普通视图不能建索引。如果你的痛点是「这条视图查询太慢」,正确做法是在底层表上建索引,或者改用物化视图。

视图的本质是一条规则 ​

在 PostgreSQL 内部,视图是用「查询重写规则」(_RETURN 规则)实现的。创建视图时,系统会自动在 pg_rewrite 里写入一条 ON SELECT DO INSTEAD 规则:

sql
-- 查看视图背后的规则
SELECT r.rulename, pg_get_ruledef(r.oid) AS rule_def
FROM pg_rewrite r
JOIN pg_class c ON c.oid = r.ev_class
WHERE c.relname = 'v_users_simple';
text
  rulename |                                                          rule_def
-----------+------------------------------------------------------------------------------------------
 _RETURN   | CREATE RULE "_RETURN" AS ON SELECT TO v_users_simple DO INSTEAD
           |   SELECT users.id, users.email, users.name FROM users;
(1 row)

这就是为什么删除视图时会连带删除它的 _RETURN 规则。规则系统的完整机制见 CREATE RULE 规则。

视图能解决什么 ​

场景视图的作用
简化复杂查询把多表 JOIN + 过滤封装成一个名字
列级权限控制只暴露部分列,隐藏敏感字段
稳定接口底层表结构变化时,视图保持对外接口不变
逻辑隔离对外只暴露「逻辑表」,屏蔽分表/分区细节
统一口径把业务过滤条件(如 is_deleted = false)固化进定义

语法与用法 ​

text
-- 创建视图
CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW name [ ( column_name [, ...] ) ]
    [ WITH ( view_option_name [= view_option_value] [, ... ] ) ]
    AS query
    [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]

-- 修改视图属性(不含查询定义)
ALTER VIEW name RENAME TO new_name
ALTER VIEW name SET SCHEMA new_schema
ALTER VIEW name OWNER TO new_owner
ALTER VIEW name SET ( view_option_name [= view_option_value] [, ... ] )
ALTER VIEW name RESET ( view_option_name [, ... ] )

-- 删除视图
DROP VIEW [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ]

WITH 选项(view options)目前只有两个:

选项取值含义版本
security_barrierboolean阻止下层谓词越过视图边界下推,保证「先按视图过滤、再由外层过滤」9.2+
security_invokerboolean视图按调用者权限执行(默认 false,即按定义者权限)15+
check_optionlocal / cascaded等价于在定义里写 WITH CHECK OPTION9.4+

最简可运行示例:

sql
CREATE TABLE users (
  id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  email      text   NOT NULL,
  name       text   NOT NULL,
  password   text   NOT NULL,
  is_deleted boolean NOT NULL DEFAULT false
);

CREATE VIEW v_users_simple AS
SELECT id, email, name
FROM users
WHERE is_deleted = false;
text
CREATE TABLE
CREATE VIEW
sql
-- 像表一样查询
SELECT * FROM v_users_simple;
text
 id | email | name
----+-------+------
(0 rows)

详细说明 ​

列别名:显式、隐式与自动推导 ​

视图的列名有三个来源,优先级从高到低:

  1. 视图定义里显式给出的列名列表(CREATE VIEW v (a, b) AS ...)
  2. SELECT 输出列的别名(SELECT id AS user_id ...)
  3. 底层列名(SELECT id ... → 列名 id)
sql
-- 方式 1:视图级列名列表(最明确)
CREATE VIEW v_orders_a (order_id, buyer, amount) AS
SELECT id, user_id, amount FROM orders;

-- 方式 2:SELECT 内别名
CREATE VIEW v_orders_b AS
SELECT id AS order_id, user_id AS buyer, amount AS amount FROM orders;

-- 方式 3:依赖底层列名
CREATE VIEW v_orders_c AS
SELECT id, user_id, amount FROM orders;

三种方式得到的列名完全相同。当输出列是表达式时,PostgreSQL 会自动推导名字:

sql
CREATE VIEW v_order_stats AS
SELECT
  user_id,
  count(*)   AS order_count,
  sum(amount) AS total_amount
FROM orders
GROUP BY user_id;
sql
-- 查看视图的列名与类型
\d v_order_stats
text
                    View "public.v_order_stats"
    Column    |  Type   | Collation | Nullable | Default
--------------+---------+-----------+----------+---------
 user_id      | bigint  |           |          |
 order_count  | bigint  |           |          |
 total_amount | numeric |           |          |

如果表达式没有别名,PostgreSQL 会生成 ?column? 之类的名字,这是不推荐的做法:

sql
-- 不推荐:表达式没有别名,列名自动生成,难以引用
CREATE VIEW v_bad AS
SELECT user_id, sum(amount) FROM orders GROUP BY user_id;

\d v_bad
text
           View "public.v_bad"
  Column  |  Type   | Collation | Nullable | Default
----------+---------+-----------+----------+---------
 user_id  | bigint  |           |          |
 sum      | numeric |           |          |

当同名表达式出现多次时,名字会带数字后缀(sum、sum_1……),非常容易出错。永远给表达式列起别名。

WITH 选项:security_barrier ​

security_barrier 解决的是「谓词下推导致的安全漏洞」。设想一个视图只暴露「自己部门」的行,其中包含一个可能报错的表达式函数:

sql
-- 定义一个会除零的过滤函数(示意)
CREATE FUNCTION safe_div(a numeric, b numeric) RETURNS numeric
LANGUAGE sql IMMUTABLE AS $$ SELECT a / b $$;

如果没有 security_barrier,优化器可能把用户写在外层的 WHERE 谓词下推到视图内部先执行,从而触发本不该被执行的函数(行级安全与视图安全的核心就是这个「求值顺序」问题):

sql
-- 带 security_barrier 的视图:先按视图条件过滤,再执行外层条件
CREATE VIEW v_secret WITH (security_barrier = true) AS
SELECT id, dept_id, salary FROM employees WHERE dept_id = current_setting('app.dept')::int;

-- 修改已有视图以开启/关闭该选项
ALTER VIEW v_secret SET (security_barrier = true);
ALTER VIEW v_secret RESET (security_barrier);

代价是失去谓词下推能力,性能会下降。完整分析见 视图与性能(视图展开)。

WITH 选项:security_invoker(PG 15+) ​

默认情况下,视图按下「视图所有者」的权限执行(security_invoker = false)。这在「视图用于授权访问」时是优点(只需给视图 SELECT 权限),但也是安全隐患:如果视图所有者是超级用户或权限很大的角色,调用者就能通过视图读到本无权访问的数据。

PostgreSQL 15 引入了 security_invoker,让视图按调用者权限执行:

sql
-- PostgreSQL 15+
CREATE VIEW v_employees_public
WITH (security_invoker = true) AS
SELECT id, name, dept FROM employees;

-- 对已有视图开启
ALTER VIEW v_employees_public SET (security_invoker = true);

两种模式的对比:

模式权限判定依据适用场景
security_invoker = false(默认)视图所有者的权限 + 视图的 ACL用视图做「受控授权」入口
security_invoker = true(PG 15+)调用者自己的权限 + 底层对象 ACL希望底层 RLS / 权限仍然生效

security_invoker = true 时,调用者必须同时拥有底层表的权限,否则即便有视图权限也会报 permission denied for table employees。这是与旧行为的最大区别,迁移时容易踩到。

CREATE OR REPLACE VIEW 的限制 ​

OR REPLACE 只能替换查询定义,且列结构必须兼容:

能否修改说明
能否改查询体可以
能否增加列可以,但新列必须追加在末尾
能否删除列不可以(除非 DROP VIEW 重建)
能否改列名不可以
能否改列类型不可以(例如 int → text)
能否改列顺序不可以
能否改 WITH 选项可以
sql
CREATE VIEW v_a AS SELECT id, email FROM users;

-- 正确:在末尾追加新列
CREATE OR REPLACE VIEW v_a AS SELECT id, email, name FROM users;

-- 错误!列顺序/名字/类型改变
CREATE OR REPLACE VIEW v_a AS SELECT email, id FROM users;
text
ERROR:  cannot change name of view column "id" to "email"
HINT:  Use ALTER VIEW ... RENAME COLUMN ... to change name of view column instead.
sql
-- 错误!试图改变列类型
CREATE OR REPLACE VIEW v_a AS SELECT id, email, name::text AS name FROM users;
text
ERROR:  cannot change data type of view column "name" from text to text

(上例在类型真的发生改变时才会报错;关键是改类型不被允许。)遇到必须改结构的情况,只能删除重建,或用 ALTER VIEW ... RENAME / SET SCHEMA 做改名与迁移。

依赖关系与 CASCADE ​

视图对底层对象有依赖,记录在 pg_depend 中。删除视图依赖的对象时,默认 RESTRICT 会直接报错:

sql
CREATE TABLE orders (
  id      bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  user_id bigint NOT NULL,
  amount  numeric(12,2) NOT NULL
);

CREATE VIEW v_orders AS SELECT id, user_id, amount FROM orders;

-- 尝试删除底层表
DROP TABLE orders;
text
ERROR:  cannot drop table orders because other objects depend on it
DETAIL:  view v_orders depends on table orders
HINT:  Use DROP ... CASCADE to drop the dependent objects too.

加上 CASCADE 会静默连带删除视图,这在生产环境是危险动作:

sql
-- 危险!会连带删掉所有依赖视图
DROP TABLE orders CASCADE;
text
NOTICE:  drop cascades to view v_orders
DROP TABLE

删除视图本身同样有依赖链:

sql
CREATE VIEW v_orders_2 AS SELECT * FROM v_orders WHERE amount > 0;

-- 删除被依赖的视图
DROP VIEW v_orders;
text
ERROR:  cannot drop view v_orders because other objects depend on it
DETAIL:  view v_orders_2 depends on view v_orders
HINT:  Use DROP ... CASCADE to drop the dependent objects too.

查依赖关系最实用的两个手段:

sql
-- 查这个视图依赖了哪些对象
SELECT
  dependent.relname AS dependent_view_name,
  dependent.relkind AS dependent_kind,
  referenced.relname AS referenced_name
FROM pg_depend d
JOIN pg_rewrite r ON r.oid = d.objid
JOIN pg_class dependent  ON dependent.oid  = r.ev_class
JOIN pg_class referenced ON referenced.oid = d.refobjid
WHERE dependent.relname = 'v_orders'
  AND referenced.oid <> dependent.oid;
text
 dependent_view_name | dependent_kind | referenced_name
---------------------+----------------+-----------------
 v_orders            | v              | orders
(1 row)
sql
-- 用 \d+ 看视图定义,一眼看清底层对象
\d+ v_orders
text
                         View "public.v_orders"
 Column  |     Type     | Collation | Nullable | Default | Storage | Description
---------+--------------+-----------+----------+---------+---------+-------------
 id      | bigint       |           |          |         | plain   |
 user_id | bigint       |           |          |         | plain   |
 amount  | numeric(12,2)|           |          |         | main    |
View definition:
 SELECT id,
    user_id,
    amount
   FROM orders;

视图的权限模型 ​

创建视图时必须对底层对象有相应权限,但视图一旦建立,其权限判定默认基于视图所有者:

sql
-- 给普通角色只授视图查询权限,不给底层表权限
CREATE ROLE app_reader LOGIN PASSWORD 'secret';
GRANT USAGE ON SCHEMA public TO app_reader;
GRANT SELECT ON v_users_simple TO app_reader;

-- 验证(以 app_reader 身份)
SET ROLE app_reader;
SELECT * FROM v_users_simple;   -- 成功(视图所有者是超级用户/表 owner)
SELECT * FROM users;            -- 失败
text
ERROR:  permission denied for table users

用视图做列级权限控制是最常见的用法:把敏感列排除在外,只暴露允许的列。

sql
-- 只暴露 id / email / name,隐藏 password
CREATE VIEW v_users_public AS
SELECT id, email, name FROM users WHERE is_deleted = false;

GRANT SELECT ON v_users_public TO app_reader;
REVOKE ALL ON users FROM app_reader;

三种权限控制的对比:

手段粒度能否按行过滤能否隐藏列
GRANT 列级权限列否可以(列级 GRANT)
视图 + 授权列 + 行可以可以
行级安全(RLS)行可以(按角色/会话)否

视图与搜索路径(search_path) ​

视图定义中的非限定名字在创建时按当时的 search_path 解析,解析结果被固化(存为对象 OID)。这让视图比「随时可能换 schema 的 SQL 文本」更安全,但也会在 search_path 变化时产生意外:

sql
-- 当前 search_path = public
CREATE VIEW v_products AS SELECT id, name FROM products;

-- 之后有人创建了 reporting.products 并把 search_path 改成 reporting
SET search_path = reporting, public;
-- 视图仍然读 public.products(OID 已固化),而不是 reporting.products
SELECT * FROM v_products;

但函数调用的解析规则不同:视图里的函数若未加 schema 限定,在执行时按当前 search_path 解析,这构成经典的安全隐患。

sql
-- 危险!未限定的函数调用会被 search_path 影响
CREATE VIEW v_dangerous AS
SELECT id, upper(name) AS name_upper FROM products;

推荐做法:在视图定义里给所有函数与对象加 schema 限定,或把视图放在不受用户 search_path 影响的 schema 里。

sql
-- 推荐:schema 限定,杜绝 search_path 劫持
CREATE VIEW v_safe AS
SELECT id, pg_catalog.upper(p.name) AS name_upper
FROM public.products AS p;

search_path 与 SECURITY DEFINER

视图默认按定义者权限执行,行为上类似 SECURITY DEFINER 函数。若函数名未限定,攻击者可以通过临时改写 search_path 让自己的恶意函数被解析到——这与 SECURITY DEFINER / INVOKER 里的风险完全同源。

ALTER VIEW:改名、换 schema、换 owner ​

ALTER VIEW 不能改查询定义(那要用 CREATE OR REPLACE VIEW),只能改属性:

sql
-- 重命名视图
ALTER VIEW v_users_simple RENAME TO v_users_active;

-- 重命名已有的列(PG 支持)
ALTER VIEW v_users_active RENAME COLUMN name TO full_name;

-- 移到另一个 schema
CREATE SCHEMA reporting;
ALTER VIEW v_users_active SET SCHEMA reporting;

-- 改所有者
ALTER VIEW reporting.v_users_active OWNER TO app_owner;

-- 修改 / 重置 view options
ALTER VIEW reporting.v_users_active SET (security_barrier = true);
ALTER VIEW reporting.v_users_active RESET (security_barrier);

重命名视图不会自动更新引用它的其他视图定义文本(它们通过 OID 引用,仍然有效),但如果别的视图的定义文本里写死了名字,pg_get_viewdef() 输出会随之更新。

用系统视图与系统表查看视图 ​

查询入口内容特点
\d+ view_name列与定义最方便的交互方式
\dv当前库所有视图psql 元命令
pg_views视图名、schema、owner、定义PostgreSQL 专有
information_schema.views标准视图信息跨库可移植,含 is_updatable
pg_get_viewdef('v'::regclass, true)格式化后的定义可编程获取
pg_class.relkind = 'v'视图对象元数据查询的通用入口
sql
-- PostgreSQL 专有视图
SELECT schemaname, viewname, viewowner, definition
FROM pg_views
WHERE schemaname = 'public'
ORDER BY viewname;
text
 schemaname |     viewname      | viewowner |                    definition
------------+-------------------+-----------+-------------------------------------------------
 public     | v_orders          | postgres  |  SELECT id, +                                 +
            |                   |           |     user_id, +                                +
            |                   |           |     amount +                                  +
            |                   |           |    FROM orders;
(1 row)
sql
-- 标准视图(含可更新性标志)
SELECT table_schema, table_name, is_updatable, is_insertable_into, check_option
FROM information_schema.views
WHERE table_schema = 'public'
ORDER BY table_name;
text
 table_schema |     table_name     | is_updatable | is_insertable_into | check_option
--------------+--------------------+--------------+--------------------+--------------
 public       | v_orders           | YES          | YES                | NONE
 public       | v_users_simple     | YES          | YES                | NONE
(2 rows)
sql
-- 用 pg_class 精确定位视图对象
SELECT c.oid::regclass AS view_name, c.relkind, c.reloptions
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' AND c.relkind = 'v';
text
     view_name    | relkind |  reloptions
------------------+---------+---------------
 v_orders         | v       |
 v_users_simple   | v       |
(2 rows)

注意 reloptions:只有设置了 WITH 选项的视图才有值,默认是空。

sql
-- 设了 security_barrier 之后
SELECT relname, reloptions FROM pg_class WHERE relname = 'v_secret';
text
 relname  |           reloptions
----------+--------------------------------
 v_secret | {security_barrier=true}
(1 row)

视图作为抽象层的最佳实践 ​

视图最大的价值是给应用一个稳定的数据接口,让底层表可以自由演进。

sql
-- 接口层:对外只暴露 v_customer_profile
CREATE VIEW v_customer_profile AS
SELECT
  c.id           AS customer_id,
  c.name         AS customer_name,
  c.email,
  coalesce(o.order_count, 0)  AS order_count,
  coalesce(o.total_amount, 0) AS total_amount
FROM customers c
LEFT JOIN (
  SELECT user_id, count(*) AS order_count, sum(amount) AS total_amount
  FROM orders GROUP BY user_id
) o ON o.user_id = c.id;

当 orders 拆分成 orders_2025 / orders_2026 时,只需改视图定义,应用代码零改动:

sql
CREATE OR REPLACE VIEW v_customer_profile AS
SELECT
  c.id AS customer_id, c.name AS customer_name, c.email,
  coalesce(o.order_count, 0) AS order_count,
  coalesce(o.total_amount, 0) AS total_amount
FROM customers c
LEFT JOIN (
  SELECT user_id, count(*) AS order_count, sum(amount) AS total_amount
  FROM (SELECT user_id, amount FROM orders_2025
        UNION ALL
        SELECT user_id, amount FROM orders_2026) t
  GROUP BY user_id
) o ON o.user_id = c.id;

注意 CREATE OR REPLACE VIEW 要求列名、顺序、类型完全一致——上面的改写正好满足,所以可以原地替换。

实战示例 ​

场景:为一个人力资源系统搭建「对外只暴露脱敏数据」的视图层,并实现列级权限控制。

第 1 步:建表与数据

sql
DROP TABLE IF EXISTS employees CASCADE;

CREATE TABLE employees (
  id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  emp_no     text   NOT NULL UNIQUE,
  name       text   NOT NULL,
  email      text   NOT NULL,
  phone      text   NOT NULL,
  salary     numeric(12,2) NOT NULL,
  dept_id    integer NOT NULL,
  hired_at   date   NOT NULL,
  is_active  boolean NOT NULL DEFAULT true
);

INSERT INTO employees (emp_no, name, email, phone, salary, dept_id, hired_at, is_active)
SELECT
  'E' || lpad(g::text, 5, '0'),
  'Employee ' || g,
  'emp' || g || '@corp.example',
  '138' || lpad((g % 100000000)::text, 8, '0'),
  (6000 + (g % 20) * 1200)::numeric(12,2),
  1 + (g % 5),
  DATE '2018-01-01' + (g % 2000),
  (g % 20 <> 0)
FROM generate_series(1, 5000) AS g;

ANALYZE employees;
text
DROP TABLE
CREATE TABLE
INSERT 0 5000
ANALYZE

第 2 步:建立三个层次的视图

sql
-- 公共视图:隐藏薪资与手机号,仅活跃员工
CREATE VIEW v_emp_public AS
SELECT id, emp_no, name, email, dept_id, hired_at
FROM employees
WHERE is_active = true;

-- 部门视图:额外暴露手机号
CREATE VIEW v_emp_dept AS
SELECT id, emp_no, name, email, phone, dept_id, hired_at
FROM employees
WHERE is_active = true;

-- HR 视图:全部字段
CREATE VIEW v_emp_hr AS
SELECT * FROM employees;

-- 给公共视图加 security_barrier,防止外部谓词下推绕过脱敏逻辑
ALTER VIEW v_emp_public SET (security_barrier = true);

第 3 步:授权

sql
CREATE ROLE app_public LOGIN PASSWORD 'secret';
GRANT USAGE ON SCHEMA public TO app_public;

GRANT SELECT ON v_emp_public TO app_public;
REVOKE ALL ON employees FROM app_public;

第 4 步:验证权限隔离

sql
SET ROLE app_public;

-- 成功:公共视图可用
SELECT count(*) FROM v_emp_public;
text
 count
-------
  4750
(1 row)
sql
-- 失败:没有底层表权限,也没有其他视图权限
SELECT salary FROM employees LIMIT 1;
text
ERROR:  permission denied for table employees
sql
RESET ROLE;

第 5 步:验证视图定义与依赖

sql
SELECT viewname, viewowner, definition
FROM pg_views
WHERE viewname = 'v_emp_public';
text
    viewname    | viewowner |                                  definition
----------------+-----------+-------------------------------------------------------------------------------
 v_emp_public   | postgres  |  SELECT id,            +                                                      +
                |           |     emp_no,            +                                                      +
                |           |     name,              +                                                      +
                |           |     email,             +                                                      +
                |           |     dept_id,           +                                                      +
                |           |     hired_at           +                                                      +
                |           |    FROM employees      +                                                      +
                |           |   WHERE is_active = true;
(1 row)
sql
-- 确认 reloptions 生效
SELECT relname, reloptions FROM pg_class WHERE relname = 'v_emp_public';
text
   relname    |           reloptions
--------------+--------------------------------
 v_emp_public | {security_barrier=true}
(1 row)

第 6 步:确认视图不占数据空间

sql
SELECT
  c.relname                AS object_name,
  c.relkind                AS kind,
  pg_relation_size(c.oid)  AS bytes
FROM pg_class c
WHERE c.relname IN ('employees', 'v_emp_public')
ORDER BY c.relname;
text
  object_name  | kind | bytes
---------------+------+--------
 employees     | r    | 524288
 v_emp_public  | v    |      0
(2 rows)

视图的 pg_relation_size 是 0,直观印证了「视图不存储数据」。

第 7 步:清理

sql
DROP VIEW v_emp_public, v_emp_dept, v_emp_hr;
DROP TABLE employees;
DROP ROLE app_public;

注意事项 ​

  • 视图不存储数据,也不加速查询。 慢视图要在底层表建索引,或改用物化视图。

    sql
    -- 错误认知:建视图就能提速
    CREATE VIEW v_slow AS SELECT ... FROM huge_table WHERE ...;
  • 视图不能建索引。 CREATE INDEX ... ON view_name 会直接报错:

    sql
    -- 错误!views cannot have indexes
    CREATE INDEX idx_v ON v_orders (user_id);
    text
    ERROR:  "v_orders" is not a table or materialized view
  • CREATE OR REPLACE VIEW 不能改列名、列顺序、列类型。 需要结构性变更时只能 DROP + CREATE。

  • DROP ... CASCADE 会静默删除所有依赖视图。 生产环境优先 RESTRICT(默认),先看清依赖再决定。

  • 未限定 schema 的函数名是安全隐患。 视图默认类似 SECURITY DEFINER,务必用 pg_catalog. / 显式 schema 限定:

    sql
    -- 不推荐
    SELECT upper(name) FROM t;
    -- 推荐
    SELECT pg_catalog.upper(t.name) FROM public.t;
  • security_invoker = true(PG 15+)要求调用者拥有底层对象权限。 打开后常见的「突然没权限了」就是这个原因。

  • security_barrier 会阻止谓词下推,性能可能下降。 只在确实需要保证求值顺序时开启。

  • 删除视图要显式 DROP VIEW。 TRUNCATE 之类对视图无效。

  • 视图列名自动推导不可依赖。 表达式列永远给别名,否则升级/改表时列名会漂移。

  • 别在视图上做业务写入逻辑(依赖 INSTEAD OF 触发器的可写视图除外,见 INSTEAD 规则)。

最佳实践 ​

  • 给视图显式声明列名或用别名,让接口稳定:

    sql
    CREATE VIEW v_report (day, order_cnt, gmv) AS
    SELECT date_trunc('day', created_at), count(*), sum(amount)
    FROM orders GROUP BY 1;
  • 视图定义里所有对象都加 schema 限定,规避 search_path 劫持:

    sql
    CREATE VIEW v AS
    SELECT p.id, pg_catalog.upper(p.name) FROM public.products p;
  • 用视图固化「软删除」与「租户隔离」过滤条件,避免每个查询重复写:

    sql
    CREATE VIEW v_active_orders AS
    SELECT * FROM orders WHERE is_deleted = false;
  • 视图只做「列裁剪 + 行过滤 + JOIN 组装」,不堆砌复杂逻辑,把重计算留给物化视图或应用层。

  • 对外接口用视图,内部查询直接查表。视图是接口,不是性能工具。

  • 改视图结构前先查依赖,避免 CASCADE 误删:

    sql
    SELECT dependent.relname FROM pg_depend d
    JOIN pg_rewrite r ON r.oid = d.objid
    JOIN pg_class dependent ON dependent.oid = r.ev_class
    JOIN pg_class referenced ON referenced.oid = d.refobjid
    WHERE referenced.relname = 'v_orders' AND dependent.oid <> referenced.oid;
  • 授权只授视图,不授底层表,把视图当成唯一的访问入口。

  • GRANT 后记得 GRANT USAGE ON SCHEMA,否则调用者连 schema 都进不去。

  • 用 pg_views / information_schema.views 做视图清单审计,定期清理僵尸视图:

    sql
    SELECT schemaname, viewname FROM pg_views ORDER BY schemaname, viewname;
  • 视图命名统一前缀 v_,让「表 / 视图 / 物化视图(mv_)」一眼可辨。

下一节

继续学习:可更新视图

参考链接 ​