真正的只读需逐层收窄权限:postgresql须创建nologin角色并授予usage、select及默认权限;mysql需禁用系统表、execute和视图越权;sql server须显式deny写操作与ddl;跨库迁移时语义差异易致失效。
postgresql 中如何创建真正的只读角色
postgresql 没有内置的 read only 角色,所谓“只读”必须靠权限逐层收窄实现。仅 grant select 不够——用户仍可能通过函数、临时表、序列甚至 create table 间接写入或破坏结构。
- 先用
CREATE ROLE readonly WITH NOLOGIN;创建无登录能力的基础角色,避免误赋密码或连接权限 - 对每个目标 schema 执行:
GRANT USAGE ON SCHEMA public TO readonly;(缺这步SELECT会报permission denied for schema public) - 再对 schema 内所有现有表执行:
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly; - 别忘了后续新建表:运行
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly;,否则新表默认不继承权限
MySQL 8.0+ 的 ROLE 机制为何仍可能漏授权
MySQL 的 SELECT 权限看似覆盖全部读操作,但实际存在三个典型越权路径:系统表访问、存储过程执行、视图底层表绕过。
- 显式禁止系统库:
REVOKE SELECT ON mysql.* FROM 'ro_user'@'%';(否则可查mysql.user等敏感表) - 禁用函数与过程调用:
REVOKE EXECUTE ON *.* FROM 'ro_user'@'%';,防止通过sys_eval()或自定义函数触发写操作 - 若使用视图,需确认其定义中未引用用户无权
SELECT的表——MySQL 视图权限检查是运行时行为,非创建时
SQL Server 里 db_datareader 不等于只读
这个内置角色允许 SELECT,但默认不限制 INSERT 到临时表、不阻止 TRUNCATE TABLE(如果用户有 ALTER 权限),更不防 DDL。
- 必须显式拒绝写权限:
DENY INSERT, UPDATE, DELETE, TRUNCATE TABLE TO readonly_role; - 禁用 DDL 关键动作:
DENY ALTER ANY SCHEMA, ALTER ANY TABLE, CREATE TABLE, DROP TABLE TO readonly_role; - 注意:SQL Server 权限是“授予 + 拒绝”双轨制,
DENY优先级高于GRANT,所以即使某人属于多个角色,只要任一角色被DENY,该操作就失败
跨数据库迁移时权限脚本失效的常见原因
不同数据库对“只读”的语义理解差异极大,直接复用脚本大概率导致权限过宽或过严。
- PostgreSQL 的
USAGEon schema ≠ MySQL 的USAGE(后者只是连接权限占位符,无实质作用) - Oracle 的
SELECT_CATALOG_ROLE允许查数据字典,而 PostgreSQL 默认pg_catalog对所有用户可读——需额外REVOKE - 某些云托管服务(如 AWS RDS)禁用
REVOKE或限制ALTER DEFAULT PRIVILEGES,此时只能靠应用层拦截或代理层过滤
真正安全的只读,从来不是加一道权限,而是拆掉所有能碰写的路径。最常被忽略的是默认权限继承、系统库暴露、以及角色嵌套时的 DENY 覆盖规则——这些地方一松,前面所有 GRANT 都白做。










