返回

CentOS 7 MySQL重置密码无法启动?3招解决启动失败

mysql

CentOS 7 重置 MySQL root 密码后无法重启?原因分析与解决方案

搞定了 MySQL root 密码重置,却发现服务死活重启不起来?别慌,这事儿不少人遇到过。通常,按照网上的教程,用 --skip-grant-tables 参数跳过权限验证来重置密码,一顿操作猛如虎,最后 systemctl restart mysqld 时,就卡壳了。

问题现象

你可能执行了类似下面的步骤来重置密码:

  1. 停止 MySQL 服务: systemctl stop mysqld
  2. 启动安全模式,跳过授权表: mysqld_safe --skip-grant-tables &
  3. 免密登录 MySQL: mysql -u root
  4. 执行密码更新语句:
    use mysql;
    -- 注意!这句可能是问题的关键
    update user set password=PASSWORD("新密码") WHERE user='root';
    flush privileges;
    quit;
    
  5. 尝试停止安全模式下的 MySQL (这里用 systemctl 可能停不掉后台的 mysqld_safe) : systemctl stop mysqld (或者用了 kill)
  6. 尝试正常启动 MySQL: systemctl start mysqld

结果,当你执行 systemctl restart mysqldsystemctl start mysqld 时,就遇到了报错:

Job for mysqld.service failed because the control process exited with error code. See "systemctl status mysqld.service" and "journalctl -xe" for details.

紧接着查看状态 systemctl status mysqld.service,会看到类似这样的信息:

● mysqld.service - MySQL Community Server
   Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
   Active: failed (Result: start-limit) since Tue 2016-04-26 07:23:19 UTC; 10s ago
  Process: 13293 ExecStartPost=/usr/bin/mysql-systemd-start post (code=exited, status=0/SUCCESS)
  Process: 13292 ExecStart=/usr/bin/mysqld_safe (code=exited, status=1/FAILURE)
  Process: 13280 ExecStartPre=/usr/bin/mysql-systemd-start pre (code=exited, status=0/SUCCESS)
 Main PID: 13292 (code=exited, status=1/FAILURE)

... systemd[1]: Failed to start MySQL Community Server.
... systemd[1]: Unit mysqld.service entered failed state.
... systemd[1]: mysqld.service failed.
... systemd[1]: start request repeated too quickly for mysqld.service
... systemd[1]: Failed to start MySQL Community Server.
... systemd[1]: Unit mysqld.service entered failed state.
... systemd[1]: mysqld.service failed.

关键信息在于 Active: failedExecStart=/usr/bin/mysqld_safe (code=exited, status=1/FAILURE),这说明 mysqld_safe 启动失败了,进而导致整个 mysqld.service 失败。systemd 尝试几次后,发现启动太频繁,就放弃了。

原因分析

罪魁祸首,十有八九是你更新密码时使用的 SQL 语句。

update user set password=PASSWORD("新密码") WHERE user='root';

这条语句在老版本的 MySQL (比如 5.6 及更早) 是标准操作。但是,从 MySQL 5.7 开始,用户认证信息存储方式发生了变化:

  1. 密码字段名称变了: 不再是 password 字段,而是 authentication_string 字段。直接更新 password 字段,对新版 MySQL 来说,可能根本没用,或者造成了数据不一致。
  2. PASSWORD() 函数可能被弃用或行为改变: 新版 MySQL 推荐使用 ALTER USER 语句来修改密码,它能正确处理密码哈希、认证插件等细节。直接在 UPDATE 语句中使用 PASSWORD() 函数可能产生不兼容的哈希值,导致 MySQL 在正常启动(不再 --skip-grant-tables)加载用户表时出错,从而启动失败。

另外,虽然不一定是主要原因,但也有一些小坑需要注意:

  • mysqld_safe 进程没有被正确关闭: 当你使用 mysqld_safe --skip-grant-tables & 把它放到后台后,后续使用 systemctl stop mysqld 可能无法精确地停止这个手动启动的后台进程。如果这个带 --skip-grant-tables 参数的进程还在运行,尝试用 systemctl start mysqld 启动一个“正常”的服务实例,可能会发生冲突或奇怪的问题。正确的做法是找到这个后台进程的 PID 并 kill 掉它。
  • flush privileges; 的时机: 虽然在 UPDATE 语句后执行 flush privileges; 是标准做法,但在 --skip-grant-tables 模式下,权限系统实际上是被绕过的。这条命令主要是为了让 MySQL 服务器重新加载授权表。

