Skip to content

CREATE / DROP ROLE ​

角色(role)是 PostgreSQL 权限体系的基石。与其他数据库把「用户」和「组」做成两类不同实体不同,PostgreSQL 从 8.1 版本起就把这两者统一成了一种东西:角色。一个角色可以扮演用户(可以登录)、扮演组(接收权限再分发给别人),或者两者兼备——这取决于你给它设置了哪些属性。

理解角色是理解一切权限问题的前提。前面学习读写分离时,我们已经在连接串里用过 user=xxx,那个「user」本质上就是一个带 LOGIN 属性的角色。本节将从 CREATE ROLE 出发,把角色的完整属性、生命周期(创建 → 修改 → 删除)、内置预设角色以及角色相关的系统视图逐一讲透。

这一节只解决「角色本身」的问题:角色是什么、怎么造、怎么删。至于权限如何授予与回收(GRANT / REVOKE)、成员关系如何继承,属于后面几页的主题。先把地基打好,后面的墙才不会歪。

前置知识

建议先阅读:读写分离与中间件

基础概念 ​

在动手写 SQL 之前,先把角色体系里的核心术语厘清。下面这张表会贯穿整个「安全与权限」章节。

概念英文 / 记法说明
角色role集群级实体,可以拥有对象、接受授权、被授予成员关系
登录角色login role带 LOGIN 属性的角色,即通常意义上的「用户」
组角色group role不带 LOGIN 的角色,仅用于承载权限并分发给成员
角色属性role attributesSUPERUSER、CREATEDB、CREATEROLE 等作用于角色本身的特权
成员关系role membershipGRANT role_a TO role_b 建立的「b 是 a 的成员」关系
预设角色predefined rolespg_monitor、pg_read_all_data 等内置角色,由 GRANT 分发
会话角色session role当前连接最初使用的登录角色,即 session_user
当前角色current role当前正在执行语句的有效角色,即 current_user

角色即用户,角色即组 ​

PostgreSQL 官方文档对角色的定义只有一句话:角色是可以拥有数据库对象、可以拥有数据库特权的实体;它可以是「用户」、是「组」,或两者都是。这句话包含了三层意思:

  • 角色拥有对象:表、序列、函数、模式、数据库……谁创建的,属主(owner)就是谁。属主天然拥有该对象的全部可授予权限,以及删除、修改它的权利。
  • 角色接受授权:权限(SELECT、INSERT、CONNECT 等)可以授给任何角色;成员关系也可以授给任何角色。
  • 用户与组只是用法,不是类型:CREATE USER 不过是 CREATE ROLE ... LOGIN 的别名(历史写法),CREATE GROUP 不过是 CREATE ROLE ... NOLOGIN 的别名。同一个角色完全可以既登录又充当组。

正是这种「单一实体」的设计,让 PostgreSQL 的权限模型异常简洁:权限永远授给角色,人通过成为角色的成员来获得权限。你不需要记住「用户表」「组表」「权限表」三套东西,只需要记住一条链路:对象 → 权限 → 角色 ← 成员 ← 人。

集群级实体:角色不属于某个数据库 ​

角色定义在数据库集群(cluster)层面,而不是数据库层面。也就是说,CREATE ROLE 创建的角色在本实例的所有数据库中都有效,pg_authid 与 pg_auth_members 目录在整个集群里只有一份。

这一点有两层推论:

  • 同名角色在不同数据库中是同一个角色,属性完全一致,不存在「A 库里 alice 是普通用户、B 库里是管理员」这种事。
  • 权限(对象授权)才是数据库级的,角色能否连上某个库、能在这个库里做什么,由该库内的授权与 pg_hba.conf 共同决定。

角色属性一览 ​

角色属性(role attributes)是「作用于角色本身」的特权,与对象权限是两套体系。CREATE ROLE 的全部属性如下:

