
1. 你遇到的是哪一种身份被拒1045 报错的常见来源半夜被微信语音叫起来通常都是因为这一句ERROR 1045 (28000): Access denied for user rootlocalhost (using password: YES)。我最早处理这类问题时也走过弯路一上来就急着重置密码结果服务搞停了半天最后发现根本是另一码事。所以先别急着动手把症状分清楚才知道要走的路径。第一种情况最简单密码真的忘了或者被某个同事更新后没同步给你。特征是输入任何密码都报 1045而且日志里没有别的异常。这种情况适合用后面讲的skip-grant-tables或init-file方案重置属于典型的我不知道当前密码。第二种情况是 5.7 和 8.0 用户特别容易踩的安装后压根不知道初始密码在哪。MySQL 5.7 之前默认 root 无密码但 5.7 开始默认会生成一个临时密码8.0 也一样。很多新手拿着旧教程直接回车当然 1045。所以装完库报错先别怀疑人生去日志里找RHEL、CentOS 系通常是/var/log/mysqld.logDebian、Ubuntu 系在/var/log/mysql/error.log用grep temporary password就能看到类似A temporary password is generated for rootlocalhost: xxxxxxxx的一行。第三种情况是密码没有错但账号被标记为过期。这时候报错往往不是 1045而是 1820You must reset your password。很多人会误判成密码失效反复重试后把自己锁在外面。这个我放到后面专门说因为重置完密码后它出现的频率反而更高。最后还要提一个版本差异这也是为什么攻略必须分 5.7 和 8.0 的原因默认认证插件变了。5.7 默认是mysql_native_password8.0 默认是caching_sha2_password。很多老教程里UPDATE mysql.user SET PasswordPASSWORD(...)的写法在 5.7 还能凑合到 8.0 直接报字段不存在的错或者改完仍然登不上。这也是我在标题里特意写适用于 5.7 和 8.0的原因两套操作在同一思路上有不同的细节要照顾。2. skip-grant-tables最通用的免密重置法但 5.7 和 8.0 有讲究2.1 这招的原理和风险--skip-grant-tables的作用是让 mysqld 启动时跳过授权表的加载和密码校验。说得直白点这个模式下任何用户登录都不验密码如同把数据库的前门拆了。正因如此危险也是同级别的操作时必须记住临时启动时一定要搭配--skip-networking。它会让 mysqld 只监听本地 socket不监听 TCP 端口杜绝远程趁虚而入。很多文章不强调这一点我见过生产环境有人用这个参数启动后忘了加skip-networking结果数据库裸奔了几个小时端口 3306 对外开放任何能连到机器的人都能免密进 root。这是绝对不该犯的错。2.2 Linux 下最标准的恢复流程以 systemd RPM 安装的 MySQL 为例完整流程如下第一步停服务。RHEL CentOS 系是systemctl stop mysqldDebian Ubuntu 系是systemctl stop mysql。注意别用kill硬杀可能损坏数据目录。第二步临时启动。两种方式任选一种是直接改配置文件在/etc/my.cnf的[mysqld]段加上skip-grant-tables skip-networking然后systemctl start mysqld。另一种是不动配置文件手动前台启动mysqld --skip-grant-tables --skip-networking --usermysql 个人建议用第一种因为结束后不容易忘记清理现场——当然前提是你记得改回来。第二种适合临时应急但进程挂在终端上不小心关掉终端服务就断了。第三步无密码登录mysql -uroot注意不需要-p直接回车即可。如果这里报 2002 socket 错误多半是服务没起来别急着怀疑回头再查。第四步关键区别来了MySQL 5.7 推荐这样写FLUSH PRIVILEGES; ALTER USER rootlocalhost IDENTIFIED BY NewPass123!;MySQL 5.7 老教程里还有一种改法UPDATE mysql.user SET authentication_stringPASSWORD(NewPass123!) WHERE Userroot; FLUSH PRIVILEGES;这条在 5.7 里能用但 PASSWORD() 函数已经标记弃用且 5.7 的字段叫authentication_string不是旧版的Password。别再把网上 5.6 时代的SET PasswordPASSWORD(...)抄过来了。MySQL 8.0 则必须这样FLUSH PRIVILEGES; ALTER USER rootlocalhost IDENTIFIED BY NewPass123!;如果没有先执行 FLUSH PRIVILEGES直接 ALTER USER 会报错ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement2.3 为什么 8.0 必须先 FLUSH PRIVILEGES原因在于skip-grant-tables模式下mysqld 启动时压根没有把磁盘上的授权表加载到内存权限系统处于不初始化的状态。ALTER USER属于权限管理语句它需要在内存里有一份完整的权限结构才能执行。先FLUSH PRIVILEGES是让服务端强制做一次加载授权表的动作把磁盘上的mysql.user读回内存里。权限结构齐了ALTER USER 才能正常跑。这也是为什么不建议在 8.0 里用 UPDATE 直接改mysql.user8.0 的账号体系比 5.7 复杂得多涉及plugin、authentication_string、account_locked、password_expired等字段手动 UPDATE 非常容易改漏。比如你把密码哈希写进了authentication_string但忘了确认plugin是不是正确的caching_sha2_password或者不小心把account_locked改成了 Y登录照样失败。ALTER USER会自动帮你处理这些关联字段少给自己找麻烦。改完后退出把配置文件里的skip-grant-tables和skip-networking删掉然后systemctl restart mysqld再验证新密码登录。2.4 临时状态下的几个隐藏细节第一执行过FLUSH PRIVILEGES之后免密登录的行为并不会自动关闭。很多人以为 flush 一下密码校验就恢复了其实没有。skip-grant-tables这个启动参数仍然生效只有重启服务并去掉这个参数密码校验才会真正恢复。第二改完密码后最好顺手查一遍SELECT user, host, plugin, account_locked FROM mysql.user WHERE userroot;确认account_locked是 Nplugin是正常插件。这一步能拦截掉一半的改完还是登不上问题。第三临时启动占用数据目录时绝不能再启动正式服务。手动跑mysqld --skip-grant-tables前务必确认没有别的 mysqld 进程否则第二个进程会因数据目录被占用直接退出报错信息还很隐蔽。3. init-file 方案不加跳过参数也能改密码的干净路子3.1 为什么还需要第二种方案skip-grant-tables虽然通用但有一个天然的缺点它会制造一段无密码校验的窗口期。哪怕加了skip-networking本机上有其他用户、或者有过路人执行mysql -uroot还是能进去。生产环境安全要求高的场景理想状态下不应该出现这种空窗。另一种选择是init-file。mysqld 在启动过程中会读取[mysqld]配置里init-file指向的 SQL 文件并按顺序执行里面的语句。关键点在于这时权限系统是正常加载的所有插件也都是正常初始化的不存在权限结构没加载的问题。所以你可以在 init-file 里直接写ALTER USER它会在服务真正对外提供服务前把密码改好。相比 skip-grant-tables这种方式的侵入性小得多全程不需要跳过密码校验、不需要手动 FLUSH PRIVILEGES、也不需要经历关闭权限校验→加载权限→修改→恢复正常的复杂切换。适合线上 MySQL 密码突然失效、但又不敢让服务裸奔的场景。3.2 具体操作步骤第一步准备 SQL 文件。比如创建/tmp/reset_root.sqlALTER USER rootlocalhost IDENTIFIED BY NewPass123!;如果你的 root 账号还有别的 host比如root192.168.%需要针对每个 host 写一条 ALTER USER。第二步在配置文件里指定[mysqld] init-file/tmp/reset_root.sql第三步重启服务让 init-file 被执行systemctl restart mysqld第四步验证mysql -uroot -p输入新密码能登进去就对了。第五步立刻清理现场。把/tmp/reset_root.sql删掉把配置文件里的init-file那行注释或删除。3.3 init-file 最大的坑每次启动都会执行很多人在这一步翻车。init-file不是只执行一次而是每次 mysqld 启动都会重新读取并执行。如果你不删掉这个配置下一次任何原因导致的重启都会把密码重新改成 init-file 里的那一串值。如果文件还在但内容被改过甚至会导致密码被改得连自己都不知道。所以一个非常实用的习惯是执行完修改后立即检查两项rm -f /tmp/reset_root.sql grep -n init-file /etc/my.cnf /etc/my.cnf.d/*.cnf确保配置里没有残留。这一步比改密码本身更重要。另外注意文件权限。init-file 里的文件需要 mysqld 进程用户一般是 mysql有读权限。如果文件放在 root 家目录且权限是 600mysqld 启动时会因为读不了而静默跳过表现就是重启后密码没变没有报错让人一头雾水。建议写完后chmod 644或者干脆放到/tmp。4. 换个环境再看一遍Windows 服务与 Docker 容器怎么处理4.1 Windows 下别再纠结服务管理器Windows 上的 MySQL 通常以系统服务方式运行服务名像MySQL80。重置 root 密码最常见的两种姿势都可行但别指望在服务管理器里直接加启动参数——那个入口很麻烦。更推荐的办法是手动起进程。以管理员身份打开 CMD先停服务net stop MySQL80然后进入 MySQL 的 bin 目录手动跑mysqld --skip-grant-tables --skip-networking --console注意务必确认服务已经停止否则数据目录被正式服务占着手动进程起不来。--console是让日志打到当前窗口方便观察有没有报错。接着另开一个 CMD同样进入 bin 目录执行mysql -uroot接下来就是 Linux 一样的操作了FLUSH PRIVILEGES 再 ALTER USER。改完后回到第一个窗口按 CtrlC 停掉手动 mysqld再用net start MySQL80把正式服务拉起来。如果是用 zip 免安装版连服务都没注册的那种思路也一样只是第一步可以跳过停服务。Windows 的路径如果有空格记得--defaults-fileC:\Program Files\...加引号同时检查C:\ProgramData\MySQL\MySQL Server 8.0\my.ini改配置前先停服务改完再启动。4.2 Docker 容器不重建容器怎么改Docker 里跑 MySQL最容易踩的坑是容器里的 root 密码忘了直接docker exec -it 容器名 mysql -uroot会提示你输密码根本进不去。而官方镜像默认数据都在 volume 里正确思路是用临时容器挂同一个 volume带上 skip-grant-tables 参数启动。举个例子。原本容器叫mysql8数据卷叫mysql8data先停掉原容器docker stop mysql8然后起一个挂载同样数据卷的临时容器docker run -d --name mysql-reset -v mysql8data:/var/lib/mysql mysql:8.0 --skip-grant-tables --skip-networking注意不要加MYSQL_ROOT_PASSWORD环境变量否则初始化逻辑会在数据目录已存在的情况下跳过不会覆盖你原来的数据。容器起来后docker exec -it mysql-reset mysql -uroot接下来的 SQL 和前面一样先 FLUSH PRIVILEGES 再 ALTER USER。改完退出删掉临时容器docker stop mysql-reset docker rm mysql-reset最后再docker start mysql8用新密码验证。晚上改密码最怕的一件事是原容器没停就起临时容器两个实例同时访问数据目录不仅起不来还可能在日志里留下另一进程正在使用 datadir的提示。所以顺序一定是先停原容器再起临时容器。如果 Docker 部署用的不是 volume 而是 bind mount原理完全相同路径换成宿主机真实目录即可。4.3 顺手提一句 Navicat 连接问题重置完密码后用 Navicat 连不上的情况太常见了。8.0 默认caching_sha2_password老版本 Navicat 会提示Authentication plugin caching_sha2_password cannot be loaded。如果重置时你确实需要兼容旧客户端可以临时这样改ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY NewPass123!;但这种做法是把账号降级到旧认证协议不算长期方案。新版客户端、新版 Navicat 都支持caching_sha2_password建议密码恢复后优先保持 8.0 默认插件而不是为了一个客户端把安全等级拉低。遇到 SSL 相关的报错先别怀疑密码尝试把连接里的 SSL 模式改成禁用或不验证大概率能定位到问题方向。5. 密码改完不等于结束1820 过期、2002 socket、和 SSL 错误5.1 ERROR 1820账号被标记为密码过期重置完成后我见过最多的后续问题是这个ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.现象是密码明明正确能登录但执行任何 SQL 都报这个。原因不是密码错而是账号上password_expired被标记成了 Y。MySQL 要求你改一次密码才能继续操作。处理方式有几种。如果是系统策略要求你必须设置新密码那就老老实实再执行一次ALTER USER rootlocalhost IDENTIFIED BY AnotherPass123!;如果只是想让这个账号不再过期可以直接ALTER USER rootlocalhost PASSWORD EXPIRE NEVER;排查时可以看SELECT user, host, password_expired FROM mysql.user;5.7 里默认密码策略是 360 天过期8.0 默认不过期但如果你在配置里显式设置了default_password_lifetime重置密码时最好顺带确认一下当前策略。这个坑的麻烦之处在于它不会在登录时拦你而是在你执行第一条语句时才跳出来特别容易让人误以为是改完密码后哪一步搞错了。5.2 ERROR 2002连不上 socket服务去哪了重置过程中经常会出现ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock (2)这类报错和密码无关而是客户端根据 socket 路径找不到服务端。常见三种原因服务确实没起来服务起来了但 socket 路径和你连的路径不一致手动起的 skip-grant-tables 进程因为终端关闭被一起带走了。排查顺序建议是ps -ef | grep mysqld看有没有进程。有进程再确认 socket 实际位置ss -lx | grep mysql.sock如果路径不一致比如服务端在/var/run/mysqld/mysqld.sock客户端默认去连/tmp/mysql.sock那就加-S /var/run/mysqld/mysqld.sock指定路径或者在my.cnf的[client]段统一配置socket。还有一种诡异情况你之前手动跑了一个 skip-grant-tables 进程文件配置里也写了 socket 路径但后来那个终端被关了进程还在却已经没人知道它监听在哪里。这时候别手动 kill按正式服务方式systemctl stop再systemctl start让服务回到受管控的状态。5.3 SSL 相关报错和密码无关但很迷惑重置密码后如果连接时报这个ERROR 2026 (HY000): SSL connection error: unknown error number先别急着反复改密码。SSL 错误多半是服务端证书有问题或者客户端默认开了 SSL 但服务端无法完成握手。快速排查mysql --ssl-modeDISABLED -uroot -p如果禁用 SSL 后能正常连接那就是证书链路的问题。检查配置里的ssl-ca、ssl-cert、ssl-key路径是否还存在、权限是否允许 mysql 用户读取。特别是有人清理过期证书或者重新签发过后忘记同步 MySQL 配置这种问题就会出现。另外MySQL 8.0 默认的caching_sha2_password在非 SSL 连接下会走 RSA 密钥交换如果auto.cnf、私钥文件缺失也可能引发连接失败。这种情况比较少见但一旦碰上会非常难查。处理方式是确认服务端配置完整后重启或者临时用--ssl-modeDISABLED绕过验证阶段先把业务恢复起来再处理证书。6. 收尾工作与安防习惯别把临时状态带进生产6.1 改完密码后的现场清理清单每次重置完我习惯按这个顺序过一遍缺一不可第一检查配置残留。全局搜一遍grep -rn skip-grant-tables\|init-file /etc/my.cnf /etc/my.cnf.d/注意my.cnf.d目录下的碎片文件经常被忽略很多事故就是这里残留了参数没被发现。第二确认进程干净。正常模式下ps -ef | grep mysqld应该只有一个守护进程没有额外的前台 mysqld。第三重启验证。systemctl restart mysqld后用新密码执行mysql -uroot -p -e SELECT VERSION();确认 SQL 能执行不只是能登录。第四删除临时 SQL 文件。如果用了 init-file别只删配置里那行文件本身也要删掉防止别人看到密码明文。6.2 把密码管理做成习惯经历过几次半夜被叫起来之后我给自己定了几条规矩现在基本没再为忘密码折腾过生产环境 root 账号只允许localhost连接业务账号单独建root 密码和业务密码彻底隔离。即使有人拿到了业务账号也不能用 root 的权限瞎搞。这个习惯比任何重置技巧都管用。密码重置完成记得同步到内部密码管理工具哪怕只是记在加密的文档里。很多人重置完密码转头就忘了三个月后再来一轮完全没有必要。有条件的话给 5.7 设置密码过期周期提醒比如default_password_lifetime180配合监控脚本或系统 cron 提前提醒而不是等报错出来再救火。这个思路对应热搜里那个linux 密码过期提醒通知的诉求本质上是把被动救火变成主动管理。最后分享一个我自己的小技巧临时启动时无论用哪种方案我都会在配置里加上skip-networking。等密码恢复、正常模式启动后需要远程访问再显式打开端口。养成这个习惯之后再也没担心过恢复期间数据库裸奔的问题。数据库密码恢复这件事本身不难难的是每一步都要想清楚它为什么能生效以及恢复完之后有没有把临时状态彻底收拾干净。