综合来看,最大的可能性就是密码字段/函数使用不当,导致 MySQL 启动时无法正确验证或加载用户数据。

解决方案

别担心,修好它并不复杂。核心思路是:再次进入 --skip-grant-tables 模式,用正确的方式设置密码,然后干净利落地重启服务。

方案一:使用 ALTER USER (推荐)

这是 MySQL 5.7 及以后版本推荐的标准密码修改方式,兼容性最好,也能正确处理各种认证插件。

  1. 确保 MySQL 服务彻底停止

    # 尝试用 systemctl 停止,可能失败,没关系
    systemctl stop mysqld
    # 强制查找并杀死所有 mysql 相关进程,确保干净
    # 注意:killall 比较粗暴,但此时服务已无法正常启动,通常是必要的
    killall mysqld mysqld_safe
    # 确认下是否还有残留
    ps aux | grep mysql
    
  2. 再次启动跳过授权表模式

    为了更安全,加上 --skip-networking 参数,禁止远程连接。

    mysqld_safe --skip-grant-tables --skip-networking &
    # 等待几秒钟,让它后台启动完成
    

    如果终端提示 Starting mysqld daemon with databases from /var/lib/mysql 之类的信息,说明可能启动成功了。

  3. 免密登录 MySQL

    mysql -u root
    
  4. 执行 FLUSH PRIVILEGES (可选但建议)

    --skip-grant-tables 环境下,有时先刷新一下权限能避免一些奇怪的问题,尤其是在准备修改用户数据前。

    FLUSH PRIVILEGES;
    
  5. 使用 ALTER USER 修改密码

    '你的新密码' 替换成你想要设置的实际密码。注意这里的用户是 'root'@'localhost',这通常是你本地登录时使用的 root 账号。

    ALTER USER 'root'@'localhost' IDENTIFIED BY '你的新密码';
    

    如果你的 MySQL 版本比较新(如 MySQL 8+),并且默认使用了 caching_sha2_password 插件,这条命令也能正确处理。

  6. 再次执行 FLUSH PRIVILEGES (保险起见)

    确保所有更改都被应用。

    FLUSH PRIVILEGES;
    
  7. 退出 MySQL 客户端

    quit;
    
  8. 手动停止 mysqld_safe 进程

    这一步很关键!不要依赖 systemctl stop

    # 查找 mysqld_safe 进程的 PID
    ps aux | grep 'mysqld_safe --skip-grant-tables'
    # 找到类似 "mysql    12345   0.0  0.1  11356  1536 ?        S    08:00   0:00 /bin/sh /usr/bin/mysqld_safe --skip-grant-tables --skip-networking" 这样的行
    # 记下 PID (例子中是 12345)
    
    # 杀死该进程 (替换 12345 为你找到的实际 PID)
    kill 12345
    # 等几秒,再次确认进程是否已消失
    ps aux | grep 'mysqld_safe --skip-grant-tables'
    
  9. 尝试正常启动 MySQL 服务

    systemctl start mysqld
    
  10. 检查服务状态

    systemctl status mysqld.service
    

    如果看到 Active: active (running),恭喜,问题解决了!

  11. 验证新密码

    尝试用新密码登录:

    mysql -u root -p
    # 输入你刚刚设置的“你的新密码”
    
  • 安全建议:

    • --skip-grant-tables 模式下操作时,务必加上 --skip-networking,阻止任何网络连接,防止未授权访问。
    • 操作完成后,立即杀死 mysqld_safe 进程,不要让跳过授权的实例长时间运行。
  • 进阶技巧:

    • 如果你需要重置其他主机(如 'root'@'%')的密码,ALTER USER 语句相应修改 'user'@'host' 部分即可。
    • MySQL 8.0 开始,默认认证插件变为 caching_sha2_password,旧的客户端可能需要特殊配置才能连接。ALTER USER 会处理好服务端的部分,但客户端连接时要注意。如果你遇到客户端连接问题,可能需要这样修改密码,强制使用旧的兼容插件:
      ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的新密码';
      