属性默认值说明
SUPERUSER / NOSUPERUSERNOSUPERUSER超级用户,绕过除登录外的一切权限检查
CREATEDB / NOCREATEDBNOCREATEDB可否创建数据库
CREATEROLE / NOCREATEROLENOCREATEROLE可否创建、修改、删除其他角色
INHERIT / NOINHERITINHERIT成员权限是否自动继承可用(详见角色继承)
LOGIN / NOLOGINNOLOGIN可否作为初始会话角色登录
REPLICATION / NOREPLICATIONNOREPLICATION可否建立复制连接、创建/删除复制槽
BYPASSRLS / NOBYPASSRLSNOBYPASSRLS可否绕过全部行级安全策略
CONNECTION LIMIT connlimit-1(不限)登录角色的并发连接数上限
[ ENCRYPTED ] PASSWORD '...' / PASSWORD NULLNULL密码(永远以加密形式存储;无密码则密码认证必然失败)
VALID UNTIL 'timestamp'无密码失效时间(只管密码,不管角色本身)
IN ROLE role_name—新角色加入指定角色(成为其成员)
ROLE role_name—把指定角色加为新角色的成员
ADMIN role_name—同 ROLE,但成员同时获得 ADMIN 选项

属性不是权限

SUPERUSER、CREATEDB、CREATEROLE 这类属性不能通过成员关系继承。即使 alice 是 admin 组的成员、而 admin 拥有 CREATEDB,alice 自己也建不了库——除非她 SET ROLE admin 切换身份。这是新手最常见的误解之一,后文与《角色继承》一页会反复强调。

语法与用法 ​

CREATE ROLE ​

完整语法如下(PostgreSQL 18):

sql
CREATE ROLE name [ [ WITH ] option [ ... ] ]

where option can be:

      SUPERUSER | NOSUPERUSER
    | CREATEDB | NOCREATEDB
    | CREATEROLE | NOCREATEROLE
    | INHERIT | NOINHERIT
    | LOGIN | NOLOGIN
    | REPLICATION | NOREPLICATION
    | BYPASSRLS | NOBYPASSRLS
    | CONNECTION LIMIT connlimit
    | [ ENCRYPTED ] PASSWORD 'password' | PASSWORD NULL
    | VALID UNTIL 'timestamp'
    | IN ROLE role_name [, ...]
    | ROLE role_name [, ...]
    | ADMIN role_name [, ...]

执行 CREATE ROLE 需要 CREATEROLE 特权或超级用户身份。几个最基础的例子:

sql
-- 能登录、无密码(密码认证永远失败,适用于 peer/trust 等本地认证场景)
CREATE ROLE jonathan LOGIN;

-- 能登录、带密码
CREATE USER davide WITH PASSWORD 'jw8s0F4';

-- 带密码且密码有时效
CREATE ROLE miriam WITH LOGIN PASSWORD 'jw8s0F4' VALID UNTIL '2027-01-01';

-- 能建库、能管角色(常用于中级管理员)
CREATE ROLE admin WITH CREATEDB CREATEROLE;

-- 纯组角色(默认就是 NOLOGIN)
CREATE ROLE app_readonly;

ALTER ROLE ​

ALTER ROLE 有三种形态:改属性、改名、设置会话默认参数。

sql
-- 1. 修改属性(选项与 CREATE ROLE 相同)
ALTER ROLE davide WITH CREATEROLE CREATEDB;
ALTER ROLE davide NOSUPERUSER NOCREATEDB;
ALTER ROLE davide CONNECTION LIMIT 20;
ALTER ROLE davide PASSWORD 'new_secret';
ALTER ROLE davide VALID UNTIL '2026-12-31';
ALTER ROLE davide NOINHERIT;

-- 2. 重命名(不影响属主关系)
ALTER ROLE davide RENAME TO david;

-- 3. 设置该角色每次登录后的默认会话参数
ALTER ROLE app_user SET search_path = app, public;
ALTER ROLE app_user IN DATABASE appdb SET statement_timeout = '5s';
ALTER ROLE app_user RESET statement_timeout;

改名有一个限制:如果角色是某个数据库对象的属主,或者在其他角色的成员关系中被引用,RENAME TO 在某些版本场景下会要求会话角色具备相应条件(最典型的报错场景是「会话用户与当前角色不一致」),通常以超级用户、且未 SET ROLE 的状态下执行即可。

DROP ROLE ​

sql
DROP ROLE [ IF EXISTS ] name [, ...]

DROP ROLE 会一并撤销该角色在各处的成员关系(它作为成员的身份),但不能删除仍然「拥有东西」的角色:只要该角色还拥有数据库对象、持有权限、或是组角色的成员,删除就会失败。完整流程见下文「DROP ROLE 与前置清理」。

SET ROLE / RESET ROLE ​

sql
-- 临时切换为某个成员角色
SET ROLE app_readwrite;

-- 回到初始登录角色(两种写法等价)
RESET ROLE;
SET ROLE NONE;

