PostgreSQL中无法仅授权CREATE TABLE而不允许DROP TABLE,需通过不赋予表所有权+ALTER TABLE OWNER TO管理员或event trigger拦截实现;MySQL和Oracle同理依赖所有权与系统权限分离控制。

PostgreSQL 中如何只给 CREATE TABLE 权限而不给 DROP TABLE
PostgreSQL 默认不支持对单个 DDL 操作(比如仅 CREATE TABLE)做原子级授权——CREATE 权限绑定在 schema 级,而 DROP 权限实际由对象所有权和 USAGE/CREATE 权限组合决定。所以“只让建表不能删表”的关键不是授什么权,而是**不给所有权 + 限制 schema 的使用方式**。
实操建议:
- 用
CREATE SCHEMA建一个专用 schema(如app_data),并把 ownership 给postgres或专用管理角色,不要给业务用户 - 对目标用户只执行:
GRANT USAGE ON SCHEMA app_data TO user_a和GRANT CREATE ON SCHEMA app_data TO user_a - 用户能在
app_data下建表,但建出来的表 owner 是user_a—— 这时他仍能DROP自己建的表 - 真正阻止
DROP:后续用ALTER TABLE ... OWNER TO postgres批量移交所有权,或用event trigger拦截DROP TABLE(见下一条)
MySQL 8.0+ 能否用 CREATE 和 DROP 分开授权
不能。MySQL 的 CREATE、DROP、ALTER 权限都是数据库级(ON db_name.*)或表级(ON db_name.tbl_name)的独立权限项,但它们互不排斥——给了 CREATE 不等于自动有 DROP,但只要用户有对应对象的 DROP 权限,就能删。
常见错误现象:GRANT CREATE ON mydb.* TO 'u'@'%' 后用户仍无法建表,报错 Access denied for user ... to database 'mydb' —— 因为 MySQL 要求用户至少有 USAGE(连接权)+ CREATE,且数据库必须存在。
实操建议:
- 先确保库存在:
CREATE DATABASE IF NOT EXISTS mydb - 授权分两步:
GRANT USAGE ON *.* TO 'u'@'%'(允许连接),再GRANT CREATE ON mydb.* TO 'u'@'%' - 若要禁
DROP,就**不授DROP权限**;但注意:如果用户是root或有GRANT OPTION,可能绕过——务必检查SELECT * FROM mysql.role_edges和SHOW GRANTS FOR 'u'@'%' - MySQL 不支持 schema 级细粒度控制,
CREATE在库级,DROP也在库级,无法做到“可建不可删同一库内对象”
Oracle 里用 CREATE TABLE 权限配合 RESTRICTED SESSION 控制删表行为
Oracle 的 CREATE TABLE 是系统权限,授予后用户可在任何有 CREATE SESSION 和对应表空间配额的 schema 下建表;但删表依赖对象所有权或 DROP ANY TABLE 系统权限——所以“只建不删”的核心是:**不给 DROP ANY TABLE,也不让用户成为表 owner**。
使用场景:应用账号需写入临时中间表,但不允许破坏已有结构。
实操建议:
- 创建专用表空间 + 用户:
CREATE USER app_user IDENTIFIED BY pwd DEFAULT TABLESPACE tmp_tbs QUOTA 100M ON tmp_tbs - 只授必要权限:
GRANT CREATE SESSION, CREATE TABLE TO app_user - 禁止删任意表:
REVOKE DROP ANY TABLE FROM app_user(如果之前误授过) - 关键点:用户建的表默认 owner 是自己,仍能删——所以建表后立刻
ALTER TABLE app_user.tmp_x OWNER TO sys(需 DBA 执行),或改用CREATE GLOBAL TEMPORARY TABLE(会话级,自动清理,无法被DROP)
通用规避思路:用视图/存储过程封装写入,彻底绕过直接 DDL
所有主流数据库都允许通过定义者权限(DEFINER)的存储过程隐藏 DDL 行为。用户调用过程建表,但过程内部用固定 schema 和 owner 执行,用户无权直接操作底层对象。
性能与兼容性影响:过程调用比直连 DDL 多一次解析开销,但可统一加审计日志、命名规范校验、配额检查;Oracle/PG/MySQL 都支持,语法略有差异。
示例(PostgreSQL):
CREATE OR REPLACE FUNCTION create_safe_table(
tbl_name TEXT,
col_def TEXT
) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TABLE IF NOT EXISTS app_data.%I (%s)', tbl_name, col_def);
-- 强制改 owner
EXECUTE format('ALTER TABLE app_data.%I OWNER TO postgres', tbl_name);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;然后只给用户 EXECUTE 这个函数的权限,而不是 CREATE 或 DROP 权限。
容易被忽略的是:SECURITY DEFINER 函数执行时以定义者身份运行,所以必须确保定义者(如 postgres)本身没有被降权,且函数体里没拼接不可信输入——否则有 SQL 注入风险,format() 必须配合 %I(标识符)和 %L(字面量)使用。










