这里有一个非常关键、却经常被忽视的点:如果你想强制 MySQL 用户在首次或下次登录后修改密码,必须使用 ALTER USER ... PASSWORD EXPIRE,单独执行 SET PASSWORD 是无效的,而且这种设置也只会在用户下一次重新登录时才会生效。原因很清楚,SET PASSWORD 只是更新 authentication_string 字段,并不会同步修改 password_expired;而 MySQL 是否要求用户强制改密,正是由 password_expired 这项元数据决定的。

必须使用 ALTER USER ... PASSWORD EXPIRE,SET PASSWORD 不生效,并且仅对新的登录连接生效。
为什么 SET PASSWORD 不能触发 MySQL 强制修改密码
SET PASSWORD FOR 'u'@'h' = 'xxx' 实际修改的只有 authentication_string 这一列,password_expired 并不会随之更新。简单来说,MySQL 判断是否需要用户登录后修改密码,看的并不是密码是否刚刚被重置,而是账户元数据中的标记状态:只要 password_expired 仍然是 'N',即使你刚执行了密码重设,用户登录后依然可以直接执行 SELECT 等操作。
- 错误写法:
SET PASSWORD FOR 'alice'@'localhost' = 'new123'; ALTER USER 'alice'@'localhost' PASSWORD EXPIRE;—— 分成两步执行,第二步非常容易遗漏 - 正确写法(推荐一步完成):
ALTER USER 'alice'@'localhost' IDENTIFIED BY 'new123' PASSWORD EXPIRE; - 如果只想把密码状态标记为过期而不修改当前密码:
ALTER USER 'alice'@'localhost' PASSWORD EXPIRE;,不要与IDENTIFIED BY混合使用
执行前必须确认的三个前提条件
命令执行成功并不等于配置已经真正生效。以下任意一个条件不满足,PASSWORD EXPIRE 都不会达到预期效果:
- 目标用户必须已经存在:
SELECT user, host FROM mysql.user WHERE user = 'alice'; - 执行该命令的账户必须具备
ALTER USER权限,而不是仅有UPDATE权限 SELECT @@default_password_lifetime;结果不能为0;如果为0,表示 MySQL 全局关闭了密码过期机制,所有PASSWORD EXPIRE设置都会失效
用户登录后没有出现 ERROR 1820?重点排查这三项
如果已经设置了 PASSWORD EXPIRE,但用户登录后仍然可以正常执行 SELECT,通常大概率是以下原因:
- 服务器在全局范围内关闭了密码过期策略:
SELECT @@default_password_lifetime;返回0 - 客户端建立连接后会自动执行
SET NAMES utf8mb4等初始化语句,但还没有发送第一条非SET类型的 SQL——ERROR 1820通常要到首条非SET语句执行时才会触发 - 该用户在设置前已经存在活跃连接:密码过期策略只影响新的登录会话,当前已建立的连接不会被立即中断
PASSWORD EXPIRE NOW 与 INTERVAL N DAY 的区别
这两种写法都会把 password_expired 设置为 'Y',但后续判断逻辑和生效方式并不完全相同:
PASSWORD EXPIRE NOW:用户下次登录认证通过后,在首次执行非SET语句时立即报错PASSWORD EXPIRE INTERVAL 60 DAY:从password_last_changed的时间开始计算 60 天;如果当前时间已经超过该期限,实际效果就等同于NOW- 如果要判断账户是否真的已经过期,不能只查看
password_expired字段:SELECT user, host, password_last_changed, TIMESTAMPADD(DAY, @@default_password_lifetime, password_last_changed) AS expires_at FROM mysql.user WHERE user = 'alice';
还有一个最容易被忽略的细节是:密码过期状态会在服务端认证完成后、授权执行前直接返回错误,根本不会进入正常的 SQL 执行流程——因此既不能通过 mysql -u alice -p 这种交互式方式绕过,也不能在连接池场景中采用“先连接、再处理”的方式规避。