SET ROLE 是「使用组成员身份」的钥匙,与 INHERIT 语义的关系详见角色继承。

详细说明 ​

LOGIN 与 NOLOGIN:能否登录的分水岭 ​

LOGIN 是区分「用户」与「组」的唯一开关。不带 LOGIN 的角色不能出现在连接串的 user= 里,但依然可以拥有对象、接受授权、作为成员关系的节点。

sql
-- 典型分工:组角色 + 登录角色
CREATE ROLE app_readwrite NOLOGIN;   -- 组:读写
CREATE ROLE app_readonly  NOLOGIN;   -- 组:只读
CREATE USER alice;                    -- 人:登录角色
GRANT app_readwrite TO alice;         -- alice 挂到读写组

一般来说,人对应登录角色,职责对应组角色。人可以换(离职、入职),职责的授权却稳定不变——把权限直接授给一个个登录角色,很快就会演变成无法审计的权限泥潭。

SUPERUSER:绕过一切检查的危险属性 ​

超级用户绕过数据库内除「登录认证」之外的所有权限检查:读写任意表、执行任意函数、访问任意系统目录。同时,只有超级用户能创建超级用户。

生产环境应当遵守一条铁律:应用连不上超级用户,人也尽量别用超级用户日常操作。超级用户只保留给实例运维(参数调整、恢复、升级),业务库的全部工作都应通过「角色 + 授权」完成。数据库里日常连接数中超级用户占比越高,权限体系就越形同虚设。

CREATEDB 与 CREATEROLE ​

这两个属性分别对应「造库」与「造角色」两类管理动作:

  • CREATEDB:可以 CREATE DATABASE。注意它只管建库,不代表对新库里对象有任何权限——新库的属主是建库者本人。
  • CREATEROLE:可以创建、修改(ALTER ROLE)、删除(DROP ROLE)角色,也可以对角色加注释、修改安全标签。在 PG16 之前,CREATEROLE 的能力接近半个超级用户(可以把自己提权);PG16 起官方收紧了语义,详见下文「PG16 起 CREATEROLE 的收紧」。

INHERIT / NOINHERIT:继承的开关 ​

INHERIT 决定「当本角色成为其他角色的成员时,那些角色的对象权限是否自动可用」。默认 INHERIT(自动可用);设为 NOINHERIT 后,成员权限必须 SET ROLE 切换过去才能使用。

这里只做铺垫,完整的语义(包括 PG16 引入的按成员关系控制的 inherit_option)请阅读角色继承(INHERIT / NOINHERIT)。

REPLICATION 与 BYPASSRLS ​

  • REPLICATION:允许以复制模式连接(物理或逻辑复制),并允许创建、删除复制槽。这是极高特权的属性,只应授予真正用于复制的角色(如 repl)。只有超级用户或拥有 REPLICATION 的角色能授予它。
  • BYPASSRLS:绕过表上的全部行级安全策略。行级安全(RLS)本身是一个大主题,在本站有专门章节;这里只需记住两点:默认 NOBYPASSRLS,且超级用户与表属主(默认情况下)本来就不受 RLS 约束,所以 BYPASSRLS 主要服务于「普通业务角色但被信任可以看全量数据」的场景(例如 pg_read_all_data 的使用者想看全量数据时,官方建议就是给该角色设 BYPASSRLS)。

CONNECTION LIMIT:连接数上限 ​

CONNECTION LIMIT 为登录角色设置并发连接数上限,-1 表示不限。有三个容易忽略的细节:

  • 对超级用户不生效:超级用户不受此限制(这也符合「留后门」的思路)。
  • 只统计普通连接:prepared transactions 的连接与 background worker 不计入。
  • 强制是近似的:两个会话恰好在只剩一个空位时同时发起,可能双双失败。

生产上,应用角色通常会配合连接池(如 PgBouncer)使用,此时 CONNECTION LIMIT 可以作为「池子打穿数据库」的兜底保险丝,例如限制为 max_connections 的 20%。

PASSWORD 与密码加密(SCRAM-SHA-256) ​

密码只对 LOGIN 角色有意义,但语法上允许给任何角色设置。关键规则:

  • 密码永远以加密形式存储在系统目录中,ENCRYPTED 关键字可以接受但没有实际效果(向后兼容)。
  • 加密方式由参数 password_encryption 决定。PostgreSQL 14 起默认值为 scram-sha-256;更早版本默认 md5。
  • 如果提交的密码串本身已经是 MD5 或 SCRAM 加密格式,则按原样存储(不再二次加密),这也是 pg_dumpall 能原样迁移密码的原因。
  • PASSWORD NULL 显式设置空密码:该角色的密码认证将永远失败(不等于免密登录)。
  • MD5 密码支持已被官方标记为弃用,未来版本将移除,新环境一律使用 SCRAM。
