应查performance_schema.accounts表获取last_seen时间识别长期未登录账号,或结合mysql.user中account_locked='y'及全权限为'n'筛选僵尸账户;删前须验证权限、配置引用及实例只读状态。

查出长期未登录的账号(MySQL 8.0+)
MySQL 8.0 开始,mysql.user 表里多了 password_last_changed 和 account_locked 字段,但不记录登录时间。真正能反映“是否活跃”的是 performance_schema.accounts 表——它会统计每个账号的连接次数和最后活跃时间,前提是 performance_schema 已启用且相关消费者开启。
执行前先确认:
SELECT VARIABLE_VALUE FROM performance_schema.setup_consumers WHERE NAME = 'events_statements_current';如果返回
NO,需手动启用(重启不持久,建议写入配置文件):
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'accounts';
然后查最近 90 天没连过的账号(排除系统账号):
SELECT USER, HOST FROM performance_schema.accounts
WHERE USER NOT IN ('mysql.session', 'mysql.sys', 'root')
AND LAST_SEEN <p>注意:<code>LAST_SEEN</code> 是最后一次成功认证时间,不是 SQL 执行时间;新创建但从未登录的账号不会出现在该表中。</p><h3>识别已禁用但残留的账号(所有版本通用)</h3><p>有些账号被 <code>DROP USER</code> 删除失败,或只执行了 <code>REVOKE</code> + <code>ALTER USER ... ACCOUNT LOCK</code>,但用户记录仍留在 <code>mysql.user</code> 中。这类账号通常满足:</p>
account_locked = 'Y'- 所有权限字段(如
Select_priv,Insert_priv等)全为N -
authentication_string为空或为占位符(如<em>0</em>、1开头的无效哈希)
检查语句:
SELECT User, Host FROM mysql.user WHERE account_locked = 'Y' AND Select_priv = 'N' AND Insert_priv = 'N' AND Update_priv = 'N' AND Delete_priv = 'N' AND Create_priv = 'N' AND Drop_priv = 'N' AND authentication_string REGEXP '^\*0$|^\*1$|^$';
这类账号已无法登录,但占着元数据空间,也应清理。
安全删除前必须验证的三件事
直接 DROP USER 'u'@'h' 风险很高,删错会导致业务中断。执行前务必确认:
-
SHOW GRANTS FOR 'u'@'h';返回空或只有USAGE权限(说明没实际权限) - 检查应用配置文件、连接池配置、定时任务脚本里是否硬编码了该账号(搜索
'u'@'h'或用户名) - 在从库上运行
SELECT @@read_only;,确保不是只读实例——否则DROP USER会报错ERROR 1290 (HY000)
另外,MySQL 5.7 不支持一次性删多个账号,必须逐个执行;而 8.0+ 支持 DROP USER 'a'@'h', 'b'@'h';,但中间不能有空格逗号,格式错误会直接报语法错。
自动清理脚本要避开的坑
有人写存储过程遍历 mysql.user 自动删账号,这很危险。常见问题包括:
- 忘记加
AND User != 'root',误删 root - 用
LIKE '%test%'匹配用户名,结果把reporting_test这类生产账号也匹配进去了 - 没判断
Host是'%'还是具体 IP,导致删掉通配符账号后,同名但更精确的账号(如'app'@'10.0.1.%')反而失效 - 日志只写 “deleted user X”,没记录
Host和执行时间,出事后无法追溯
真要自动化,建议用外部脚本(Python/Shell),先生成待删清单到文件,人工确认后再执行,且每次只处理一个账号,配合 mysql --defaults-file 使用专用权限账号操作。
清理这件事,宁可慢一点,也不能靠“看起来像不用”就删。很多所谓“过期账号”,其实是某台离线服务器、某个归档脚本、甚至监控探针在用。











