news 2026/9/26 2:28:32

PostgreSQL用户与权限管理:角色、授权与默认权限实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PostgreSQL用户与权限管理:角色、授权与默认权限实战

如果管理过任何一套正经的 PostgreSQL,你大概率遇到过两种经典场面:一是新同事在测试库上死活查不着一张表,你在工位上一看就知道是权限没给到位;二是上线前夜,有人来问某个账号为啥能碰生产库的数据。两个问题指向同一个根源——账号与权限体系没有理顺。PostgreSQL 的用户与权限管理在圈内以"灵活"著称,但灵活的背面就是复杂。它不像 MySQL 那样有 user@host 这种相对直观的授权模型,而是把用户、角色、成员关系、对象权限、默认权限、行级安全叠在一起,新手第一次接触很容易绕晕。

这篇是 PostgreSQL 16 系列里专门讲用户与权限管理的一篇,内容包括基础概念、语法拆解、实战案例,以及我从 PG15 升到 PG16 之后在权限方面踩过的坑。适合刚接触 PostgreSQL 的同学建立完整认知,也适合有一定经验但平时主要写业务 SQL、很少系统梳理权限体系的开发同学查漏补缺。我会尽量把每个知识点都写清楚"为什么这样做",而不只是给一段能跑的命令。

1. 权限体系概览:先搞清角色、用户和授权的关系

很多人第一次被 PostgreSQL 绕住,就是因为它没有一个独立的"用户"概念。在 MySQL 里,用户就是登录账号,和库、表是分开的;在 PostgreSQL 里,用户和角色本质上是一个东西——用户就是带有 LOGIN 属性的角色。这个设计在早期看起来反直觉,但真正理解之后会发现它非常强大,因为你可以把一个角色当作"权限集合"来用,再把这个集合交给别的角色,实现权限的批量管理。

1.1 "用户"和"角色"在 PostgreSQL 里是同一个实体

先记住一句话:PostgreSQL 中的 ROLE 既可以是一个人(登录账号),也可以是一个组(权限集合)。系统表 pg_authid 里存的就是所有角色,你在 psql 里执行\du看到的列表就是它们。

创建的时候,CREATE ROLE和CREATE USER几乎等价,唯一的区别是CREATE USER会默认带上 LOGIN 属性,而CREATE ROLE默认不带。也就是说:

CREATE USER alice; -- 等价于 CREATE ROLE alice WITH LOGIN;

所以后面讲角色属性时,你不用把"用户"和"角色"当成两个东西,只需要区分:某个角色是不是允许登录(LOGIN),以及它被授予了哪些权限。

1.2 授权的层级:集群、数据库、Schema、对象

PostgreSQL 的权限不是一条 GRANT 走天下,而是分层的。从大到小大致是:

  • 集群级:能否连接、能否创建数据库、是否超级用户等,由角色属性控制。
  • 数据库级:CONNECT、CREATE、TEMPORARY。
  • Schema 级:USAGE(能不能用这个 schema 里的对象)、CREATE(能不能在里面建对象)。
  • 对象级:表、视图、序列、函数等各自有各自的权限集合。

这个层级最容易踩的坑是:很多人只给用户授了表的 SELECT,却忘记授 Schema 的 USAGE,结果查询时报permission denied for schema public。权限检查是从外层往内层走的,外层的门没开,里层的钥匙再多也没用。

1.3 拆解一条 GRANT 语句

先看一条最简单的授权:

GRANT SELECT ON TABLE public.orders TO app_rw;

这条语句拆开来看,包含四个要素:GRANT 关键字、权限类型(SELECT)、对象(public.orders)、被授权者(app_rw)。如果再加上 WITH GRANT OPTION,就意味着"这个用户拿到权限后,还可以把这个权限转授给别人"。别小看这个选项,很多权限失控就出在它上面——被授权者可以把自己拥有的权限继续扩散,而回收时往往需要级联处理。