sql
-- 查看当前密码加密方式
SHOW password_encryption;

-- 修改密码(SQL 方式:明文经网络传输,注意日志与历史记录)
ALTER ROLE app_user PASSWORD 'new_secret';

-- 更安全的方式:psql 的 \password 元命令(客户端加密后传输)
\password app_user

明文密码的暴露面

在 psql / CREATE ROLE / ALTER ROLE 中直接写明文密码,可能进入 shell 历史、服务器日志(log_statement 开启时)、pg_stat_activity(旧版本)等位置。交互式场景请优先使用 \password;自动化场景请把密码放在环境变量或密钥管理系统里,而不是脚本常量。

VALID UNTIL:密码有效期 ​

VALID UNTIL 只对密码认证生效:到期后密码认证失败,但角色本身仍然存在,通过证书、GSSAPI、peer 等非密码方式登录不受影响。常见用法:

sql
-- 临时审计账号,90 天后自动失效
CREATE ROLE auditor LOGIN PASSWORD '...' VALID UNTIL '2026-12-31';

-- 取消有效期
ALTER ROLE auditor VALID UNTIL 'infinity';

注意:VALID UNTIL 不会自动禁用或删除角色,它只是让「用户名 + 密码」这对凭证失效。

CREATE USER 与 CREATE GROUP ​

CREATE USER 是 CREATE ROLE ... LOGIN 的别名——唯一区别是隐含 LOGIN。CREATE GROUP 则等价于 CREATE ROLE ... NOLOGIN,属于更早期的历史写法。类似的还有 CREATE ROLE ... IN GROUP(与 IN ROLE 行为相同,PG18 文档已将其标记为弃用写法)以及 CREATE ROLE ... USER ...(同 ROLE 子句,同样弃用)。

sql
-- 以下两两等价
CREATE USER alice;                 =  CREATE ROLE alice LOGIN;
CREATE GROUP app_devs;             =  CREATE ROLE app_devs NOLOGIN;
CREATE ROLE alice IN ROLE app_devs;=  CREATE ROLE alice IN GROUP app_devs;

命令行工具 createuser 也封装了同样能力(内部就是调用 CREATE ROLE),并且以加密方式传输密码。

createuser:命令行工具 ​

需要在 Shell 脚本、Ansible、容器初始化里建角色时,createuser 比 psql -c 更顺手:

bash
# 交互式创建:逐项询问是否超级用户/可建库/可建角色等
createuser --interactive --pwprompt app_user

# 脚本化创建一个受限登录角色
createuser --login --connection-limit 10 reporter

# 创建超级用户(等价于 CREATE ROLE ... SUPERUSER LOGIN)
createuser --superuser admin_user

# --echo 显示它实际执行的 CREATE ROLE 语句,便于学习与审计
createuser --echo --login dev_user

# 指定目标实例与库(连接参数与 psql 一致)
createuser -h 127.0.0.1 -p 5432 -U postgres --login billing_svc

两个与 psql 内写 SQL 的差异值得记住:其一,--pwprompt 让密码经终端输入并加密后传输,不会落入 shell 历史;其二,它默认连接到本地库执行,集群级角色意味着「连哪个库执行效果都一样」。

IN ROLE / ROLE / ADMIN:创建时顺手建立成员关系 ​

三个子句解决「建角色时顺手挂进组」的需求,方向恰好相反:

子句语义默认选项
IN ROLE g新角色加入 g(新角色是成员)SET 开、ADMIN 关、INHERIT 开(除非声明了 NOINHERIT)
ROLE m把 m 加入新角色(新角色是组)SET 开;m 拥有角色级 INHERIT 属性则 INHERIT 开
ADMIN m同 ROLE m,但成员获得 ADMIN 选项成员可以继续把别人拉进组
sql
CREATE ROLE app_readwrite NOLOGIN;

-- alice 创建后即为 app_readwrite 的成员
CREATE ROLE alice LOGIN IN ROLE app_readwrite;

-- 等价写法:先建后挂(更常用,GRANT 语法更通用)
CREATE ROLE alice LOGIN;
GRANT app_readwrite TO alice;

