
前阵子处理了一个 Alibaba Cloud Linux 服务器上的 MySQL 故障业务侧的人把 root 密码改乱了交接文档里也没记清楚最后只能启用 skip-grant-tables 模式进库修复。这个方案在社区里讨论很多但真正拿到生产环境执行远比博客里一两句话要复杂要考虑服务怎么停、配置怎么改、免密状态下怎么保证安全、恢复后怎么验证。这篇文章就围绕这套完整操作链路结合我这次在 Alibaba Cloud Linux 上安装 MySQL 并启用 skip-grant-tables 模式的实际过程把步骤、原理和坑都拆开讲清楚。如果你正在用的是阿里云 ECS Alibaba Cloud Linux线上 MySQL 突然登不进去或者你只是想提前演练一遍密码找回流程这篇内容可以直接照着操作。文章以 MySQL 8.0 为主用到的系统镜像是 Alibaba Cloud Linux 3.2104 LTS 64 位同时会把 5.7 版本的关键差异单独标出来。1. 先弄清楚一件事skip-grant-tables 到底在解什么围很多 DBA 看到 skip-grant-tables 会本能地警惕因为它的语义就是“跳过授权表验证”。但生产环境里它依然是一个刚需功能关键不是能不能用而是你知不知道什么时候该用、用了之后要承担什么后果。1.1 哪些情况会让你非用它不可我遇到的最典型场景是 root 密码被改乱或者彻底遗忘。理论上 MySQL 有--init-file方案可以免密重置密码但那要求你还能通过其他管理员账号登录系统而且 init-file 里写的 SQL 会在启动时自动执行万一写错容易出更大问题。skip-grant-tables 则更直接绕过认证阶段让你进到 MySQL 内部去修复用户表。除了密码遗忘下面这几种情况也常会用到认证插件异常。例如把 root 的 plugin 改成了auth_socket但客户端连接时根本没法满足 socket 用户匹配条件导致所有登录失败。mysql.user表被误操作。比如批量 update 时 where 条件写错把所有账号的 authentication_string 清空了。密码过期策略导致连锁反应。MySQL 8.0 默认default_password_lifetime如果账号密码过期并且没有及时修改应用侧会持续报Your password has expired如果 DBA 手上也没有有效账号就只能通过维护模式进去重置。这些场景共同点是你已经失去了正常的认证入口但必须进到数据库内部才能恢复。此时 skip-grant-tables 几乎是唯一的常规手段。1.2 绕过权限验证的工作原理MySQL 服务端启动时会读取mysql库下的授权表user、db、tables_priv、columns_priv等把权限数据加载到内存里。客户端连接时服务端根据内存中的账号记录做密码校验和权限判断。加了skip-grant-tables之后启动流程会跳过授权表的加载与校验任何客户端连接进来都被看作匿名用户并且默认拥有所有库的所有权限。注意这里说的是“跳过校验”不是“删除权限表”mysql.user里的数据仍然在磁盘上只是启动时不再依赖它来判断你能不能连、能不能查。这也是为什么进入免密模式后修改密码之前必须执行FLUSH PRIVILEGES因为通过 SQL 修改用户表时服务端需要把磁盘上的授权表重新加载到内存否则ALTER USER这类操作会因为你当前会话没有正常权限上下文而报错或者不生效。1.3 它带来的风险比你想象的大我在不少教程里看到有人直接写“修改 my.cnf 加 skip-grant-tables然后重启mysql -uroot 登录”然后就结束了。这种写法遗漏了最致命的一点免密模式下MySQL 依然会按照原有配置监听端口。如果你的bind-address是0.0.0.0ECS 安全组又放通了 3306那么任何能访问到这个 IP 和端口的人都可以直接免密登录而且拿到的是超级权限。这不是理论风险真实环境里出现过用扫描工具扫到 3306 开放端口免密直连然后拖库的案例。所以我在进入维护模式之前会同时做两件事一是确认 ECS 安全组和系统防火墙对 3306 的访问控制二是在 my.cnf 里临时加上skip-networking。这两个措施叠加起来MySQL 只允许本地 socket 连接外部网络根本无法触及安全性才有保障。2. Alibaba Cloud Linux 上把 MySQL 装到能用的状态如果你已经装好了 MySQL只是想看 skip-grant-tables 的部分可以跳到下一章。但如果你是第一次在 Alibaba Cloud Linux 上装生产库建议把这一章的选型和初始化细节过一遍因为很多后续故障都源于安装阶段埋下的坑。2.1 系统版本与安装方式选型Alibaba Cloud Linux 是阿里云基于社区版 Linux 打造的操作系统目前主流是 Alibaba Cloud Linux 2 和 Alibaba Cloud Linux 3。两者的软件包兼容性有差异选 MySQL 源的时候要注意对应关系。我这边最推荐的是用 MySQL 官方 Yum 仓库原因有三个官方仓库会跟随 MySQL 官方的补丁更新安全修复及时和系统自带的 MariaDB 不会有包冲突安装后的目录结构、systemd 服务脚本都符合主流习惯排查问题时有文档可查。如果你所在环境无法访问公网也可以先在有网环境把 rpm 包下载好再拷贝到服务器本地安装。实际步骤就是 rpm 安装不做额外编译生产环境没必要自己编译 MySQL除非你有非常特殊的优化需求。2.2 用官方仓库完成安装以 Alibaba Cloud Linux 3 为例安装 MySQL 8.0 的完整命令如下# 下载并安装 MySQL 官方 Yum 仓库el8 对应 Alibaba Cloud Linux 3 rpm -ivh https://dev.mysql.com/get/mysql80-community-release-el8-6.noarch.rpm # 确认仓库已经启用 dnf repolist enabled | grep mysql # 安装 MySQL 社区版服务端 dnf install -y mysql-community-server # 设置开机自启并启动 systemctl enable --now mysqld如果你用的是 Alibaba Cloud Linux 2道理一样只是把el8换成el7rpm -ivh https://dev.mysql.com/get/mysql80-community-release-el7-11.noarch.rpm yum install -y mysql-community-server systemctl enable --now mysqld安装过程里有个细节容易被忽略MySQL 8.0 首次启动后会自动生成一个临时 root 密码写在错误日志里。想登录必须先找到这个临时密码grep temporary password /var/log/mysqld.log然后执行mysql -uroot -p输入上面的临时密码进入后强制修改ALTER USER rootlocalhost IDENTIFIED BY YourStrongPassword;这里还要提示一下不要轻视 validate_password 组件的存在。默认策略要求密码包含大小写字母、数字和特殊字符长度至少 8 位。如果你在安装 MySQL 之后立刻修改密码建议直接设置一个复杂密码否则会反复被策略拦下。后面如果需要降低强度可以通过SET GLOBAL validate_password.policy LOW;调整生产环境不推荐这样做。2.3 初始化后的基本检查安装完成后不要急着直接进入业务配置先把基础状态确认一遍systemctl status mysqld mysqladmin ping -uroot -p ss -tlnp | grep 3306这一步能确认 MySQL 是否正常监听、systemd 是否处于 active 状态。我习惯顺手记下几个关键路径配置文件/etc/my.cnf数据目录/var/lib/mysql错误日志/var/log/mysqld.logsocket 文件/var/run/mysqld/mysqld.sock后面积累的很多排查经验都和这些路径相关。比如你改完配置重启后连不上第一步就该去看/var/log/mysqld.log而不是盲目重试。3. 进入 skip-grant-tables 模式的完整操作链路现在开始切入正题。以“root 密码已经彻底不可用”为前提我们要从正常生产模式切换到免认证模式然后进去修复。3.1 安全停机这一步决定恢复成功与否很多人在“已经登录不了数据库”的情况下会直接用kill -9杀 mysqld 进程。这个做法非常危险。如果 MySQL 正在处理事务强制杀死进程可能导致 InnoDB 崩溃恢复重启时间变长不说极端情况下还会损坏数据文件。正确的停机方式有优先级如果 root 密码还能用哪怕要通过某个临时账号都优先使用mysqladmin shutdown优雅关闭。如果密码不可用但 mysqld 进程是正常状态可以试试systemctl stop mysqld。systemd 会向进程发送 SIGTERMMySQL 会走正常关闭流程。只有进程完全卡死、systemctl 无法停止时才考虑kill -9而且 kill 之后要立即关注错误日志做好 InnoDB 恢复的心理准备。我这次操作时MySQL 还能响应 systemctl stop所以进程关闭很顺利。关闭后先用ps -ef | grep mysqld确认没有任何残留进程再进入下一步。3.2 配置层面与服务层面的两种进入方式进入 skip-grant-tables 模式有两种常见方式差别很大。方式 A修改/etc/my.cnf推荐用于完整恢复场景[mysqld] skip-grant-tables skip-networking然后在 my.cnf 同目录下确认没有其他冲突配置再启动服务systemctl start mysqld这种方式的好处是配置固化MySQL crash 之后自动重启仍然会进入维护模式方便你反复排查。坏处是如果你改完密码忘了把配置改回来服务重启后依然是免密状态这是一个高频事故点。方式 B命令行临时指定参数mysqld_safe --skip-grant-tables --skip-networking 这种方式适合只在内存中临时生效重启后自动消失不需要担心配置文件残留。缺点是不太适配 systemd 管理启动方式绕了一圈进程管理上容易混乱。我的建议是如果这是你第一次操作就用方式 A至少出现问题还能通过重启回到同一个可预期状态日志也更完整。但一定要做一个动作给 my.cnf 加备份甚至保存一份修改前后 diff。cp /etc/my.cnf /etc/my.cnf.bak.$(date %F)恢复模式结束前用这份备份还原能省去很多麻烦。3.3 启动后确认“免密”状态是否生效启动完成之后直接执行mysql -uroot如果无需密码就能进入 MySQL 命令行看到mysql提示符说明 skip-grant-tables 已经生效。这个时候不要急着改密码先看几项信息SELECT user, host, plugin, authentication_string FROM mysql.user;这一步能帮你理清当前到底有哪些账号、root 账号的 host 是localhost还是%、当前认证插件是什么。在免密状态下你拥有完全权限可以放心查。此外错误日志里也会出现相关提示。日志中可能会记录 MySQL 是在什么模式下启动的。确认模式是否生效以实际能否免密登录为准而不是只看配置文件里有没有那一行因为有些场景下 my.cnf 里的配置可能会被后面的加载项覆盖。4. 免认证模式下的密码与权限修复实操进入免密状态只是第一步真正的修复操作要看你的目标是单纯重置 root 密码还是需要清理权限表里的异常账号。4.1 先记一笔FLUSH PRIVILEGES 为什么必须最先执行免密登录后执行的第一条 SQL 应该是FLUSH PRIVILEGES;这条命令很关键原因我在前面原理部分提到过skip-grant-tables 模式下mysql.user等授权表的数据没有主动加载到内存用于认证你虽然能连接但会话的权限上下文并不完整。直接执行ALTER USER或UPDATE mysql.user时可能会收到类似ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement的报错。FLUSH PRIVILEGES会强制服务端重新加载授权表让后续权限变更走正常流程。这一步不做的话后面的修改很可能失败。4.2 重置 root 密码5.7 与 8.0 的差异MySQL 5.7 和 8.0 在密码存储和修改方式上有明显差异网上很多教程把两者混在一起照抄容易踩坑。下表是对照版本推荐命令备注MySQL 5.7UPDATE mysql.user SET authentication_stringPASSWORD(NewPass) WHERE userroot;也支持 ALTER USER但 UPDATE 方式更通用MySQL 8.0ALTER USER rootlocalhost IDENTIFIED BY NewPass;PASSWORD()函数已移除不能再使用 UPDATE 方式如果你确认自己的版本是 8.0执行ALTER USER rootlocalhost IDENTIFIED BY YourNewPassword;如果 root 账号不止localhost一个还需要逐个检查。比如有些服务器上存在root%账号这是之前为了远程连接创建的。建议先用查询语句看清楚SELECT user, host, plugin, authentication_string FROM mysql.user WHERE userroot;然后针对每个需要的 host 执行对应修改。修改完之后再执行一次FLUSH PRIVILEGES确保生效。4.3 创建临时管理账号与权限核对有时候光重置 root 密码还不够。比如这次故障是因为业务账号权限异常或者 root 的 host 范围太广需要收敛那么可以在免密模式下顺手处理。我惯用的做法是创建一个临时的管理账号比如ops_admin给它完整权限但只允许从内网管理机连接CREATE USER ops_admin192.168.1.0/255.255.255.0 IDENTIFIED BY StrongOpsPass; GRANT ALL PRIVILEGES ON *.* TO ops_admin192.168.1.0/255.255.255.0 WITH GRANT OPTION; FLUSH PRIVILEGES;这个账号在恢复正常模式后仍然可以用方便后续管理。等所有问题处理完再根据实际需要决定保留还是删除。同时要做权限核对。重点看三处是否存在空密码账号SELECT user, host FROM mysql.user WHERE authentication_string;是否存在非必要超级权限账号SELECT user, host FROM mysql.user WHERE super_privY;是否存在无主机的异常账号比如 user 为空字符串。这些检查在免密状态下一目了然比正常模式更方便但操作时也要小心不要误删真实业务账号。5. 退出免认证模式回到生产运行状态密码修好并不代表结束。从 skip-grant-tables 模式退回正常模式是整个流程里最容易出错的环节。我见过太多人在这一步“功亏一篑”要么忘了删配置要么删除配置后服务起不来。5.1 恢复配置与重启的三种落地方式第一种也是我推荐的方式修改/etc/my.cnf注释或删除skip-grant-tables和skip-networking然后重启。[mysqld] # skip-grant-tables # skip-networking重启之前先校验配置mysqld --validate-config这条命令会检查 my.cnf 里有没有语法错误。如果输出OK再执行systemctl restart mysqld第二种方式是在免密模式里直接用 mysqladmin 关闭服务mysqladmin -uroot shutdown然后确认进程退出把 my.cnf 里的维护参数删掉再用 systemctl start mysqld 启动。这种方式比直接 restart 更能模拟真实重启流程适合验证 MySQL 是否能正常关机。第三种方式针对临时用命令行参数启动的情况直接 kill 掉 mysqld_safe 进程再重新通过 systemd 启动即可。但这种方式进程管理比较乱不推荐作为常规手段。5.2 验证服务真正受控的测试清单重启完成后不能只看到“进程活着”就以为大功告成。我给自己列了一个测试清单照着走一遍# 1. socket 本机登录 mysql -uroot -pYourNewPassword -e SELECT 1 # 2. TCP 方式登录 mysql -h127.0.0.1 -P3306 -uroot -pYourNewPassword -e SELECT 1 # 3. 确认端口监听地址 ss -tlnp | grep 3306 # 4. 查看错误日志尾部 tail -50 /var/log/mysqld.log # 5. 检查关键参数 mysql -uroot -p -e SHOW VARIABLES LIKE skip_grant_tables; SHOW VARIABLES LIKE skip_networking;最后一条查询输出应该是-------------------------- | Variable_name | Value | -------------------------- | skip_grant_tables | OFF | | skip_networking | OFF | --------------------------如果skip_grant_tables还是 ON说明配置没有真正生效需要检查启动脚本是否读取了其他配置文件。注意 MySQL 读取配置的优先级是/etc/my.cnf、/etc/mysql/my.cnf、~/.my.cnf等路径不同安装方式可能不同。5.3 日志里常见的告警怎么看恢复模式期间错误日志里会留下一些特征信息。比如启动时如果带上了 skip-grant-tables日志中会出现类似 warning 的记录。正常重启之后这些信息不应该再出现。我通常会关注以下几类Access denied记录正常模式下一个账号反复登录失败说明密码没有改对或者客户端还持有旧连接缓存。Plugin caching_sha2_password相关的报错如果你的客户端版本较老不支持 MySQL 8.0 默认认证插件连接可能报错。解决办法是调整账号认证插件为mysql_native_password或升级客户端驱动。Too many connections恢复后大量业务连接涌入可能瞬间打满连接数。建议分批次启动应用不要把流量一次性切回。日志是排错的第一手资料不要只看 systemctl status。6. 一次维护之后生产环境还需要补上的几道防线skip-grant-tables 是一次高危维护操作恢复之后如果不做后续加固下次还是会陷入同样的被动。从这次故障里我总结了几件值得立刻做的事。6.1 连接来源与账号权限收敛生产环境 MySQL 的 root 账号原则上不应该允许从任意主机远程登录。你可以用下面命令查看当前情况SELECT user, host FROM mysql.user WHERE userroot;如果存在root%建议删除只保留rootlocalhost。业务访问使用独立账号并且只授予所需库的权限。例如一个只读写appdb的账号CREATE USER app_user192.168.%.% IDENTIFIED BY AppPass123; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO app_user192.168.%.%; FLUSH PRIVILEGES;同时检查 ECS 安全组入方向规则。阿里云控制台里3306 端口只应该对需要访问数据库的机器网段开放不对 0.0.0.0/0 开放。这里是最容易忽略的外部入口。6.2 备份、可追溯性与变更流程这次操作过程中我保留了 my.cnf 备份并且把执行过的 SQL 都记录在案。生产库变更没有日志出了问题根本没法复盘。建议至少做到三件事操作前备份mysql库mysqldump -uroot -p --single-transaction --databases mysql mysql_backup.sql。免密模式下备份权限不受影响但要注意 mysqld 正在运行时的锁行为。操作完成后保存一份权限快照mysql -uroot -p -e SELECT user,host,plugin FROM mysql.user user_list_$(date %F).txt。把变更记录写进团队文档包括为什么进维护模式、改了什么、验证了什么。备份不是万能药但能在误操作后把损失降到最低。6.3 从这次恢复过程中总结的几个坑最后分享几个真实踩过的坑每个都是花了时间才填平的。第一个坑是免密登录后直接执行 ALTER USER没有先 FLUSH PRIVILEGES结果 MySQL 报错拒绝执行。当时我还以为是账号状态问题来回试了好几次才反应过来。顺序真的很重要。第二个坑是恢复配置时只删了 skip-grant-tables忘了删 skip-networking导致重启后所有远程连接都失败。因为 skip-networking 会强制 MySQL 只走 socketTCP 连接直接拒绝。所以我在 5.2 的测试清单里特意加了skip_networking检查这也是后来养成的习惯。第三个坑是 MySQL 8.0 上沿用老的 5.7 重置密码语句。PASSWORD()函数在 8.0 里已经被移除执行直接报语法错误。这个坑非常典型尤其当你同时维护着两套数据库版本的时候。第四个坑是改完密码后应用侧连接池里的旧连接还在持续报错。MySQL 密码变更后已建立的连接可能不会立即断开但新连接会失败。如果应用连接池没有配置自动重连你会看到服务时好时坏。处理办法是改密码后主动重启应用服务或者让连接池配置空闲连接检测。这些经验听起来琐碎但生产维护本来就是由这些琐碎细节组成的。skip-grant-tables 模式是一把双刃剑用好了能给紧急故障一个安全出口用不好也会把一个小问题放大成安全事故。希望这篇内容能帮你把整个流程梳理清楚。