广告位 · 页头(后台-模块管理-新建模块后替换此区域)

为什么MySQL还原后数据权限丢失?导出包含mysql系统库和手动授权的影响

MySQL还原之后业务表都在,应用账号却提示Access denied,这个问题在实际运维中并不少见。权限丢失往往不是数据行损坏,而是备份时对mysql系统库和授权语句的处理不完整。下文从mysqldump导出范围、手动授权持久化、恢复后的权限重建三个角度展开。

为什么MySQL还原后数据权限丢失?导出包含mysql系统库和手动授权的影响

一、全量导出中的mysql系统库为什么会覆盖权限

mysqldump是逻辑备份工具,它把选定的数据库或表转换成一条条SQL语句。如果为了省事使用mysqldump --all-databases,或者手动把mysql库加入导出列表,备份文件里就会出现mysql.user、mysql.db、mysql.tables_priv等系统表的DROP TABLE、CREATE TABLE和INSERT语句。恢复这个文件时,MySQL会按顺序执行这些语句,旧文件里的权限表数据会直接覆盖当前实例中的权限表。

这种情况在权限发生过变更的环境里尤其危险。比如上午用全量方式导出了一份备份,下午又给应用账号增加了新的库级授权,晚上因为误操作需要恢复上午的备份,恢复完成后新授权就会消失。因为备份文件里的mysql.user还是上午的状态,而恢复过程中没有跳过权限表。业务数据回滚的同时,权限也一并回滚了,但运维人员通常只关注业务表,直到应用报Access denied才发现账号丢了。

下面是一个容易踩坑的导出和恢复方式。这个命令会连同mysql系统库一起导出,恢复时就会覆盖权限。

-- 错误方式:把所有库包括mysql系统库一起导出
mysqldump -uroot -p --all-databases > /backup/all_databases.sql
-- 恢复时会把旧的mysql.user等表导入,覆盖现有权限
mysql -uroot -p < /backup/all_databases.sql

更合理的做法是把业务库和权限对象分开。业务库的备份只包含业务数据,权限则单独从mysql系统库里导出需要的表,或者直接用专门的工具保存授权语句。下面这种分开导出的方式可以避免恢复业务数据时误伤权限。

-- 只导出业务库
mysqldump -uroot -p --single-transaction --set-gtid-purged=OFF appdb > /backup/appdb.sql
-- 单独导出mysql系统库中的权限相关表
mysqldump -uroot -p --no-create-info mysql user db tables_priv columns_priv procs_priv > /backup/mysql_priv.sql

二、手动授权为什么不会跟着业务库备份走

手动执行GRANT语句是很多中小项目里常见的授权方式。开发或运维在源库执行一条GRANT SELECT, INSERT, UPDATE ON appdb.* TO 'appuser'@'%',然后FLUSH PRIVILEGES。这条语句只修改mysql系统库中的权限表,不会在appdb库里写入任何表或数据。因此后续使用mysqldump appdb导出业务库时,这份备份里自然不会有appuser这个账号和它的授权。

很多迁移案例都是这样发生的:源库的业务表备份完整,目标实例上恢复了appdb,但应用连不上目标库,登录后执行SHOW GRANTS发现appuser账号根本不存在。如果之前没有单独保存授权语句,只能依靠记忆重新授权,库级权限、表级权限、列级权限很容易漏掉。尤其当账号权限是经过多轮变更积累下来的时候,手工重建几乎不可能还原完整。

要避免手动授权丢失,需要把授权语句的导出纳入日常备份流程。可以用Percona Toolkit中的pt-show-grants,也可以直接从系统表查询后生成授权脚本。下面两条命令的作用就是把授权语句单独保存下来。

pt-show-grants -uroot -p > /backup/grants.sql
mysql -uroot -p -N -e "SHOW GRANTS FOR 'appuser'@'%';" > /backup/appuser_grants.sql

第二条命令只保存一个账号的授权,适合账号数量少的环境。如果实例中有多个应用账号,使用pt-show-grants更省事,它会遍历所有用户并输出完整的授权语句。保存下来的grants.sql要跟业务库备份文件放在一起,恢复时先导入业务库,再执行grants.sql重建权限。

三、恢复后如何核对并重建权限

恢复完成后不要立刻放开应用流量,先在目标实例上做权限核对。最基本的检查是列出mysql.user表中的账号和来源主机,确认应用账号是否存在。如果账号存在,再用SHOW GRANTS FOR语句查看它的实际授权,和源库导出的grants.sql逐条比较。不能只看账号能登录,就认为权限没问题,因为库级、表级、列级权限可能存在差异。

以下SQL可以在目标实例上快速检查账号情况。如果发现账号不存在,就需要执行CREATE USER和GRANT重新创建并授权。MySQL 5.7和8.0在创建账号语法上略有区别,8.0更推荐先CREATE USER再GRANT。

-- 检查账号是否存在
SELECT user, host FROM mysql.user WHERE user = 'appuser';
-- 查看某个账号的授权
SHOW GRANTS FOR 'appuser'@'%';
-- MySQL 8.0 推荐先创建账号再授权
CREATE USER 'appuser'@'%' IDENTIFIED BY 'App@123';
GRANT SELECT, INSERT, UPDATE ON appdb.* TO 'appuser'@'%';
FLUSH PRIVILEGES;
-- MySQL 5.7 也可以一条GRANT完成创建和授权
GRANT SELECT, INSERT, UPDATE ON appdb.* TO 'appuser'@'%' IDENTIFIED BY 'App@123';
FLUSH PRIVILEGES;

如果源库使用的是MySQL 5.7,目标库升级到了MySQL 8.0,不建议直接把mysql.user表覆盖过去,因为两者的权限表结构和字段定义存在差异,跨版本系统表混用可能引发更多问题。更稳妥的方式是在目标库执行grants.sql里的授权语句,并在执行前检查语句中的IDENTIFIED BY子句是否兼容目标版本。

另外,如果已经误用包含mysql库的全量备份覆盖了权限,不要尝试再从当前目标实例倒推权限,应该回到源库重新导出授权语句,或者根据变更记录重建账号。重建后再次执行SHOW GRANTS和SELECT user, host FROM mysql.user确认结果,并用应用账号做连接测试,避免只验证root账号而遗漏应用账号。

权限丢失的本质是备份对象和恢复对象不一致。业务数据、系统权限、账号授权是三类完全不同的内容,备份时分开保存,恢复时按顺序执行,才能避免Access denied这类看似简单但影响业务的问题。

广告位 · 文章底部(后台-模块管理替换此区域)