官方文档的建议是:成员关系的增删优先用 GRANT / REVOKE,CREATE ROLE 的这些子句只适合「一次成型」的初始化脚本。

ALTER ROLE:改名、属性与配置参数 ​

ALTER ROLE ... SET 是一个非常实用但常被忽略的能力:把会话参数固化为某角色的登录默认值。它有三层作用域,优先级从高到低:

  1. ALTER ROLE ... IN DATABASE db SET ...——针对「某角色 + 某数据库」;
  2. ALTER ROLE ... SET ...——针对角色全局;
  3. postgresql.conf / ALTER SYSTEM——实例级默认。
sql
-- 应用角色默认走 app 模式,并限制单语句超时
ALTER ROLE app_rw SET search_path = app, public;
ALTER ROLE app_rw IN DATABASE appdb SET statement_timeout = '30s';

-- 只读角色强制开启事务只读(双保险)
ALTER ROLE app_ro SET default_transaction_read_only = on;

-- DBA 角色默认打开自动解释执行计划
ALTER ROLE dba SET auto_explain.log_min_duration = '1s';

角色 SET 与连接池

ALTER ROLE ... SET 在新连接建立时生效。经过连接池复用的旧连接不会立刻应用新值;生产上变更后需要对池执行对应的清空/重启操作(如 PgBouncer 的 RELOAD / PAUSE),或依靠池的 server_reset_query 让会话参数回落。

DROP ROLE 与前置清理 ​

删除角色前必须清空它与世界的三条联系:它拥有的对象、它持有的权限、它作为成员的身份。相关工具有两个:

sql
-- 把某角色的全部对象(含权限)转交给新角色
REASSIGN OWNED BY old_app TO app_owner;

-- 删除某角色在被授予方的全部对象与权限(含默认权限条目)
DROP OWNED BY old_app;

-- 然后才能删除角色本身
DROP ROLE old_app;

两个命令都必须在目标角色拥有对象的每个数据库里执行。组角色的删除则相对简单:DROP ROLE 组角色时,其成员关系会被自动撤销,成员角色本身不受影响。详细流程将在《GRANT / REVOKE 权限管理》的「REASSIGN OWNED 与 DROP OWNED」小节展开。

PG16 起 CREATEROLE 的收紧 ​

PG16 之前,CREATEROLE 的实际能力接近半个超级用户:持有者可以修改任意非超级用户角色,甚至可以把自己的密码改成某个已存在角色的等价凭证,从而横向提权。PG16 对此做了系统性收紧,方向可以概括为:

  • CREATEROLE 角色不再能随意修改「任何」角色,而只能管理被授予了 ADMIN(管理)选项的角色;
  • 它创建角色时,会自动建立起「被创建角色 → 创建者」的、带 ADMIN/INHERIT/SET 选项的成员关系(可用参数 createrole_self_grant 调整自动授予哪些选项);
  • 继承、SET ROLE 也从「角色级属性」细化为「每条成员关系各自的选项」(详见角色继承)。

版本差异要写进运维手册

同一份授权脚本在 PG15 与 PG16+ 上行为可能不同(尤其是依赖 CREATEROLE 自动化建角色的场景)。升级前请核对 role attributes 官方文档中关于 role creation 的说明,并在测试环境演练。

角色命名规范 ​

角色名会出现在连接串、pg_stat_activity、日志、审计报表与 \du 输出里,命名混乱的代价会随时间放大。推荐的约定:

类型模式示例
人的登录角色真实标识(工号/拼音/邮箱前缀)zhangsan、li.siwang
应用连接角色<应用>_<用途>orders_svc、billing_api
组角色<应用>_<职责>app_readonly、app_readwrite、app_admin
属主角色<应用>_ownerapp_owner
工具类角色<工具名>flyway、pgbouncer_stats

三条红线:

  • 避开 pg_ 前缀:以 pg_ 开头的名字为内置预设角色体系保留,日常命名不要触碰。
  • 不给人用共享名:dev、test、admin 这类含糊的共享登录角色是审计噩梦,一人一号。
  • 组名表职责不表人:app_readwrite 表达的是「读写这个职责」,而不是「张三李四的集合」——人员变动只动成员关系,组名与授权保持稳定。

命名定了之后,用注释(COMMENT ON ROLE)记录每个角色的用途与负责人,让 \du+ 的输出自带说明书。

内置预设角色 ​