到这里,权限体系的大框架就清楚了。接下来我们从账号管理的生命周期开始,把每一步操作都过一遍。

2. 账号管理实操:从创建角色到安全删除

用户和权限管理的第一课,永远是"账号怎么建、怎么改、怎么删"。

2.1 创建角色:CREATE ROLE 的完整姿势

创建一个用于业务系统的登录账号,最少要指定密码和登录权限:

CREATE ROLE app_rw WITH LOGIN PASSWORD 'strong_password_here';

如果你不想让这个角色能登录,就建一个"组角色"用来收拢权限:

CREATE ROLE app_readonly_role;

组角色不设 LOGIN,不接业务,只作为权限的载体。然后你把具体用户挂到这个组下面:

GRANT app_readonly_role TO alice;

这样 alice 就自动继承了 app_readonly_role 拥有的所有权限。以后想调整一批账号的权限,只需要改组角色的授权关系,不需要逐个账号 GRANT。

2.2 角色属性逐个说清楚

除了 LOGIN 和 PASSWORD,角色还有很多属性,我列一个常用表:

属性含义使用建议
SUPERUSER超级用户,绕过一切权限检查生产环境不要给业务账号
CREATEDB允许创建数据库只给 DBA 类角色
CREATEROLE允许创建和管理角色只给 DBA 类角色
REPLICATION复制账号专用只在搭建流复制时使用
BYPASSRLS绕过行级安全策略谨慎授予
CONNECTION LIMIT限制并发连接数业务账号常用,可防连接风暴
VALID UNTIL密码有效期临时账号必备
INHERIT / NOINHERIT是否继承成员角色的权限默认 INHERIT,一般不用改

举个例子,创建一个有效期到今年年底、并发连接上限 50 的临时分析账号:

CREATE ROLE analyst_bob WITH LOGIN PASSWORD 'xxx' CONNECTION LIMIT 50 VALID UNTIL '2025-12-31';

这里有两个细节值得注意。第一,VALID UNTIL到期后账号不是被删除,而是无法登录,数据都还在,适合做短期外包或临时合作场景。第二,CONNECTION LIMIT限制的是这个角色同时建立的连接数,不是所有用户加起来的总连接数,理解这一点就不会把参数设得太激进而影响正常业务。

2.3 修改角色:ALTER ROLE 的典型场景

账号建好之后免不了要调整。最常见的几个操作:

-- 修改密码 ALTER ROLE app_rw WITH PASSWORD 'new_password'; -- 临时禁用登录(保留账号和数据) ALTER ROLE app_rw WITH NOLOGIN; -- 重新启用登录 ALTER ROLE app_rw WITH LOGIN; -- 修改连接数上限 ALTER ROLE app_rw WITH CONNECTION LIMIT 100;

我的经验是,遇到"某个账号被怀疑泄露或需要暂停使用"的情况,先不要急着 DROP,改为NOLOGIN是更稳妥的第一步。原因有两点:DRO P 之后如果发现误判,重建账号还要重新配权限;而NOLOGIN撤销走 GRANT 即可,影响面小得多。

还有一类需求是修改角色在某个数据库里的默认配置,比如要限制某个角色只在指定库里生效:

ALTER ROLE app_rw IN DATABASE appdb SET statement_timeout = '30s';

这样 app_rw 在其他数据库里不受影响,只在 appdb 里执行超时 30 秒。这对写业务代码的同学特别有用,因为可以在数据库层面做一道防线,防止慢查询拖垮实例。

2.4 删除角色:依赖关系怎么处理

DROP ROLE 报错是我见过的新手高频问题。最常见报错是:

ERROR: role "app_rw" cannot be dropped because some objects depend on it

这个报错翻译成人话是:这个账号名下还持有表、视图、序列等对象,或者在某些对象的 owner 字段里。直接删是删不掉的,得先把这些对象转给别人:

-- 把旧账号拥有的所有对象重新指派给另一个角色 REASSIGN OWNED BY app_rw TO app_owner; -- 删除旧账号拥有的、且不属于任何人的残留对象 DROP OWNED BY app_rw; -- 最后删除角色 DROP ROLE app_rw;

