能恢复,前提是log_bin=on且binlog_format=row;否则仅记录sql原文,无法还原password_last_changed等关键字段;需用mysqlbinlog定位delete事件并提取完整字段快照,执行前设set sql_log_bin=0,最后必须flush privileges生效。

确认 binlog 是否能支撑恢复
恢复误删用户权限,前提是 MySQL 启用了 log_bin 且 binlog_format 是 ROW。其他模式(MIXED 或 STATEMENT)对 DROP USER、DELETE FROM mysql.user 这类操作只记录 SQL 原文,无法还原字段值——尤其是 MySQL 8.0 必须的 password_last_changed、authentication_string、Account_locked 等字段。
执行这两条命令验证:
SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_format';
二者必须同时为 ON 和 ROW。若不满足,跳过 binlog 恢复路径,直接转向备份或 skip-grant-tables 方案。
从 binlog 中提取被删用户的完整行数据
mysqlbinlog 输出中,DELETE FROM mysql.user 不是明文 SQL,而是以 ### DELETE FROM `mysql`.`user` 开头、后跟多行 ### @1=... 字段快照。直接 grep "DELETE FROM mysql.user" 容易漏掉或匹配错误。
- 用时间范围缩小解析体积:
mysqlbinlog --base64-output=DECODE-ROWS -vv --start-datetime="2026-08-10 14:00:00" --stop-datetime="2026-08-10 14:30:00" /var/lib/mysql/binlog.000012 - 在输出中搜索
### DELETE FROM `mysql`.`user`或### UPDATE `mysql`.`user`(权限清空常表现为UPDATE将关键字段设为空) - 找到后,向上翻找同一用户的上一次
### INSERT INTO `mysql`.`user`或完整### UPDATE记录——它包含全部字段值,包括password_last_changed的时间戳和authentication_string的哈希值
缺失任意一个非 NULL 字段(特别是 password_last_changed),插入后 FLUSH PRIVILEGES 也无法登录。
构造并执行 INSERT 语句前必须关闭二进制日志写入
把从 binlog 提取的字段值拼成 INSERT INTO mysql.user (...) VALUES (...) 后,不能直接执行。否则该语句又会写入 binlog,可能触发复制环路或污染日志。
执行前务必先运行:
SET sql_log_bin = 0;
再执行 INSERT,最后必须执行:
FLUSH PRIVILEGES;
注意:FLUSH PRIVILEGES 不是可选步骤——MySQL 不自动重载内存中的权限缓存,不执行就等于没恢复。
没有 binlog 或备份时,只能靠 skip-grant-tables + 手动重建
如果 log_bin=OFF 或 binlog 已过期,且没有 mysqldump --databases mysql 备份,唯一入口是 skip-grant-tables。但这不是“随便插一条 root 就行”。
MySQL 8.0 对 mysql.user 表结构校验更严:
- 编辑
/etc/my.cnf,在[mysqld]下加skip-grant-tables,重启服务 - 登录后不能直接
INSERT:8.0 报错Field 'password_last_changed' doesn't have a default value - 正确做法是先
UPDATE已有 root 记录:UPDATE mysql.user SET authentication_string='', password_last_changed=NOW() WHERE User='root' AND Host='localhost'; - 再
FLUSH PRIVILEGES;,删掉配置、重启,最后用ALTER USER设新密码
普通用户重建时,Host 字段必须完全一致('appuser'@'10.20.1.%' ≠ 'appuser'@'%'),且 MySQL 8.0 默认认证插件是 caching_sha2_password,老应用连不上就得显式指定 IDENTIFIED WITH mysql_native_password。