PostgreSQL 内置了一组预设角色(predefined roles),把常见的高频特权打包成可以直接 GRANT 的角色,避免管理员手工拼凑一堆细碎权限。它们不能被删除或改名,也不能作为成员关系的「被授予方」(不能 GRANT xxx TO pg_monitor)。

预设角色能力说明
pg_database_owner隐式成员恒为「当前数据库的属主」;初始拥有 public 模式。把它当作「数据库属主占位符」用在授权里,模板库授权可随建库自动生效
pg_read_all_data以 SELECT 读取所有表、视图、序列,并拥有所有模式的 USAGE;不绕过 RLS
pg_write_all_data对所有表、视图、序列拥有 INSERT / UPDATE / DELETE,并拥有所有模式的 USAGE;不绕过 RLS
pg_read_all_settings读取全部配置参数(含仅超级用户可见的)
pg_read_all_stats读取全部 pg_stat_* 统计视图与统计类扩展
pg_stat_scan_tables执行可能对表持有 ACCESS SHARE 锁较长时间的监控函数(如 pgrowlocks)
pg_monitor监控合集:pg_read_all_settings + pg_read_all_stats + pg_stat_scan_tables 的成员
pg_signal_backend向其他会话发送取消查询 / 终止会话信号(不能作用于超级用户的会话)
pg_signal_autovacuum_worker向 autovacuum worker 发送取消 / 终止信号(PG18 新增)
pg_read_server_files通过 COPY 等文件函数读取服务器上数据库进程可访问的任意文件
pg_write_server_files通过 COPY 等文件函数写服务器文件
pg_execute_server_program以数据库进程用户身份执行服务器端程序
pg_checkpoint执行 CHECKPOINT 命令
pg_create_subscription在拥有数据库 CREATE 权限的前提下执行 CREATE SUBSCRIPTION(PG16 新增)
pg_maintain对所有关系执行 VACUUM、ANALYZE、CLUSTER、REFRESH MATERIALIZED VIEW、REINDEX、LOCK TABLE(PG17 新增)
pg_use_reserved_connections使用 reserved_connections 参数保留的连接槽(PG17 新增)

三个「服务器级」角色约等于超级用户

pg_read_server_files、pg_write_server_files、pg_execute_server_program 允许绕过数据库级权限检查直接触达服务器文件系统与进程,完全可以被用来获取超级用户等价能力。除非明确知道自己在做什么,否则不要授予——尤其是不要授予给任何应用角色。

角色相关的系统视图与目录 ​

角色信息分布在两个目录与三个视图中,按「能否看到密码」分层:

视图 / 目录可见性内容
pg_authid仅超级用户全部角色的完整定义(集群级共享),含 rolpassword 密文
pg_shadow仅超级用户仅 LOGIN 角色(历史命名),含真实密码密文
pg_auth_members公开可读角色成员关系(roleid、member、grantor、admin_option 及 PG16 起的 inherit_option、set_option)
pg_roles公开可读pg_authid 的脱敏视图:字段同名,rolpassword 恒为 ********
pg_user公开可读仅 LOGIN 角色的脱敏信息(usename、usecreatedb、usesuper、userepl、usebypassrls、passwd、valuntil、useconfig)

日常运维建议统一从 pg_roles 查询(能看全量、不泄露密码):

sql
-- 登录角色一览
SELECT rolname, rolcanlogin, rolconnlimit, rolvaliduntil
FROM pg_roles
WHERE rolcanlogin
ORDER BY rolname;

-- 找出「危险角色」:带管理属性或绕过 RLS 的
SELECT rolname, rolsuper, rolcreatedb, rolcreaterole,
       rolreplication, rolbypassrls
FROM pg_roles
WHERE rolsuper OR rolcreatedb OR rolcreaterole
   OR rolreplication OR rolbypassrls;

-- 某角色挂在哪里(组成员视角)
SELECT r.rolname AS member, g.rolname AS "group",
       am.admin_option, am.inherit_option, am.set_option
FROM pg_auth_members am
JOIN pg_roles r ON r.oid = am.member
JOIN pg_roles g ON g.oid = am.roleid
ORDER BY 1, 2;

实战示例 ​

示例一:创建应用角色 ​

业务应用连接数据库应使用专用角色:能登录、有密码、限制连接数、固化 search_path,且不属于任何人类。

bash
psql -U postgres -d appdb <<'SQL'
-- 应用读写角色(组,不登录)
CREATE ROLE app_rw NOLOGIN;