REASSIGN OWNED BY会自动处理该角色在数据库中拥有的所有对象,包括表、索引、视图、序列、函数、schema 等等。DROP OWNED BY会删除该角色拥有但尚未转移的对象。这两条命令执行时一定要确认目标角色选对了,尤其DROP OWNED BY是删除对象而不是转移,误操作后果很严重。

我在实际运维里见过最危险的误操作,就是有人为了删一个账号,先把账号提升成超级用户,然后强行 DROP。这种操作一旦出问题,牵连的权限关系非常难恢复。正确的顺序永远是:先转移所有权,再清理,最后删角色。

3. 对象级授权语法详解:GRANT / REVOKE 全参数解析

账号建好之后,真正的重头戏是对象级授权。这一层最灵活,也最容易出错。

3.1 对象权限类型一览

PostgreSQL 里不同对象类型有不同的权限集合,我用表格整理一下:

对象类型可授权权限
数据库CREATE, CONNECT, TEMPORARY
SchemaCREATE, USAGE
表 / 视图 / 物化视图SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, MAINTAIN
序列USAGE, SELECT, UPDATE
函数 / 过程EXECUTE
类型USAGE
表空间CREATE

这里要解释一个容易混淆的点:TRUNCATE和DELETE是两回事。DELETE 只删行数据,TRUNCATE 是清空表并重置存储。如果一个账号只需要能删数据,不给TRUNCATE是更安全的默认选择。

再看REFERENCES权限,它允许用户创建外键引用该表。平时业务账号可能不需要,但如果有报表团队要把不同表关联起来做分析,没有 REFERENCES 权限,建外键或者 JOIN 某些场景会受限。这个权限经常被遗漏,等别人来问"为什么我建不了外键"时才想起来。

至于MAINTAIN,这是 PostgreSQL 16 新增的权限,专门用来解决"非表 owner 想执行 VACUUM / ANALYZE 却能不开超级用户"的问题。后面专门有一节单独讲,这里先记住它属于对象级权限。

3.2 GRANT 授权语法与常见场景

授权语法可以总结为:

GRANT 权限列表 ON 对象类型 对象名 TO 被授权者 [WITH GRANT OPTION];

一个典型只读账号授权,需要同时处理数据库、Schema、表三层:

-- 允许连接数据库 GRANT CONNECT ON DATABASE appdb TO app_ro; -- 允许使用 schema GRANT USAGE ON SCHEMA public TO app_ro; -- 允许读取所有表数据 GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_ro;

注意第二行USAGE ON SCHEMA,它不等于"能读表",只表示"允许进入这个 schema 并使用其中的对象"。没有这一步,后面 SELECT 授权做得再全也会报权限错误。这个分层设计不是多余的,它允许你创建一个"看得见门但进不去屋"的账号,对安全隔离很有用。

如果你希望一次性把某个 schema 里所有表的所有权限都交给一个账号:

GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_rw;

也可以针对特定表单独授权:

GRANT SELECT, INSERT, UPDATE, DELETE ON public.orders TO app_rw;

还有一个容易被忽略但其实很重要的对象是序列。业务表经常有自增主键,如果只给表授予INSERT权限,却没有给序列授予USAGE权限,插入时会报 "permission denied for sequence"。完整的读写账号授权必须把序列也覆盖进去:

GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_rw;

3.3 WITH GRANT OPTION 和 REVOKE 的回收行为

WITH GRANT OPTION表示被授权者可以把权限转授给其他人。这个选项的核心风险在于回收时需要级联,因为权限链条已经往外延伸了。

先看一下 REVOKE 的两种写法:

-- 普通回收:只回收 alice 自己的权限 REVOKE SELECT ON public.orders FROM alice; -- 回收转授权能力:alice 不能再给别人授权,但它自己已有的权限还在 REVOKE GRANT OPTION FOR SELECT ON public.orders FROM alice; -- 级联回收:回收权限,同时把 alice 转授权产生的所有下游权限一并回收 REVOKE SELECT ON public.orders FROM alice CASCADE;

