MySQL 8.0.19+ 原生支持密码重用限制,通过 SET PERSIST password_history = N 和 password_reuse_interval = D 配置,ALTER USER 时自动校验 mysql.password_history 只读表;此前版本须由应用层显式校验并记录哈希。

MySQL 触发器无法直接实现密码历史排重
触发器在 MySQL 中不能用于拦截或校验 ALTER USER 语句中的新密码是否重复——因为 ALTER USER 是 DDL 操作,不经过用户表的 UPDATE/INSERT 触发器;而 mysql.user 表受系统保护,你无法在其上创建触发器,更不能用 BEFORE/AFTER 触发器修改认证逻辑。
password_history 参数才是唯一原生支持路径
MySQL 8.0.19+ 提供了真正起作用的内置机制:password_history 和 password_reuse_interval。它们不是靠触发器,而是由服务端在执行 ALTER USER ... IDENTIFIED BY 时自动比对 mysql.password_history 表(只读)中已存的哈希记录。
-
SET PERSIST password_history = 5;:要求新密码不能与最近 5 次密码相同 -
SET PERSIST password_reuse_interval = 180;:要求新密码不能是过去 180 天内任一旧密码 - 二者同时生效,满足任一条件即报错
ERROR 3638 - 必须用
ALTER USER,SET PASSWORD不触发校验 -
mysql.password_history表不可读写、不可删、不可手动插入——它是内部只读表
为什么“自己建表 + 触发器”方案行不通
常见错误思路是:在业务库建 password_history 表,再给 users 表加触发器,在密码字段更新时 INSERT 记录。这看似合理,但存在根本缺陷:
- 触发器只能响应
UPDATE users SET password = ?,而真实生产环境几乎不用这种方式改密(绕过权限系统、不兼容 caching_sha2_password 插件) -
ALTER USER不会触发任何用户定义的触发器 - 即使你硬改
users表,也无法同步更新mysql.user的authentication_string字段,导致认证失败 - 无法保证事务一致性:密码改成功了,但历史表 INSERT 失败,就会漏记
- 应用层若未主动校验,该表形同虚设
真正可控的排重必须由应用层主导
如果你需要跨版本兼容(如低于 8.0.19)、或需对接外部审计系统、或要自定义比对逻辑(比如排除某几类弱密码哈希),唯一可靠方式是把校验和记录完全交给应用层:
- 改密前,应用先查自己维护的密码哈希表(建议存
SHA2('plain', 256),别碰authentication_string) - 确认不重复后,再执行
ALTER USER 'u'@'h' IDENTIFIED BY 'newpass' - 立即执行
INSERT INTO myapp.password_history (user, host, password_hash, changed_at) VALUES (?, ?, SHA2(?, 256), NOW()) - 这两步必须强顺序保障(同一事务或带重试的幂等写入)
- 注意权限:普通用户需被授予对
myapp.password_history的INSERT和SELECT权限
这个模式下,触发器毫无用武之地;所谓“利用触发器实现排重”,本质上是个伪命题——MySQL 不开放这个能力,强行套用只会引入不一致和维护黑洞。


















