alter user是锁定或解锁账号最直接方式,执行即生效;需严格匹配host、同时处理account_locked与password_expired双状态,且flush privileges对其无效。

用 ALTER USER 一步锁定或解锁账号
锁定和解锁用户账号最直接的方式就是 ALTER USER,它不依赖缓存刷新、不需重启服务,执行完立刻生效。关键不是“能不能做”,而是“怎么写才不报错”。
常见错误现象:执行 ALTER USER 'u'@'h' ACCOUNT LOCK 报 ERROR 1396 (HY000);或者明明写了 ACCOUNT UNLOCK,登录还是提示 “Account is locked”。
- 主机名必须完全匹配:
'user1'@'localhost'和'user1'@'%'是两个不同账号,查状态时用SELECT user, host, account_locked FROM mysql.user WHERE user = 'user1',别漏掉host列 - MySQL 5.7.6+ 才支持
ACCOUNT LOCK/UNLOCK,旧版本(如 5.6)执行会直接报语法错误 - 锁定操作本身不要求密码重置,但解锁时如果同时存在
password_expired = 'Y',只写ACCOUNT UNLOCK仍无法登录——得搭配IDENTIFIED BY
解锁被锁账号时必须同时处理 password_expired
很多 DBA 解锁后连不上,不是语句写错了,是没意识到 MySQL 8.0+ 的双重状态机制:一个账号可以同时处于 account_locked = 'Y' 和 password_expired = 'Y'。前者拦在认证入口,后者卡在首次登录后的改密环节——而你根本进不去。
所以实际解法不是“先解锁再改密”,而是“一步到位”:
ALTER USER 'appuser'@'10.20.30.%' IDENTIFIED BY 'new_secure_pass' ACCOUNT UNLOCK;
- 这个语句同时清除
account_locked和password_expired字段(password_expired会被重置为'N') - 如果不想改密码,可用原密码重设:
IDENTIFIED BY 'old_pass',但前提是你知道原密码且它没被哈希策略拒绝(比如 MySQL 8.0 默认插件不接受弱密码) - 若用户用的是
caching_sha2_password插件,而客户端不兼容(如老版 Navicat),即使解锁成功也会静默失败——此时要加IDENTIFIED WITH mysql_native_password
连 root 都被锁死?用 --init-file 绕过登录启动
当 root 账号也被锁,又不敢用 --skip-grant-tables(它禁用 ALTER USER,且暴露高危面),最稳妥的自救方式是 --init-file 启动。
操作链很短,但每步都得严丝合缝:
- 停服务:
sudo systemctl stop mysql - 写临时 SQL 文件(如
/tmp/unlock-root.sql),内容仅一行:ALTER USER 'root'@'localhost' ACCOUNT UNLOCK;——不能多空格、不能有注释、不能换行 - 临时启动:
sudo mysqld --user=mysql --init-file=/tmp/unlock-root.sql & - 验证能登录后,立刻杀掉该进程:
sudo kill $(pgrep mysqld),再正常启服务:sudo systemctl start mysql
注意:--init-file 只执行一次初始化 SQL,不会加载配置文件里的其他设置,所以它比 --skip-grant-tables 更可控,也更安全。
为什么 FLUSH PRIVILEGES 对账号锁定完全无效
这是高频误区。很多人看到账号没解锁,第一反应是 FLUSH PRIVILEGES,结果毫无作用。因为 account_locked 是持久化字段,不是权限缓存项。
FLUSH PRIVILEGES 只重新加载 mysql.user 表里的权限列(如 Select_priv、Grant_priv),而 account_locked 和 password_expired 属于账户状态字段,由 ALTER USER 直接更新磁盘并通知服务进程同步内存状态。
- 执行
ALTER USER ... ACCOUNT LOCK后,account_locked字段立即写入表,服务进程内部状态也同步更新,无需额外刷新 - 如果你手动用
UPDATE mysql.user SET account_locked = 'N' WHERE user = 'x'(不推荐!),那才需要FLUSH PRIVILEGES——但这种绕过 DDL 的操作极易破坏数据一致性 - 真正该查的,是登录失败时的错误码:
ERROR 3118 (HY000)或日志里有没有Account is locked字样,而不是盲目刷权限
账户锁定机制本身很简单,但容易卡在 host 匹配、插件兼容、状态叠加这几个细节上。只要查清 account_locked 和 password_expired 的真实值,再严格按语法写 ALTER USER,基本没有解不开的锁。











