
MySQL 8.0+ 如何启用 password_history 限制
MySQL 原生支持密码历史策略,但仅限于 8.0.19 及以上版本,且必须使用 caching_sha2_password 或 sha256_password 认证插件(默认插件已满足)。低于该版本或用 mysql_native_password 插件时,password_history 直接无效,不会报错,但也不起作用。
- 确认版本:
SELECT VERSION();,低于8.0.19就别折腾这个参数了 - 检查用户插件:
SELECT user, host, plugin FROM mysql.user WHERE user = 'xxx';,不是caching_sha2_password就先改插件:ALTER USER 'xxx'@'%' IDENTIFIED WITH caching_sha2_password BY 'xxx'; - 全局开启需设置系统变量:
SET PERSIST password_history = 5;(保留最近 5 次密码) -
PERSIST会写入mysqld-auto.cnf,重启不丢失;用SET GLOBAL则仅当前会话有效
password_history 和 password_reuse_interval 必须配对用
只设 password_history 不够——MySQL 要求同时约束“次数”和“时间”,否则新密码仍可能被拒绝或策略形同虚设。比如设了 password_history = 5 却没设 password_reuse_interval,用户换回第 6 次之前的密码时,MySQL 实际按默认的 0 天处理,等于没限制。
- 二者是绑定生效的:修改任一参数,另一参数若为 0(默认值),策略整体不触发
- 推荐组合:
SET PERSIST password_history = 5;+SET PERSIST password_reuse_interval = 90;(单位:天) -
password_reuse_interval = 0表示“不限制时间”,此时即使password_history> 0,也等价于关闭策略 - 注意:这两个变量对已存在的旧密码不追溯,只从启用后首次改密开始记录
ALTER USER 时触发历史校验的真实行为
密码历史检查只发生在显式执行 ALTER USER ... IDENTIFIED BY 或调用 SET PASSWORD 时,且仅校验**当前用户自身的历史密码**。它不会跨用户比对,也不检查其他账户是否用过相同密码。
- 错误现象:
ERROR 3031 (HY000): Cannot use the password from password history.—— 这说明新密码出现在该用户最近password_history次记录中 - 不触发校验的场景:通过
mysqladmin password、GRANT 语句带 IDENTIFIED BY、或 root 修改他人密码时未加IDENTIFIED WITH ... BY显式指定密码 - 查看某用户密码变更记录?MySQL 不提供直接 SQL 查看,只能查
mysql.password_history表(需有 SELECT 权限),但该表只存哈希,不可逆 - 重置策略计数?没有命令清空历史,只能等旧记录自然过期(超过
password_reuse_interval天)或删用户重建
兼容性与运维容易忽略的点
这套机制在主从复制、MGR 或 ProxySQL 环境下基本透明,但有几个边界情况极易踩坑。
- 从库上执行
ALTER USER不会同步密码历史表(mysql.password_history是非事务表,且不复制),所以从库无法做历史校验——意味着如果应用连从库改密,策略失效 - 备份恢复后,
mysql.password_history表内容随备份一起还原,但系统变量(如password_history)不会自动恢复,需手动重设 - Percona Server 或 MariaDB 不支持该原生机制,它们用各自扩展(如 Percona 的
validate_password插件增强版),参数名和行为都不同 - 应用层密码修改失败时,错误码是
3031,不是常见的1045或1820,监控告警若只捕获常见错误码,会漏掉这类策略拦截
真正麻烦的是:密码历史依赖内部表和插件协同,一旦插件切换、版本降级或误删 mysql.password_history 表,MySQL 不报错也不修复,只是静默失效。上线前务必在测试环境走一遍完整改密链路,别只看 SET 成功就认为 OK。











