MySQL 密码这个东西,平时放在那里谁也不会多看一眼,直到某天你打开 Navicat 或者敲下mysql -u root -p,回车三次都提示ERROR 1045 (28000): Access denied for user 'root'@'localhost',那一刻脑子里是空的。尤其是刚装完 MySQL 的朋友,跟着教程一路 Next 点下来,密码随手输了点什么,第二天就全忘了;还有人接手了同事留下的开发机,root 密码写在某个早已删除的 txt 里;更多的场景是测试环境为了图方便设成123456,上线前要统一改成强密码。修改 MySQL 密码看着简单,实际上版本差异、认证插件、密码策略三座大山往那一摆,新手很容易卡住。这篇内容就把我这些年用过的四种改密码方法从头到尾捋一遍,从 MySQL 8.0 首选的 SQL 语句,到彻底忘记密码时的服务重启大法,每一步都配上可直接复制的命令、参数解释和我自己踩过的坑。不管你是刚学 MySQL 的学生,还是在 Windows 上用免安装压缩包版折腾的开发者,跟着走都能改明白。
1. 动手之前,先把这几件事搞清楚
改密码失败的绝大多数原因不是命令写错,而是根本没搞明白自己手上这个 MySQL 是什么版本、用什么认证方式、密码策略卡在哪一步。花五分钟把下面这几件事确认清楚,后面能省一小时。
1.1 先确认版本,8.0 和 5.7 的语法真不一样
很多人复制网上的教程报ERROR 1064语法错误,八成就是版本对不上。MySQL 5.7 时代改密码流行这么写:
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass');到了 MySQL 8.0,PASSWORD()这个函数被官方彻底移除了,再这么写就是语法错误。官方给的理由是密码散列不应该在 SQL 层暴露给用户,改密码统一走ALTER USER或SET PASSWORD的字面量形式。
确认版本的方式很简单,登录进去执行:
SELECT VERSION();或者不登录,直接在命令行敲:
mysql --version输出形如mysql Ver 8.0.43 for Win64 on x86_64,前面的 8.0.43 就是版本号。这个版本号后面会反复用到,因为 8.0 内部还有小版本差异,比如 8.0.14 之后--skip-grant-tables的行为就有调整,后面第 5 章会细说。
顺带提一句,热词里频繁出现的“mysql 9.7”其实是搜索联想搞错了,MySQL 官方目前稳定版本线是 8.0 和 8.4 LTS,不存在 9.7 这个版本,别被搜索结果带偏。
1.2 认证插件决定了客户端能不能连上
MySQL 8.0 默认的认证插件是caching_sha2_password,5.7 及以前是mysql_native_password。这两个插件的差别在于密码传输和缓存机制,caching_sha2_password安全性更高,但它有个副作用:一些老版本的图形化客户端、旧版驱动、老旧的 JDBC 包认不出这个插件,连接时会直接报Authentication plugin 'caching_sha2_password' cannot be loaded。
所以在改密码的时候,如果你用的 Navicat 是十年八年前的老版本,或者项目里的 JDBC 驱动还停留在 5.1.x,那改密码的同时最好顺手把插件也指定成mysql_native_password:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'YourNewPass123!';这一句既改了密码,也换了认证插件,一步到位。反过来,如果你的客户端比较新,就老老实实用默认的caching_sha2_password,没必要为了兼容降级。
查当前用户用的是哪个插件,执行:
SELECT user, host, plugin FROM mysql.user WHERE user = 'root';结果里的plugin列就是答案。注意这里的host字段,localhost和%是两个不同的账户,改密码时一定要写对,改错了账户等于白改。
1.3 密码策略会拦下你的弱密码
MySQL 8.0 默认启用了validate_password组件,密码长度不到 8 位、纯数字、纯字母、包含用户名,统统会被拒,报错长这样:
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements新手看到这行字经常一脸懵,以为自己命令写错了。其实不是,是策略在拦。先看看当前策略:
SHOW VARIABLES LIKE 'validate_password%';重点关注三个值:validate_password.policy(策略等级)、validate_password.length(最小长度)、validate_password.mixed_case_count(大小写字母数量要求)。策略等级从低到高是 LOW、MEDIUM、STRONG,默认 MEDIUM 要求长度 8 且必须含数字、大小写字母和特殊字符。
注意:在测试环境或者本机学习环境,可以临时放宽策略,但生产库千万别这么干。修改策略的写法是
SET GLOBAL validate_password.policy = LOW;,8.0 里参数名带点号,5.7 里是下划线validate_password_policy,写错了会提示 unknown system variable。
如果你是在自己电脑上学习,我建议是别关策略,直接用一个满足要求的强密码,比如Root@2024!,既过了校验,又顺便记住了强密码长什么样。真觉得麻烦,把策略降到 LOW 并设validate_password.length = 6就够了。
1.4 改密码前的兜底动作
改 root 密码这件事,属于典型的“改之前觉得稳,改之后连不上”的操作。我自己的习惯是动手前做两件事:一是把当前的用户权限表导出一份,二是确认还有第二种登录方式(比如另一个管理员账号,或者能重启服务的权限)。
-- 备份用户表(用 mysqldump 更稳妥,这里给个快速版) SELECT user, host, plugin, authentication_string FROM mysql.user INTO OUTFILE '/tmp/user_backup.csv';这个语句在 Windows 上会因为路径和 secure_file_priv 限制报错,那就退而求其次,直接把查询结果复制粘贴到记事本里存着。别嫌麻烦,我见过有人改 root 密码时手抖敲错一个字符,又没有第二个账号,最后只能走第 5 章的暴力重置流程,多花半小时。
2. 方法一:ALTER USER 语句(8.0 首选,也最推荐)
ALTER USER是目前官方最推荐、语义最清晰的改密码方式,8.0 全系列支持,5.7 也支持(5.7 从 5.7.6 开始引入)。它的好处是只管改密码和插件,不牵扯权限刷新,写起来也最像人话。
2.1 基础语法和实操步骤
登录 MySQL 之后,一条语句解决:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass@2024';这里要逐词拆一下,因为新手最容易在引号和 host 上翻车:
'root'@'localhost'是账户的完整标识,用户名和主机名都要用单引号包起来,中间用@连接。IDENTIFIED BY后面跟新密码,也必须是单引号字符串。- 语句结尾的分号别丢,这是最容易犯的低级错误。
改完立即验证,先退出客户端:
EXIT;再用新密码登录:
mysql -u root -p回车后输入新密码,能进到mysql>提示符就算成功。
提示:很多人改完密码会习惯性敲一句
FLUSH PRIVILEGES;。对于ALTER USER来说这一句是多余的,语句本身就会同步更新内存中的权限表。但如果你是通过UPDATE mysql.user直接改表,那就必须FLUSH PRIVILEGES,否则新密码不生效。这个区别后面第 5 章会再用到。
2.2 只改某个特定主机下的账户
假设你的 MySQL 里同时存在root@localhost、root@127.0.0.1、root@%三个账户,只改localhost那个,用 127.0.0.1 连接时用的还是老密码,就会出现“我明明改了密码为什么还能用旧密码登录”的诡异现象。所以改之前先用前面 1.2 节的查询语句把账户列表列出来,有几个改几个。
如果想让某个用户从任意主机都能用同一个密码登录,可以一次性写多条:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass@2024'; ALTER USER 'root'@'127.0.0.1' IDENTIFIED BY 'NewPass@2024'; ALTER USER 'root'@'%' IDENTIFIED BY 'NewPass@2024';改完用下面的语句确认,authentication_string字段会变成一串新的散列值,肉眼看不出来是什么密码,但能看出它变了:
SELECT user, host, plugin, authentication_string FROM mysql.user WHERE user = 'root';2.3 顺手解决密码过期问题
MySQL 有个default_password_lifetime参数控制密码有效期,单位是天,0 表示永不过期。8.0 默认是 0,但有些公司会把它设成 90,于是三个月后你突然登录不了,提示密码已过期。这时候ALTER USER还能救场,因为你用旧密码连上后,MySQL 会允许你先改密码再干别的:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'FreshPass@2024';改完新密码就恢复正常了。如果想给账户单独设过期策略,可以这么写:
ALTER USER 'root'@'localhost' PASSWORD EXPIRE INTERVAL 180 DAY;设成 NEVER 就是永不过期:
ALTER USER 'root'@'localhost' PASSWORD EXPIRE NEVER;这个参数在团队协作环境里挺有用,公共账号设个 90 天,个人账号设 NEVER,避免大家被频繁弹窗打断工作。
2.4 本节注意事项
ALTER USER虽然好用,但有两个坑必须提。第一,8.0 里如果新密码包含!、$、&这些 shell 特殊字符,在 Linux 命令行直接用mysql -e传参时会被 shell 吃掉,导致 SQL 收到的是残破的密码,建议这类操作一律进交互式客户端再执行。第二,改密码需要CREATE USER权限或者对mysql库的UPDATE权限,普通业务账号是改不了别人密码的,报错ERROR 1227就是权限不足,别以为是语法问题。
3. 方法二:SET PASSWORD 语句(新旧版本都能用)
SET PASSWORD是比ALTER USER更老的一种写法,5.6、5.7、8.0 都支持,适合在多个版本间来回切换的人记忆一套写法。但要注意 8.0 里去掉了PASSWORD()函数,写法有变化。
3.1 标准写法和作用范围
给自己改密码,不用写用户名,直接:
SET PASSWORD = 'NewPass@2024';给别的账户改密码,就要带上账户标识:
SET PASSWORD FOR 'root'@'localhost' = 'NewPass@2024';这两种写法的区别在于权限要求:给自己改只需要普通权限,给别的账户改需要更高权限。日常自己用,第一种就够了,少打几个字。
3.2 和 ALTER USER 的对比
两种方式在功能上高度重叠,我把差异整理成一张表,方便对照记忆:
| 对比项 | ALTER USER | SET PASSWORD |
|---|---|---|
| 最低支持版本 | 5.7.6 | 5.6 及更早 |
| 语法直观度 | 高,读起来像英文句子 | 中,=号容易漏 |
| 指定认证插件 | 支持,IDENTIFIED WITH ... BY ... | 不支持 |
| 设置密码过期 | 支持,PASSWORD EXPIRE | 不支持 |
| 跨版本兼容性 | 8.0 和 5.7 好,5.6 不行 | 全版本通吃 |
| 推荐程度 | 首选 | 备选 |
从表里能看出来,ALTER USER是超集,功能更全。之所以还要讲SET PASSWORD,是因为很多遗留脚本、老教程、自动化部署脚本里用的是它,看得懂比会用更重要。
注意:在 8.0 里执行
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('xxx');一定会失败,因为PASSWORD()函数已经不存在了。如果你在网上抄的脚本里有这个函数,直接把它删掉,改成ALTER USER就行。
3.3 一个容易被忽略的细节
SET PASSWORD执行成功后同样不需要FLUSH PRIVILEGES,因为它也是 DDL 层面的操作,会自动更新权限缓存。真正需要手动刷新的只有直接UPDATE mysql.user表这一种情况。很多人把“改密码”和“刷新权限”绑在一起当成固定动作,其实区分清楚之后,能省掉一步无意义操作,也能避免在skip-grant-tables模式下搞混顺序(那个场景恰恰必须先刷新,原因见 5.3 节)。
4. 方法三:mysqladmin 命令行工具(连客户端都不用进)
前面两种方法都得先登录进 MySQL 才能执行。但有一种尴尬情况:密码你已经知道了,就是想换一个,同时又懒得进客户端敲 SQL。这时候用mysqladmin最省事,一条命令搞定。
4.1 基本用法和参数陷阱
命令格式是这样:
mysqladmin -u root -p password 'NewPass@2024'回车后会提示Enter password:,这里输入的是旧密码,输完回车,密码就改成新的了。整个过程不用进 MySQL 客户端。
新手最容易踩的坑是-p的写法。-p和后面的密码之间不能有空格,直接连着写表示旧密码:
mysqladmin -u root -pOldPass@2024 password 'NewPass@2024'如果写成-p OldPass@2024(中间有空格),MySQL 会把OldPass@2024当成password之外的多余参数,然后仍然提示你输入密码,实际登录用的还是交互输入的密码,行为和预期不符。这个细节在官方文档里写得很含蓄,我当年是踩过一次才记住的。
4.2 Windows 压缩包版的路径问题
Windows 上用免安装 zip 版 MySQL 的朋友,mysqladmin不在系统 PATH 里,直接敲会提示“不是内部或外部命令”。两个解决办法:一是进入 MySQL 的bin目录再执行,比如:
cd C:\mysql-8.0.43-winx64\bin mysqladmin -u root -p password "NewPass@2024"二是把bin目录加到系统的环境变量 Path 里,一劳永逸。我推荐第二种,因为后面你还会反复用到mysql、mysqld、mysqldump,加一次终身受益。加完之后记得重开一个命令行窗口,环境变量才会生效。
另外 Windows 下密码里有$、!这些字符时,用双引号包起来,PowerShell 里可能还要加反引号转义,最稳的办法是用单引号或者干脆换成不含特殊字符的密码。
4.3 什么时候该用它
mysqladmin适合的场景有两类:一是写运维脚本批量改密码,因为它不依赖交互式客户端,可以塞进 shell 脚本;二是在一些精简容器镜像里,为了减小体积可能只装了mysqladmin而没装完整客户端。缺点是它只能改密码,不能指定认证插件,也不能设置过期策略,功能比较单一。日常手动改密码,我还是更习惯用ALTER USER,因为语法更清晰,出错时提示也更友好。
5. 方法四:彻底忘记密码时的强制重置
前面三种方法的前提都是“我知道旧密码”。真正让人抓狂的是旧密码完全想不起来,ERROR 1045像一堵墙。这时候只能请出终极方案:用--skip-grant-tables模式启动 MySQL,跳过权限验证,进去把密码改掉再恢复正常启动。
5.1 原理解析:为什么这个模式能绕过密码
MySQL 启动时有一个参数叫--skip-grant-tables,它的作用是让服务器启动时不去加载权限表。权限表不加载,所有连接都默认拥有最高权限,登录时自然也就不校验密码了。这原本是给管理员做紧急维护用的,比如权限表损坏导致谁都登不进去时用来修复,现在被广泛用于“忘记密码”的救援场景。
要强调的是,这个模式下 MySQL 处于完全不设防状态,任何能连上服务器的人都能拿到全部数据。所以官方在 8.0 里做了一个改进:使用--skip-grant-tables时会自动启用--skip-networking,也就是只允许本机通过 socket 或命名管道连接,不允许网络远程连接。这个细节很重要,意味着你不用担心在重置过程中被人从外网趁虚而入。
5.2 Linux 环境下的完整操作流程
先停掉正在运行的 MySQL 服务。用 systemctl 管理的发行版:
sudo systemctl stop mysqld然后用mysqld_safe以跳过权限表的方式启动:
sudo mysqld_safe --skip-grant-tables --skip-networking &看到日志里出现mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql就说明起来了。另开一个终端,不用密码直接登录:
mysql -u root进去之后这一步非常关键,必须先刷新权限表:
FLUSH PRIVILEGES;然后再执行改密码的语句:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass@2024';最后退出客户端,把 MySQL 服务停掉,用正常方式重启:
mysqladmin -u root -p shutdown sudo systemctl start mysqld重启后用新密码登录验证。
注意:8.0.14 之后的版本,如果不在
skip-grant-tables模式下先执行FLUSH PRIVILEGES,直接ALTER USER会报ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement。这是因为服务器认为权限表未加载,拒绝执行需要查询权限的操作。刷新一次之后,权限表被读取,ALTER USER就能正常执行了。这个坑我第一次遇到时在网上翻了半天才找到原因。
5.3 Windows 环境下的完整操作流程
Windows 上用免安装 zip 版的朋友,操作稍微不一样,因为 Windows 没有mysqld_safe。思路是先用管理员身份打开命令行,把服务停掉,再手动启动一个跳过权限校验的实例。
第一步,停服务。如果你是用mysqld --install注册过服务的:
net stop mysql80服务名可能是mysql80、MySQL或者你自定义的名字,用services.msc打开服务列表可以确认。
第二步,启动跳过权限校验的实例:
mysqld --skip-grant-tables --console--console的作用是把日志直接打印到当前命令行窗口,方便观察启动状态。这个窗口不要关,它就相当于正在运行的 MySQL 服务。再新开一个命令行窗口,执行:
mysql -u root后续步骤和 Linux 一致:先FLUSH PRIVILEGES;,再ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass@2024';,改完EXIT退出。
第三步,回到第一个窗口,按Ctrl + C终止那个跳过权限的实例,然后用正常方式启动服务:
net start mysql80还有一种更优雅的做法是使用--init-file参数。新建一个文本文件,比如C:\reset.sql,内容写:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass@2024';然后用这个文件启动:
mysqld --init-file=C:\reset.sql --console服务器启动时会自动执行这个文件里的语句,改完密码后停掉服务,正常启动即可。这种方式不用手动进客户端,适合写成一键脚本。
5.4 这一个方法的安全要点
--skip-grant-tables本质上是把数据库的门锁拆了,所以整个操作过程要尽量短,改完立即恢复正常启动。如果有人在你重置期间连上了数据库,所有数据对他来说都是敞开的。生产环境做这件事,一定要挑业务低峰期,并且确保没有其他会话在连接。
另外,如果你是在云服务器上操作,注意云厂商的安全组可能在网络层做了访问控制,但本机 socket 连接不受影响,所以--skip-networking这个保护是有效的。操作完之后建议检查一下mysql.user表里有没有多出来的陌生账户,顺便看一眼最近的登录日志,这个习惯在安全敏感的环境里很值得养成。
6. 图形化客户端改密码与常见报错速查
不是所有人都习惯敲命令,用 Navicat、MySQL Workbench 这类图形化工具的人其实更多。图形化方式本质上还是把 SQL 包装了一层,理解底层逻辑之后用起来会更踏实。
6.1 Navicat 里怎么改
用 Navicat 连上 MySQL 之后,左侧连接上右键选择“管理用户”或者“编辑用户”,在用户列表里选中目标账户,密码那一栏直接输入新密码,点保存,Navicat 会自动在后台执行ALTER USER或SET PASSWORD语句。改完之后记得右键连接选择“刷新”或者断开重连,否则 Navicat 里缓存的还是旧连接。
有一个坑要提醒:Navicat 保存密码后如果提示2059 - Authentication plugin 'caching_sha2_password' cannot be loaded,说明你的 Navicat 版本比较老(一般是 12 及以前),不支持 8.0 的默认认证插件。解决办法就是用前面 2.1 节提到的语句,把账户的认证插件改成mysql_native_password,再重连即可。这个报错在学生群体里出现频率极高,因为它和密码本身没关系,纯粹是客户端兼容性问题。
6.2 MySQL Workbench 里怎么改
Workbench 是官方出的客户端,兼容性没问题。在左侧导航栏的Users and Privileges页面,选中用户,在Login标签页里可以修改密码,还可以勾选Password Expired强制用户下次登录时改密码。保存后立即生效,不需要重启服务。
Workbench 还有一个实用功能是自带的 SQL 编辑器,前面所有的ALTER USER、SET PASSWORD语句都可以直接在工作区的查询窗口里执行,效果和命令行完全一致。对于刚开始学 MySQL 的朋友,我建议用 Workbench 练习 SQL 语句,同时保留一个命令行窗口备用,因为一旦密码改错连不上图形界面,命令行是最后一根救命稻草。
6.3 常见报错速查表
把改密码过程中最常遇到的报错整理成一张表,遇到问题先在这里对照,多数情况能直接定位:
| 报错信息 | 根本原因 | 解决方向 |
|---|---|---|
| ERROR 1045 Access denied | 密码错误,或账户 host 不匹配 | 确认用的是哪个 host 账户,走第 5 章重置 |
| ERROR 1819 密码不满足策略 | validate_password 组件拦截 | 换强密码,或临时降低 policy 等级 |
| ERROR 1064 语法错误 | 用了 8.0 已移除的 PASSWORD() 函数 | 改用 ALTER USER 字面量写法 |
| ERROR 1290 skip-grant-tables 下不可执行 | 未先刷新权限表 | 先执行 FLUSH PRIVILEGES 再操作 |
| ERROR 1396 操作失败 | 账户不存在,用户名或 host 写错 | 用 SELECT 查 mysql.user 确认真实账户 |
| ERROR 1227 权限不足 | 当前账户没有管理权限 | 换 root 或有 CREATE USER 权限的账户 |
| ERROR 2003 无法连接服务器 | 服务没启动,或端口不对 | 检查服务状态,确认 3306 端口监听 |
| ERROR 2059 认证插件无法加载 | 客户端版本太老 | 将账户插件改为 mysql_native_password |
这张表里的每一条我几乎都遇到过,尤其是 1045、1819、2059 这三条,占据了新手提问的绝大多数。表存在手机备忘录里,以后遇到直接翻,比搜索引擎快。
6.4 几个补充经验
最后分享几个零散但很实用的点。第一,8.0 用--initialize初始化数据目录时,root 会生成一个随机临时密码,打印在错误日志里,路径通常是数据目录\主机名.err,Windows 下可以用记事本打开搜索temporary password。如果是用--initialize-insecure初始化的,root 初始密码为空,直接回车就能进,进去第一件事就是设密码。第二,改密码之后如果应用连不上,先别急着怀疑密码本身,八成是连接池里缓存的旧连接还在用老凭据,重启一下应用服务通常就好了。第三,团队里共用一套测试库时,别把 root 密码设成所有人都知道的那一个,正确的做法是给每个人建独立账户,通过权限控制隔离,root 只用来做管理操作。
我自己这些年在密码这件事上摔过的跤,印象最深的是有一次为了省事把开发库 root 密码设成了纯数字,结果公司推密码策略检查,所有低于 MEDIUM 的账户被批量锁定,我花了一上午在十几台机器上跑重置脚本。从那以后我形成了一个习惯:不管是多临时的环境,密码一律用前缀 + 特殊符号 + 年份的格式,比如Dev@2024,既过了策略检查,又好记。另外还有个小心得,重置密码成功后别急着关终端,先在同一个窗口里用新密码重新登录一次确认无误,再关掉旧的恢复窗口,这个顺序能避免“服务重启后新密码不生效”导致两边都进不去的死循环。数据库这东西,权限和密码是最基础也最容易被忽视的一环,把这几套方法存下来,真到了关键时刻能少慌半小时。