我在生产环境处理过一次权限事故,就是某位同事给一个外包账号加上了WITH GRANT OPTION,外包账号又把它复制给了好几个服务账号。最后排查时不得不一路CASCADE回收,虽然解决问题了,但整个过程胆战心惊,因为中间有一层关系没梳理清楚,就可能误伤正常业务。所以我的建议很简单:默认不要用 WITH GRANT OPTION,需要"让某个人帮忙管理权限"的场景,优先选择把他加进一个管理组角色,而不是把转授权交给他个人。

3.4 ALTER DEFAULT PRIVILEGES:一次性解决"新表没权限"问题

手动 GRANT 最大的痛点在于:只对当前已存在的对象有效。今天授权了 100 张表,明天开发新建一张表,新账号还是没有权限。如果每次建表都手工补 GRANT,权限管理就会变成一场永无休止的追赶游戏。

ALTER DEFAULT PRIVILEGES就是为这个问题设计的。它的作用是定义"未来由某个角色创建的对象,默认授权给谁":

ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_rw; ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO app_rw;

这两行命令的意思是:当app_owner这个角色在 public schema 里新建表时,这些新表自动授予 app_rw 读写权限;新建序列时自动授予 app_rw 使用权限。注意FOR ROLE app_owner是关键,它限定了"谁的建表行为会触发默认授权",如果建表的人不是 app_owner,这个默认权限不生效。

在实际项目里,我会要求应用相关的表统一由某个建表角色创建(比如app_owner),这样配合ALTER DEFAULT PRIVILEGES,整个对象生命周期内的权限都能自动覆盖。已经存在的表再用GRANT ON ALL TABLES批量补齐一次,双管齐下。

这里还有个容易踩的细节:默认权限只对新建对象有效,不会回头修改已有对象。所以新上线一个项目时,正确的顺序是先手动批量授权已有对象,再设置默认权限,并让后续对象都使用指定角色创建。

4. 更高阶的权限控制手段:预定义角色、成员关系与行级安全

如果只是单人小项目,前面三节的内容已经够用了。但生产环境往往有多角色分权的问题,这就要用到 PostgreSQL 提供的"组角色"和预定义角色体系。

4.1 预定义角色:官方帮你审计好的常用权限包

PostgreSQL 内置了一批预定义角色,本质上是官方定义好的权限组,直接 GRANT 给某个账号即可:

预定义角色用途
pg_read_all_data读取所有数据库对象,相当于全局只读
pg_write_all_data写入所有数据库对象,相当于全局读写
pg_maintain对所有对象执行 VACUUM、ANALYZE、CLUSTER、REINDEX 等维护操作
pg_monitor读取监控相关视图和函数
pg_signal_backend取消或终止其他用户的后端进程
pg_checkpoint执行 CHECKPOINT
pg_use_reserved_connections使用预留连接槽位
pg_create_subscription创建逻辑复制订阅
pg_execute_server_program执行服务端程序,用于 COPY 等场景

举例来说,需要一个全局只读的监控账号:

CREATE ROLE monitor_user WITH LOGIN PASSWORD 'monitor_pass'; GRANT pg_monitor TO monitor_user; GRANT pg_read_all_data TO monitor_user;

这样 monitor_user 既能看监控视图,也能读所有业务表,比手动给几百张表逐个授权要省太多事。我建议不要自行模拟这些权限,直接使用官方预定义角色,因为 PG 后续版本会跟随系统目录变化维护这些角色的定义。

4.2 成员关系与 INHERIT:组角色的行为边界

当你执行GRANT group_role TO user_role时,user_role 就成为了 group_role 的成员。成员继承权限的行为受 INHERIT 属性控制。

默认情况下,角色 INHERIT,成员角色自动获得组角色的对象权限,不需要显式切换。比如:

