直接GRANT ALL ON .极危险,因赋予DROP database、SHUTDOWN等高危权限,应用账号亦可删库或重启实例;应遵循最小权限原则,按实际sql需求精确授权,禁用GRANT OPTION,优先细化到表或列,并利用mysql 8.0角色与动态权限机制实现安全管控。

为什么直接 GRANT ALL ON *.* 很危险
因为这会让用户拥有 DROP DATABASE、SHUTDOWN、RELOAD 等高危权限,哪怕只是应用连接账号,也能意外删库或重启实例。MySQL 8.0 默认启用 sql_mode=STRICT_TRANS_tableS,但权限宽松本身不触发任何警告,风险完全靠人控制。
最小化不是“能连上就行”,而是“只给当前业务 SQL 真正需要的权限”。比如一个只查订单列表的接口,连 INSERT 都不该有。
- 应用账号绝不分配
GRANT OPTION—— 否则可能自行提权 - 避免使用
ON *.*或ON database.*,优先细化到表甚至列(如select(order_id, status) ON db.orders) - MySQL 8.0+ 支持角色(
CREATE ROLE),可用角色封装权限组合,再授给用户,便于批量调整
如何用 SHOW GRANTS 定位冗余权限
SHOW GRANTS for 'app_user'@'10.20.%' 返回的是当前生效权限集合,但注意:它不区分权限来源(是直授还是通过角色继承),也不显示已撤销但未刷新的缓存。真正要确认最小集,得结合实际运行日志反推。
推荐做法是开启 general_log(仅临时),让应用跑完典型链路(登录 → 查询 → 提交),再 grep 出所有 SELECT/UPDATE 语句涉及的库、表、字段:
SELECT DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(argument, ' ', 3), ' ', -1) AS table_name, SUBSTRING_INDEX(argument, ' ', 2) AS stmt_type FROM mysql.general_log WHERE argument LIKE 'SELECT %' OR argument LIKE 'UPDATE %' AND argument NOT LIKE '%information_schema%' AND event_time > NOW() - INTERVAL 5 MINUTE;
结果出来后,再用 GRANT SELECT(col1,col2) ON db.table TO ... 精确授权,比凭经验猜更可靠。
MySQL 8.0 的动态权限与旧版兼容陷阱
MySQL 8.0 把部分管理权限(如 BACKUP_ADMIN、CLONE_ADMIN)从全局权限中拆出,变成可独立授予的「动态权限」。它们不会出现在 SHOW GRANTS 的传统输出里,必须用 SHOW GRANTS FOR 'u'@'h' using role_name 或查 mysql.role_edges 才能看到。
容易踩的坑:
- 用 Percona Toolkit 或某些 ORM 连接池初始化时,默认尝试
SET session sql_log_bin = 0,需要BINLOG_ADMIN权限 —— 但该权限在 5.7 不存在,升级到 8.0 后若没补授,连接直接失败 -
CREATE TEMPORARY TABLES在 8.0 是动态权限,但很多文档仍按老方式写成全局权限,导致GRANT语句报错Unknown privilege - 复制账号(如用于 GTID 搭建)必须显式授予
REPLICATION SLAVE ADMIN,而非旧版的REPLICATION SLAVE
权限回收后为什么应用还能查?
常见原因不是权限没生效,而是 MySQL 缓存了权限判断结果。执行 FLUSH PRIVILEGES 并不能解决所有情况 —— 它只刷新内存中的权限表副本,不影响已建立连接的会话权限。
真正生效的方式只有两个:
- 让应用重连(最稳妥,尤其在容器/K8s 环境下滚动更新即可)
- 对已存在的连接,执行
KILL CONNECTION强制断开(需先查SHOW PROCEsslIST)
另一个隐蔽原因是:用户可能通过角色间接持有权限。比如你 revoke 了用户 A 的 SELECT,但他属于角色 R,而 R 仍有该权限。此时必须检查 SELECT * FROM mysql.role_edges WHERE TO_HOST = 'A',再针对性 revoke 角色权限。
权限最小化不是一次性配置,而是持续收敛的过程 —— 日志分析、角色审计、连接复用策略,每个环节都可能暴露过度授权。