mysql中用户权限的最小化原则与实践

来源:这里教程网 时间:2026-02-28 20:47:24 作者:

为什么直接 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 角色权限。

权限最小化不是一次性配置,而是持续收敛的过程 —— 日志分析、角色审计、连接复用策略,每个环节都可能暴露过度授权。

相关推荐