CREATE ROLE app_ro_group; GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_ro_group; CREATE ROLE alice LOGIN PASSWORD 'xxx'; GRANT app_ro_group TO alice;

默认情况下 alice 登录后直接可以查询 public schema 的所有表,因为在权限检查时,PostgreSQL 会沿着角色成员链一路向上找权限。

但有一个例外要特别记住:INHERIT 只对对象权限生效,对角色属性(LOGIN、SUPERUSER、CREATEDB 等)不生效。也就是说,即使某个角色是超级用户的成员,它也不会因此自动成为超级用户,需要切换身份或者显式设置 SET ROLE 才能使用超级用户属性。这个设计其实是为了防止权限链无意间放大,理解之后就不会觉得它"反直觉"了。

4.3 SET ROLE:临时切换身份

有时候你需要以某个组角色的身份执行操作,而不是自己的身份。比如一个 DBA 平时用低权限账号登录,做维护时临时切换成维护角色:

SET ROLE pg_maintain; -- 执行 ANALYZE 等操作 ANALYZE public.orders; RESET ROLE;

SET ROLE和SET SESSION AUTHORIZATION的区别需要理清:前者切换的是"当前会话中的角色身份",后者切换的是"会话所属的登录用户身份"。日常运维中基本不用后者,前者足够。

有一个小坑:RESET ROLE 之后会回到登录角色的默认权限,但如果你开了多个嵌套的 SET ROLE,需要一层层 RESET。生产环境里我发现很多同学在脚本里写了 SET ROLE,但忘写 RESET ROLE,于是脚本后续所有操作都带着高权限身份执行,这个隐患在审计时会被拎出来重点查。

4.4 行级安全(RLS):让同一张表对不同用户显示不同行

对象级权限控制的是"能不能操作这张表",行级安全(Row-Level Security)控制的是"能操作这张表的哪些行"。两者互补,在实际的多租户场景里非常有价值。

启用 RLS 的步骤是:

-- 1. 启用表的行级安全 ALTER TABLE public.orders ENABLE ROW LEVEL SECURITY; -- 2. 创建一条策略,比如只允许用户看到属于自己组织的数据 CREATE POLICY tenant_isolation ON public.orders USING (org_id = current_setting('app.org_id')::int);

这条策略的含义是:当用户查询 orders 表时,PostgreSQL 会自动追加一个条件org_id = current_setting('app.org_id')。如果你在同一个表里存了 A 和 B 两个租户的数据,A 租户的连接即使执行SELECT * FROM orders,也只能看到自己的行。

启用 RLS 后默认行为是"拒绝",也就是说如果没有匹配的策略,普通用户看不到任何行。表 owner 和超级用户默认不受 RLS 限制,除非你显式执行FORCE ROW LEVEL SECURITY。业务账号如果不想被 RLS 影响,可以给角色加BYPASSRLS属性,但这等于放弃了行级安全防线,需要严格评估。

RLS 是把双刃剑,它极大地增强了多租户隔离能力,但也会让 SQL 执行计划变得更复杂,而且策略本身如果写得不对,可能连 owner 都查不到数据。我建议在开发阶段就启用并做好充分测试,不要等生产数据进去之后再“补”策略。

5. 实战:从开发库到生产库,一套完整授权方案设计

理论讲了那么多,还是落到一个实际场景里看看完整做法。假设现在要为一个中型 Web 应用设计数据库权限,业务库叫 appdb,schema 是 app,表结构由团队维护。

5.1 场景与角色拆分

我们需要三类角色:

角色用途权限范围
app_owner建表、改表结构,DDL 操作schema 和对象的 owner
app_rw应用服务运行账号,DML 操作表的增删改查、序列使用
app_ro报表、临时查询、数据导出表只读

再加上一个管理员账号 admin_dba,用于日常 DBA 操作,拥有超级用户权限。这样设计的核心思想是:应用连接时用 app_rw 或 app_ro,不要用拥有对象的账号直接跑 DML,避免应用账号误操作 DDL。

