最稳的mysql权限导出方式是用mysql客户端批量执行show grants:mysql -u root -p -nse "select concat('show grants for '', user, ''@'', host, '';') from mysql.user" | mysql -u root -p | sed 's/$/;/; s/\//g' > grants_server_a.sql

直接导出 MySQL 用户权限 SQL 语句(含 GRANT)
MySQL 没有内置“权限快照对比”命令,但 mysqldump 和 SELECT ... INTO OUTFILE 都不导出权限——真正能复现权限的只有 SHOW GRANTS。最稳的方式是用 mysql 客户端批量执行它。
在目标服务器上运行:
mysql -u root -p -Nse "SELECT CONCAT('SHOW GRANTS FOR \'', user, '\''@\'', host, '\';') FROM mysql.user" | mysql -u root -p | sed 's/$/;/; s/\\/\/g' > grants_server_a.sql
说明:-N 去除列名,-s 简洁输出,-e 直接执行;管道二次调用 mysql 执行每条 SHOW GRANTS,再用 sed 补分号、转义反斜杠。注意:必须用 root 或有 SELECT 权限于 mysql.user 的账号。
- 别用
mysqldump --all-databases:它不导出mysql.user表的权限逻辑(比如密码哈希、plugin 字段不影响权限,但SHOW GRANTS是语义级授权表达) - 如果提示
Access denied for user 'xxx'@'localhost' (using password: YES),说明账号没查mysql.user表权限,换更高权限账号或加GRANT SELECT ON mysql.user TO ... -
SHOW GRANTS输出含当前用户默认角色(8.0+),若跨版本对比,先确认双方是否启用 roles,否则差异会误判
过滤掉无关用户和通配符 host 导致的噪声
生产环境常存在 '%'、'192.168.%' 这类泛 host 用户,它们在不同服务器上可能指向不同实际访问来源,直接对比会刷屏无效差异。
导出前建议先筛出关键用户(如业务账号、DBA 账号):
mysql -u root -p -Nse "SELECT DISTINCT CONCAT('SHOW GRANTS FOR \'', user, '\''@\'', host, '\';') FROM mysql.user WHERE user IN ('app_rw', 'bi_ro', 'dba_admin') AND host NOT IN ('%', 'localhost', '127.0.0.1')" | mysql -u root -p > grants_clean.sql
-
host = 'localhost'和host = '127.0.0.1'在 MySQL 中是两个不同账户(Unix socket vs TCP),但权限行为一致,对比时建议统一排除或显式保留 - 避免用
WHERE host != '%':有些合法账号就是'monitor'@'%',应按业务名单白名单过滤,而非黑名单 - MySQL 8.0+ 的
mysql.role_edges表会影响权限继承,若用了 roles,需额外导出SELECT * FROM mysql.role_edges并加入对比范围
用 diff 工具做语义级比对(不是字符串逐行)
diff grants_server_a.sql grants_server_b.sql 会因排序、空格、注释顺序失败。权限本质是“谁对哪些库表有哪几种操作”,应提取核心三元组:user@host + database.table + privileges。
推荐用 Python 快速归一化(无需安装包):
python3 -c "
import sys, re
for line in open(sys.argv[1]):
m = re.match(r"GRANT (.+?) ON (.*?)(?= TO |$);", line)
if m:
priv, obj = m.groups()[0].strip(), m.groups()[1].strip()
user_host = re.search(r"TO '([^']+)'@'([^']+)'", line)
if user_host:
print(f'{user_host[1]}@{user_host[2]} {obj} {priv.upper()}')
" grants_server_a.sql | sort > a_normalized.txt
- 正则只捕获
GRANT后的权限列表、作用对象、TO后的用户,忽略WITH GRANT OPTION等修饰——它不改变权限本身,只影响授权能力 - 用
sort统一顺序后diff a_normalized.txt b_normalized.txt,结果可读性强 - 如果某行是
GRANT PROXY ON ''@'' TO 'u'@'h',它属于代理权限,需单独判断是否应纳入对比(通常不应,除非明确依赖 proxy)
注意 MySQL 版本间 GRANT 语法的隐性差异
MySQL 5.7 和 8.0 对权限表达不完全兼容。例如 CREATE TABLESPACE 在 5.7 是全局权限,在 8.0 被拆为 TABLESPACE;BACKUP_ADMIN 是 8.0 新增权限,5.7 服务器导出时根本不会出现。
对比前务必确认两端版本:
mysql -u root -p -e "SELECT VERSION();"
- 用
SELECT * FROM information_schema.role_table_grants替代SHOW GRANTS可获得更结构化输出,但仅 8.0.16+ 支持,且不含 role 继承链 - 5.7 服务器导出的
USAGE权限(空权限)在 8.0 中可能被省略,这不是差异,是版本行为变化 - 若发现某权限在 A 服务器有、B 服务器无,先查
SELECT * FROM mysql.tables_priv WHERE user='xxx',确认是不是表级权限被漏导(SHOW GRANTS默认不显示表级,除非显式ON db.tbl)
权限对比不是比文件是否一样,而是比“执行效果是否等价”。一个 GRANT SELECT ON *.* 和一百个 GRANT SELECT ON db1.t1 + GRANT SELECT ON db2.t2… 在语义上可能完全等效,但 diff 会报巨量差异——这时候得退一步,看业务实际访问路径,而不是追着工具输出跑。