方案二:修正 UPDATE 语句 (适用于特定场景或作为备选)

如果 ALTER USER 命令因某种原因(比如极老的版本?)无法使用,或者你想尝试修复之前 UPDATE 的问题,可以试试直接更新 authentication_string 字段。但这种方式更“手动”,容易出错,一般不推荐。

操作步骤 1-3 与方案一相同(停止旧服务、用 --skip-grant-tables --skip-networking 启动、免密登录)。

  1. 切换到 mysql 数据库

    USE mysql;
    
  2. 执行 FLUSH PRIVILEGES (同样建议)

    FLUSH PRIVILEGES;
    
  3. 更新 authentication_string 字段

    直接将密码设置为空字符串。这是一种恢复策略:先让你能无密码登录,然后再用标准方式设置密码。

    UPDATE user SET authentication_string = '' WHERE User = 'root' AND Host = 'localhost';
    -- 注意: 这里直接清空密码,而不是使用 PASSWORD() 函数生成可能不兼容的哈希值
    

    如果你确实知道当前 MySQL 版本适用的哈希函数(比如 MySQL 5.7+ 的 mysql_native_password,可以使用 PASSWORD();MySQL 8 的 caching_sha2_password 需要特定的函数),也可以尝试直接生成。但清空密码然后重设,通常更稳妥。

  4. 刷新权限

    FLUSH PRIVILEGES;
    
  5. 退出 MySQL 客户端

    quit;
    
  6. 手动停止 mysqld_safe 进程

    同方案一的步骤 8,找到 PID 并 kill 掉。

  7. 尝试正常启动 MySQL 服务

    systemctl start mysqld
    
  8. 检查服务状态

    systemctl status mysqld.service
    

    如果成功启动(Active: active (running)),立刻执行下一步!

  9. 立即设置新密码!

    因为上一步清空了密码,现在 root 是空密码状态,非常不安全。

    # 无密码登录
    mysql -u root
    # 使用 ALTER USER 设置密码
    ALTER USER 'root'@'localhost' IDENTIFIED BY '你的非常健壮的新密码';
    FLUSH PRIVILEGES;
    quit;
    
  • 安全建议:
    • 方案二中的“清空密码”是非常危险的临时状态!必须在服务启动成功后,第一时间 登录并设置一个强密码。
    • 依然强调,--skip-grant-tables 运行时加 --skip-networking

方案三:检查并修复进程残留和 systemd 状态

有时,即使你修正了密码问题,之前的反复失败可能导致 systemd 认为该服务“失败”了,或者有僵尸进程干扰。在尝试了方案一或方案二后,如果仍然无法启动,可以试试这个清理步骤。

  1. 再次确认并清理所有 MySQL 进程

    killall mysqld mysqld_safe
    sleep 2 # 等待进程退出
    ps aux | grep mysql # 确认没有 mysql 进程了
    
  2. 重置 systemd 的失败状态

    这会告诉 systemd:“忘掉它之前的失败记录,让我们重新试试。”

    systemctl reset-failed mysqld.service
    
  3. 尝试启动 MySQL 服务

    systemctl start mysqld
    
  4. 检查状态

    systemctl status mysqld.service
    # 如果这次成功,说明之前的确是 systemd 状态或进程残留的问题
    # 如果还是失败,回头仔细检查方案一或方案二的步骤是否都执行到位,或者查看 MySQL 的错误日志文件 (通常在 /var/log/mysqld.log 或 /var/log/mysql/error.log) 获取更详细的失败原因。
    

处理这类问题,关键在于理解 --skip-grant-tables 的影响以及新旧版本 MySQL 在用户管理上的差异。ALTER USER 是现代 MySQL 的正确选择。操作时保持冷静,一步步来,特别是注意清理进程和使用 --skip-networking,问题通常都能解决。