5.2 落地步骤

第一步,创建数据库和 schema,并指定 owner:

CREATE DATABASE appdb OWNER app_owner; \connect appdb CREATE SCHEMA app AUTHORIZATION app_owner;

第二步,创建应用账号:

CREATE ROLE app_rw WITH LOGIN PASSWORD 'rw_pass'; CREATE ROLE app_ro WITH LOGIN PASSWORD 'ro_pass' CONNECTION LIMIT 20;

第三步,授予数据库连接和 schema 使用权限:

GRANT CONNECT ON DATABASE appdb TO app_rw, app_ro; GRANT USAGE ON SCHEMA app TO app_rw, app_ro;

第四步,配置默认权限,让 app_owner 未来新建的任何表自动授权:

ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_rw; ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT USAGE, SELECT ON SEQUENCES TO app_rw; ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT SELECT ON TABLES TO app_ro; ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT SELECT ON SEQUENCES TO app_ro;

第五步,如果 schema 里已经有一些表(比如从旧库迁过来),需要手动批量补一次权限:

GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_rw; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA app TO app_rw; GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_ro; GRANT SELECT ON ALL SEQUENCES IN SCHEMA app TO app_ro;

到这里,app_rw 登录后可以直接读写应用表,app_ro 登录后可以查询、导出数据,两者都没有 DDL 权限,也不会误删表结构。

5.3 权限验收与检查

授权完成后,不能只看"好像能用了",要做一次系统性的检查。我常用的检查 SQL 是以 grantee 维度查看表权限:

SELECT grantee, privilege_type, table_name FROM information_schema.role_table_grants WHERE table_schema = 'app' ORDER BY grantee, table_name, privilege_type;

另一个方式是从系统目录看 ACL,能看到每个对象的授权全貌:

SELECT n.nspname AS schema, c.relname AS table, pg_get_userbyid(c.relowner) AS owner, acl.privileges FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace LEFT JOIN LATERAL aclexplode(c.relacl) AS acl ON TRUE WHERE c.relkind = 'r' AND n.nspname = 'app' ORDER BY n.nspname, c.relname;

aclexplode是理解 PostgreSQL ACL 的利器,它可以把表上的 ACL 数组展开成一行行权限记录,方便导出审计。建议把这两条 SQL 保存成常用查询脚本,每次给新环境配置完权限后跑一遍。

5.4 方案延伸:环境隔离

开发库、测试库、生产库的权限应该分开设计,不要在三个环境共用一套密码。我见过不少团队开发环境直接用超级用户跑业务,等到了生产环境才想起来收敛权限,结果应用上线第一天就因为权限不足报错,手忙脚乱补授权。正确的节奏是:开发环境可以宽松,但生产库从第一张表开始就执行 app_rw / app_ro 的最小权限原则。权限这场仗应该在生产环境之前就打赢,而不是在生产环境边救火边总结。

6. 升级到 PostgreSQL 16,权限这块要注意什么

PG16 在权限管理上做了几个比较重要的调整,从旧版本升级后如果不了解这些变化,很容易踩雷。

6.1 新增 MAINTAIN 权限

PG16 之前,要对一张不是自己拥有的表执行VACUUM或ANALYZE,通常需要超级用户。这在分权管理的环境里很麻烦:业务表的 owner 可能是一个建表账号,但日常维护人员并不需要拥有表的所有权限,只想做统计分析和优化。

PG16 新增了MAINTAIN权限,专门解决这类需求。拥有某张表的 MAINTAIN 权限后,就可以对该表执行 VACUUM、ANALYZE、CLUSTER、REINDEX 等维护操作,不需要成为表 owner,也不需要超级用户。

授权方式和其他对象权限完全一样:

GRANT MAINTAIN ON TABLE public.orders TO maintainer_role;

也可以批量授权:

GRANT MAINTAIN ON ALL TABLES IN SCHEMA app TO maintainer_role;

