为什么直接 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 <id></id>强制断开(需先查
SHOW PROCESSLIST)
另一个隐蔽原因是:用户可能通过角色间接持有权限。比如你 revoke 了用户 A 的
SELECT,但他属于角色 R,而 R 仍有该权限。此时必须检查
SELECT * FROM mysql.role_edges WHERE TO_HOST = 'A',再针对性 revoke 角色权限。
权限最小化不是一次性配置,而是持续收敛的过程 —— 日志分析、角色审计、连接复用策略,每个环节都可能暴露过度授权。
