MySQL中“长期未使用”不等于LAST_SEEN为空,因该字段仅记录成功认证账号;FAILED_LOGIN_ATTEMPTS用于防暴力破解,需validate_password插件启用、caching_sha2_password认证插件及PASSWORD_LOCK_TIME成对配置才生效。

长期未使用 ≠ LAST_SEEN 为空
MySQL 8.0+ 的 performance_schema.accounts 表里确实有 LAST_SEEN 字段,但它只记录**成功认证过的账号**;那些创建后从没登录过、或只连过一次就再没动过的账号,根本不会出现在这张表里。直接查 LAST_SEEN 会漏掉大量“静默僵尸账号”。
- 真正能覆盖全量账号的判断基线,只能回到
mysql.user表:结合created时间 +password_expired = 'Y'+ 应用层访问日志交叉验证 - 如果没开
general_log或审计插件,LAST_SEEN就是“已登录但久不活跃”的唯一依据,不能当“是否用过”的判据 -
password_last_changed不是登录时间,改密和登录完全无关,别拿它当活跃指标
锁定操作必须用 ALTER USER,不能 UPDATE mysql.user
直接 UPDATE mysql.user SET account_locked = 'Y' 看似快,但会导致权限缓存不一致、后续连接异常,甚至让管理员自己都连不上——MySQL 8.0+ 的账户状态由内部元数据系统管理,绕过 ALTER USER 就是破坏一致性。
- 正确命令是:
ALTER USER 'user'@'host' ACCOUNT LOCK;(注意必须带完整 host,'user'@'%'和'user'@'192.168.1.100'是不同账号) - 批量生成语句时,
Host值含通配符(如%)无需转义,但需确保 SQL 拼接时单引号闭合正确 - 执行前务必备份:
mysqldump -u root -p --no-data mysql user > user_meta_backup.sql
锁定后应用报错不是 ERROR 1045,而是 ERROR 3118 或 ERROR 3956
很多 DBA 看到 ERROR 1045 (28000): Access denied 就去重置密码,结果发现密码没错——这大概率是账号已被锁,但错误码没被应用层捕获或日志过滤掉了。
- 真正由
ACCOUNT LOCK触发的错误是:ERROR 3118 (HY000): Access denied for user ... Account is locked(MySQL 5.7.16+)或ERROR 3956 (HY000): Account is locked(MySQL 8.0.19+) - 连接池(如 HikariCP、Druid)会缓存失败连接,重启服务或手动触发连接重建才能清掉错误状态
- Laravel、Django 等 ORM 默认不抛出锁定异常,需显式配置
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION才能捕获
自动策略 FAILED_LOGIN_ATTEMPTS 不等于“长期未用”锁定
FAILED_LOGIN_ATTEMPTS 是防暴力破解的机制,不是闲置清理工具。它只在用户**主动尝试登录且输错密码**时计数,对安静躺着 2 年没动过的账号完全无感。
- 该策略生效前提极多:必须启用
validate_password插件、认证插件必须是caching_sha2_password或mysql_native_password、FAILED_LOGIN_ATTEMPTS和PASSWORD_LOCK_TIME必须同时设置 - 设了
FAILED_LOGIN_ATTEMPTS 3却没锁住?先查SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'validate_password'; - 想靠它清理僵尸账号,纯属误用——它管的是“输错太多次”,不是“多久没来过”


