这个权限非常适合运维团队和数据分析团队。他们需要做定期的ANALYZE来生成统计信息,但不需要也不应该有 DDL 权限。过去很多团队只能给这类账号开超级用户,风险很大,有了 MAINTAIN 之后授权粒度精细了很多。

6.2 新增预定义角色 pg_maintain

与 MAINTAIN 权限配套,PG16 还新增了pg_maintain预定义角色。授予这个角色的账号可以对数据库内的所有对象执行维护操作,而无需拥有这些对象:

GRANT pg_maintain TO maintainer_role;

这里要区分的场景是:MAINTAIN是对象级权限,适合精确指定某几张表;pg_maintain是全局角色,适合"这个人要做全区维护"的场景。两者可以叠加使用,但实际设计权限方案时,我会优先考虑 MAINTIAIN 精确授权,只有维护人员数量多、且维护范围覆盖全库时才考虑直接给 pg_maintain。

6.3 从 PG15 / PG14 升级时容易踩的权限问题

  • public schema 的默认 CREATE 权限变化。PG15 之后,public schema 不再默认对 public 角色授予 CREATE 权限。这意味着从 PG14 升级到 PG15 或 PG16 后,普通用户在同一数据库的 public schema 里建表的操作会失败。如果你的应用确实需要在 public schema 里动态建表,需要显式授予:
GRANT CREATE ON SCHEMA public TO app_rw;

但更建议的做法是:使用独立 schema,而不是继续在 public 里建业务表。public schema 的定位应该是默认放系统信息的,业务表放独立 schema 会更安全。

  • 预定义角色的成员关系不会自动迁移。升级后检查一遍\du,确认 pg_read_all_data、pg_monitor 等角色的授权是否还在。有些安全加固类工具在旧版本里直接改系统表添加角色成员关系,升级后可能失效。

  • 密码认证方式的兼容。PG16 已经默认使用 SCRAM-SHA-256 密码加密,如果旧库里有使用 md5 认证的账号,升级后连接可能失败,需要在 pg_hba.conf 和密码格式上做好同步。这一点虽然不是纯权限问题,但影响登录行为,排查时常常容易往权限方向误判。

6.4 升级后的权限审计清单

作为运维习惯,升级完成后我会按这个清单快速过一遍:

  1. 用\du查看所有角色及属性,确认超级用户列表没有异常膨胀。
  2. 用\l查看数据库 owner 和权限,确认没有 plan 以外的默认 public 权限开放。
  3. 用\dn+查看 schema 权限,尤其确认 schema 的 CREATE 权限是否符合预期。
  4. 抽查几张核心表,确认业务账号的 SELECT / INSERT / UPDATE / DELETE 权限正确。
  5. 确认 pg_maintain、MAINTAIN 等新权限没有被错误授予给不必要的账号。

这套检查我一般会在升级演练环境的测试阶段做一次,生产切换后立刻再做一次,前后对比能快速发现权限配置漂移。

7. 权限问题排查思路与运维心得

最后一部分,聊一聊真正遇到权限问题时应该怎么排查。这部分内容很实战,建议保存下来遇到问题时候再翻。

7.1 常见权限报错与含义

报错信息常见原因
permission denied for schema publicSchema 层 USAGE 权限缺失
permission denied for table orders表级 SELECT / INSERT 等权限缺失
permission denied for sequence orders_id_seq序列 USAGE 权限缺失
permission denied for database appdb数据库 CONNECT 权限缺失
must be owner of table orders当前角色不是 owner,且没有 MAINTAIN 权限
role does not exist角色名写错了,或者还没有创建

排查思路可以总结成一条链:先确认能不能连上库,再看能不能进 schema,再看能不能操作表,一层一层往里走。很多人习惯直接去查表权限,查了半天没发现问题,最后发现是数据库连接权限没给,白白折腾。

7.2 快速定位一次权限问题的复盘路径

假设你收到反馈:"账号 bob 查不了 public.orders 表"。我会按这个顺序快速定位:

