必须主动REVOKE全局DDL权限,因MySQL权限叠加且角色可继承,仅不授无法禁用;需查mysql.user和role_edges、回收CREATE/ALTER/DROP等、再授DML权限并FLUSH PRIVILEGES。

只给DML权限但禁用DDL,关键在彻底回收全局DDL权限
MySQL默认不阻止用户执行DDL,哪怕你没显式授予CREATE、ALTER、DROP,只要用户有CREATE、ALTER、DROP这些权限位中的任意一个(哪怕来自角色继承或旧配置),就可能触发意外操作。所以不能只“不给”,必须主动REVOKE。
常见错误是只执行GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'dev'@'%',却忽略用户可能已从mysql.role_edges继承了CREATE权限,或建库时被误授了ALL PRIVILEGES。
- 先查清用户实际拥有的DDL权限:
SELECT * FROM mysql.user WHERE User = 'dev' AND Host = '%',重点看Create_priv、Alter_priv、Drop_priv字段是否为Y - 显式回收所有DDL权限:
REVOKE CREATE, ALTER, DROP, INDEX, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EXECUTE ON *.* FROM 'dev'@'%' - 再授DML权限:
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'dev'@'%' - 最后
FLUSH PRIVILEGES确保立即生效(尤其在非动态权限系统中)
为什么不能只靠“不授权”来禁用DDL
MySQL权限是叠加生效的:全局(mysql.user)→ 数据库(mysql.db)→ 表(mysql.tables_priv)→ 列 → Routine。哪怕你在mydb.*上没给CREATE,只要用户在mysql.user里Create_priv = 'Y',就能在任意库建表。
更隐蔽的是角色继承:如果用户被赋予了'developer_role',而该角色有CREATE权限,那用户就自动获得——即使你没直接GRANT给他。
- 检查角色分配:
SELECT * FROM mysql.role_edges WHERE TO_USER = 'dev' - 若存在,先
REVOKE 'developer_role' FROM 'dev'@'%',再单独重授最小权限角色 - 避免使用
GRANT ALL ON mydb.*,它隐含包含CREATE、ALTER等DDL权限
开发人员仍可能绕过限制的三个真实路径
权限配置只是第一道防线,开发人员若拥有某些高危权限或访问路径,仍可能间接执行DDL或泄露结构。
-
PROCESS权限开启后,可执行SHOW PROCESSLIST看到其他会话正在执行的SQL,包括未脱敏的CREATE TABLE语句 -
SELECT权限若给了整个库(mydb.*),用户能查information_schema.TABLES、information_schema.COLUMNS,反推出表结构甚至索引定义 - 视图若设为
SQL SECURITY INVOKER,且用户有基表SELECT权,那调用视图时实际走的是用户自己的权限——等于白设
所以务必配合:REVOKE PROCESS ON *.* FROM 'dev'@'%',并确保视图创建时明确写SQL SECURITY DEFINER。
MySQL 8.0+推荐用角色做权限隔离
比起给每个用户单独GRANT/REVOKE,用角色集中管理更可控,也避免漏收。
- 创建干净的角色:
CREATE ROLE 'dml_only'; - 只授DML:
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'dml_only'; - 回收所有DDL权限:
REVOKE CREATE, ALTER, DROP ON *.* FROM 'dml_only'; - 把用户加入角色:
GRANT 'dml_only' TO 'dev'@'%'; - 禁用自动激活:
SET PERSIST activate_all_roles_on_login = OFF;,防止登录即获得全部角色权限
角色方案真正难处理的是历史遗留权限:升级到8.0后,旧用户权限不会自动转为角色,必须人工核对并迁移。这点常被跳过,导致“以为收了,其实还在”。


