-- 应用连接角色(登录,挂到组)
CREATE ROLE app_conn LOGIN
    PASSWORD 'Str0ng_P@ss'
    CONNECTION LIMIT 30
    NOINHERIT;

GRANT app_rw TO app_conn;

-- 连接默认行为固化
ALTER ROLE app_conn SET search_path = app, public;
ALTER ROLE app_conn IN DATABASE appdb SET statement_timeout = '10s';

-- 验证
SELECT rolname, rolcanlogin, rolconnlimit, rolconfig
FROM pg_roles WHERE rolname IN ('app_rw', 'app_conn');
SQL
text
 rolname  | rolcanlogin | rolconnlimit |              rolconfig
----------+-------------+--------------+--------------------------------------
 app_conn | t           |           30 | {search_path=app, public}
 app_rw   | f           |           -1 |
(2 rows)

示例二:创建只读角色 ​

只读角色是数据分析、报表类需求的标准答案。先建组角色,授权动作(GRANT 的完整细节在下一页)这里只需照抄:

bash
psql -U postgres -d appdb <<'SQL'
CREATE ROLE app_readonly NOLOGIN;

-- 模式可见 + 未来新表自动可读(默认权限,详见后续页)
GRANT USAGE ON SCHEMA app TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT ON TABLES TO app_readonly;

-- 挂成员
CREATE ROLE reporter LOGIN PASSWORD 'R3port_P@ss' VALID UNTIL '2026-12-31';
GRANT app_readonly TO reporter;
GRANT CONNECT ON DATABASE appdb TO reporter;
SQL

示例三:创建监控角色 ​

监控_exporter(postgres_exporter、pgwatch 等)只需要读统计视图与配置,绝不需要超级用户。pg_monitor 一个角色就够:

bash
psql -U postgres -d postgres <<'SQL'
CREATE ROLE monitoring LOGIN PASSWORD 'M0nitor_P@ss' CONNECTION LIMIT 5;
GRANT pg_monitor TO monitoring;

-- 验证:能否读仅超级用户可见的配置
SET ROLE monitoring;
SHOW data_directory;   -- pg_read_all_settings 使普通角色也能读
RESET ROLE;
SQL
text
               data_directory
---------------------------------------------
 /var/lib/postgresql/18/main
(1 row)

示例四:角色信息的日常巡检查询 ​

sql
-- 1. 哪些登录角色没有设置密码过期时间(合规检查项)
SELECT rolname
FROM pg_roles
WHERE rolcanlogin AND rolvaliduntil IS NULL AND rolname <> 'postgres';

-- 2. 哪些登录角色的连接数不受限
SELECT rolname, rolconnlimit
FROM pg_roles
WHERE rolcanlogin AND rolconnlimit = -1;

-- 3. 有成员的组角色及其成员数量
SELECT g.rolname AS group_role, count(*) AS members
FROM pg_auth_members am
JOIN pg_roles g ON g.oid = am.roleid
GROUP BY 1
ORDER BY members DESC;

-- 4. 危险信号:登录角色同时具备 CREATEROLE
SELECT rolname FROM pg_roles
WHERE rolcanlogin AND rolcreaterole;

示例五:临时角色与生命周期管理 ​

给第三方做临时数据核对的账号,应当「带期限、带限制、可追溯」:

bash
psql -U postgres -d appdb <<'SQL'
-- 临时账号:30 天有效期 + 只读组成员
CREATE ROLE vendor_check LOGIN
    PASSWORD 'V3ndor_Tmp'
    VALID UNTIL now() + interval '30 days'
    CONNECTION LIMIT 3;
GRANT app_readonly TO vendor_check;

COMMENT ON ROLE vendor_check IS '供应商临时核对账号,负责人:王五,工单:OPS-1024';
SQL

到期后的处置要写成流程:账号密码认证自动失败只是第一步,正式下线仍要走「REVOKE 成员 → 逐库 DROP OWNED / REASSIGN OWNED → DROP ROLE」的完整三步。巡检时把 rolvaliduntil 已过期但仍存在的角色列出来,作为待清理清单:

sql
SELECT rolname, rolvaliduntil
FROM pg_roles
WHERE rolcanlogin AND rolvaliduntil IS NOT NULL AND rolvaliduntil < now();

注意事项 ​

角色是集群级的,权限才是库级的。 在 A 库里 REVOKE 权限不会影响角色本身的存在,也不会影响它在 B 库的权限。做「租户隔离」时不要指望「不同数据库里的同名角色」,同名就是同一个。