第一步,确认 bob 是否存在及是否能登录:

SELECT rolname, rolcanlogin, rolsuper FROM pg_roles WHERE rolname = 'bob';

第二步,确认 bob 对 appdb 的连接权限:

SELECT has_database_privilege('bob', 'appdb', 'CONNECT');

第三步,确认 bob 对 public schema 的权限:

SELECT has_schema_privilege('bob', 'public', 'USAGE');

第四步,确认 bob 对 orders 表的权限:

SELECT has_table_privilege('bob', 'public.orders', 'SELECT'); SELECT has_table_privilege('bob', 'public.orders', 'INSERT');

每一步返回true或false,很快就能定位是哪一层权限缺失。这比翻\dp输出直观得多,尤其是权限关系复杂、有多层组角色嵌套时,直接查询某个角色最终是否拥有某项权限,才是真正有效的办法。

7.3 长期权限运维的几个习惯

第一,不要随手给业务账号开超级用户。超级用户会绕过所有权限检查,会掩盖掉所有权限设计上的漏洞。业务代码里有不当操作,只有把它们暴露在最小权限环境下,才能尽早暴露问题。

第二,角色的命名和用途要有记录。我见过一个库里有 20 多个角色,没人说得清哪个角色是干什么用的。建议在备注里写明用途,比如创建角色时加上 COMMENT:

COMMENT ON ROLE app_rw IS 'appdb 应用读写账号,仅用于Web服务,禁止用于日常运维';

第三,定期做权限审计。至少每季度导出一次所有角色和各 schema 的 ACL,交给负责人确认。权限这个东西,时间一长就会因为各种临时授权逐步“膨胀”,等出事的时候再去追溯,成本远高于日常定期检查。

第四,利用ALTER DEFAULT PRIVILEGES作为权限管理的主路径,而不是事后补 GRANT。每次新项目上线,我都会先设计好默认权限,再让业务账号接触数据库。这能避免绝大多数"新表没权限"的凌晨告警。

第五,不要把权限都放在一个角色里。哪怕项目规模不大,也尽量保持"owner 角色 + 读写角色 + 只读角色"的结构。这个成本很低,但对后续的权限收敛和安全审计帮助极大。

我在实际维护中最大的体会是,权限管理做得好不好,往往不是看某一条授权语句写得有多完美,而是看整个体系是否可预期。所谓可预期,就是你不用翻半天日志,也能说出某个角色在某些条件下能做什么、不能做什么。PostgreSQL 提供的能力足够支撑这个目标,关键是把语法用对、把边界想清楚,然后让规则长期稳定地执行下去。希望这篇把概念和实战串起来的内容,能让你在搭权限体系的时候少走一些弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 2:25:29

黑马电商后台管理系统实战:从环境搭建到前后端联调与部署

简介:这是一套面向前后端开发者的电商后台管理系统实战资源,以前端 Vue.js 与后端 Node.js 为主线,覆盖用户管理、商品管理、订单管理、库存管理、数据分析、权限控制等核心业务模块,既适合初学者理解项目结构,也适合有…

作者头像 李华
网站建设 2026/9/26 2:24:37

恩施碎米荠基因组--Cell Discovery

The Cardamine enshiensis genome reveals whole genome duplication and insight into selenium hyperaccumulation and tolerance 恩施碎米荠基因组揭示全基因组复制事件及硒超富集与耐硒机制 摘要 恩施碎米荠(Cardamine enshiensis)是知名的硒超富集…

作者头像 李华
网站建设 2026/9/26 2:24:17

森林火灾烟雾检测数据集:VOC/COCO/YOLO标签转换与YOLO训练全流程

简介:本资源面向目标检测初学者与需要森林火灾烟雾识别方案的开发者,提供一套可直接用于YOLO系列训练的真实场景数据集。数据包含1000张高质量图片,场景丰富,经labelimg精细标注,并同步提供voc(xml)、coco(json)与yolo…

作者头像 李华