先查password_expired状态:若为y且password_lifetime非零,则是密码过期导致access denied;需用alter user重置密码并设password expire never,或全局设default_password_lifetime=0持久生效。

看到 Access denied 但密码没改过,先查 password_expired 状态
应用启动失败、日志里反复出现 Access denied for user,而你确认密码没动过——这大概率是密码过期在伪装成权限错误。别急着重置密码或改权限,先连上 MySQL(如果还能连)执行:
SELECT User, Host, password_expired, password_last_changed, password_lifetime FROM mysql.user WHERE User = 'app_user';
关键看 password_expired 是否为 Y,以及 password_lifetime 是否为非零值。若为 NULL,说明它继承自全局 default_password_lifetime,得再查:SELECT @@global.default_password_lifetime;
能登录但一执行语句就报 ERROR 1820 或 1862,必须用 ALTER USER 改密
这种状态叫“过期待重置”:MySQL 允许你输密码进 shell,但立刻拦截后续所有操作,强制你走标准流程。此时:
- 不能直接 UPDATE
mysql.user表的authentication_string字段——会触发更深层校验失败 - 不能用
SET PASSWORD或UPDATE ... SET Password = PASSWORD(...)——这些在 5.7+ 已被弃用且不更新password_expired标志 - 必须执行:
ALTER USER 'app_user'@'%' IDENTIFIED BY '新密码';(自动清除过期状态) - 如果想一劳永逸,补一句:
ALTER USER 'app_user'@'%' PASSWORD EXPIRE NEVER; -
FLUSH PRIVILEGES不需要——ALTER USER自动生效
根本连不上 MySQL,得跳过权限表启动
mysql -u root -p 输完密码直接退出、GUI 工具报错后断开、Java 应用卡在 HikariCP 初始化——说明客户端根本不支持 MySQL 的“过期密码交互式重置”协议(比如旧版 CLI、pymysql、Druid)。这时必须绕过认证层:
- Linux/macOS:停服务后用
sudo mysqld_safe --skip-grant-tables --skip-networking &启动(--skip-networking防止远程未授权访问) - Windows:在
my.ini的[mysqld]下加skip-grant-tables,然后重启服务 - 启动后直接
mysql -u root(不加-p)即可进库 - 进库后先
USE mysql;,再执行ALTER USER 'app_user'@'%' IDENTIFIED BY '新密码'; - 必须执行
FLUSH PRIVILEGES;(因为--skip-grant-tables下ALTER USER不自动刷新权限缓存) - 删掉
skip-grant-tables或杀掉mysqld_safe进程,再正常启服务
永久禁用过期策略,别只修当前用户
只给当前用户加 PASSWORD EXPIRE NEVER 是治标。生产环境推荐两步走:
- 对已有用户批量处理:
SELECT CONCAT('ALTER USER ''', User, '''@''', Host, ''' PASSWORD EXPIRE NEVER;') FROM mysql.user WHERE password_expired = 'Y';,复制结果执行 - 设全局默认永不过期:
SET PERSIST default_password_lifetime = 0;(写入mysqld-auto.cnf,重启不失效) - 注意:
SET GLOBAL default_password_lifetime = 0重启后失效,别用 -
password_lifetime = 0和NULL行为完全不同——前者明确永不过期,后者仍受全局值影响
最易忽略的一点:连接池(如 HikariCP、Druid、SQLAlchemy + pymysql)完全不响应 MySQL 的“请改密”握手指令,所以不能指望应用自己恢复——只要用户过期,连接池初始化就必然失败,必须 DBA 主动干预。