DROP ROLE 的前置清理必须逐库执行。 REASSIGN OWNED / DROP OWNED 只作用于当前连接的数据库;角色如果在多个库里拥有对象,需要在每个库里跑一遍,最后才能删除角色。

不要用超级用户跑业务。 超级用户绕过一切检查,任何一次注入或误操作都是全库级事故;它还绕过 CONNECTION LIMIT、不受 RLS 约束。应用、报表、监控各用各的角色,超级用户只留给运维。

密码永远以密文存储,但「传输」可能明文。 CREATE ROLE ... PASSWORD 'xxx' 里的明文会经网络到达服务器,并可能落入 shell 历史与日志。交互式用 \password,脚本用环境变量或 .pgpass,并保证 password_encryption = scram-sha-256。

VALID UNTIL 只约束密码认证。 证书、GSSAPI、peer 认证不受它约束;不要把它当作「定时禁用账号」的完备方案,配合定期巡检 pg_roles.rolvaliduntil 使用。

MD5 已弃用。 老实例升级时检查 password_encryption 与 pg_authid.rolpassword 的前缀(MD5 开头即 MD5 密文),并安排滚动重置密码迁移到 SCRAM。

PG16 前后 CREATEROLE 行为不同。 依赖「CREATEROLE 可以管理所有角色」的自动化脚本在 PG16+ 上会开始报权限错误;应改为显式 GRANT ... WITH ADMIN OPTION。版本升级的变更清单里必须有这一条。

组角色命名要有约定。 推荐统一前缀(如 app_readonly / app_readwrite / app_admin),因为角色名在 \du、日志、pg_stat_activity 中都会出现,混乱的命名会让权限审计事倍功半。

每个角色都要有「户口」。 用 COMMENT ON ROLE 记录用途、负责人与关联工单;没有注释的角色半年后就是「没人敢删、没人说得清」的化石。

给角色级参数留迁移说明。 ALTER ROLE ... SET 的值藏在 pg_roles.rolconfig 里,连接串上看不见;应用侧排查「参数不对」时,记得这层配置的存在。

最佳实践 ​

  1. 人、应用、职责分离成三类角色:人用 LOGIN 角色登录;应用用专属 LOGIN 角色连接(一人一号,禁止共享);权限全部挂在 NOLOGIN 组角色上,通过成员关系分发。
  2. 登录角色一律显式写 LOGIN 并给 CONNECTION LIMIT:默认值 NOLOGIN 容易让人误以为「不写就是能登录」;连接数限制是连接风暴的保险丝。
  3. 密码策略写进标准:password_encryption = scram-sha-256、关键角色 VALID UNTIL 有期限、交互改密用 \password、连接串密码交给密钥管理而不是代码仓库。
  4. 超级用户只用于运维动作:建角色、改参数、恢复数据之外的日常操作(建表、调数、发布)都应由普通管理角色完成,并为其保留操作审计。
  5. 给中间管理角色 CREATEDB / CREATEROLE 时最小化:能只给 CREATEROLE 就不要给超级用户;PG16+ 上它已被大幅收紧,是可以放心下放的最小管理特权。
  6. 善用 ALTER ROLE ... SET 固化会话行为:search_path、statement_timeout、default_transaction_read_only 按「角色 + 库」固化,比依赖客户端连接参数可靠得多。
  7. 监控与备份用预设角色:监控用 pg_monitor,备份读数据用 pg_read_all_data(按需),不要为了「方便」把超级用户密码填进 exporter。
  8. 定期巡检危险属性:把「rolsuper、rolcreaterole、rolbypassrls、rolreplication 为真的角色清单」纳入巡检脚本,新增即告警。
  9. 成员关系变更走流程:GRANT group TO user / REVOKE ... FROM 是权限审计的核心事件,应在工单或 IaC 中留痕,避免口头授权。
  10. 删除角色用标准三步:REASSIGN OWNED(逐库)→ DROP OWNED(逐库)→ DROP ROLE,并把三步写成一个脚本,防止漏库导致删除失败或半清理状态。
  11. 升级大版本前重读角色文档:PG16 收紧了 CREATEROLE 与继承语义、PG17 新增了 pg_maintain 等预设角色,官方文档明确提示预设角色的权限可能随版本调整,升级前务必核对 release notes。

下一节

继续学习:GRANT / REVOKE 权限管理

参考链接 ​