MySQL无法直接查长期未用账号权限,因不记录登录日志;需结合account_locked、password_last_changed、ProxySQL连接统计等间接判断;删前须确认无活跃连接、无隐性依赖、无权限残留。

查哪些账号权限长期没用过
MySQL 本身不记录账号的登录时间或权限使用痕迹,mysql.user 表只存授权状态,不存行为日志。所以“过期权限”不是指时间戳超期,而是指账号创建后从未登录、或最后一次登录距今超过 N 天(需依赖外部审计日志或代理层日志)。
如果没开 general_log 或没接入 MySQL 审计插件(如 audit_log),就只能靠间接方式判断:
-
SELECT user, host FROM mysql.user WHERE account_locked = 'Y' —— 锁定账号大概率已废弃
-
SELECT user, host, password_last_changed FROM mysql.user WHERE password_last_changed < DATE_SUB(NOW(), INTERVAL 365 DAY) —— 密码一年没换,且无其他活跃迹象(如近期连接)
- 对接了 ProxySQL 或 MaxScale 的,可查其连接统计表,过滤
total_connections = 0 的 backend 用户
SELECT user, host FROM mysql.user WHERE account_locked = 'Y' —— 锁定账号大概率已废弃 SELECT user, host, password_last_changed FROM mysql.user WHERE password_last_changed < DATE_SUB(NOW(), INTERVAL 365 DAY) —— 密码一年没换,且无其他活跃迹象(如近期连接) total_connections = 0 的 backend 用户 注意:password_last_changed 在 MySQL 5.7+ 才有;8.0 中若用 caching_sha2_password 且未显式改密,该字段可能为 NULL,不能直接过滤。
删权限前必须确认的三件事
直接 DROP USER 或 REVOKE 可能导致服务中断,尤其当账号被应用硬编码、或用于备份脚本、监控采集等隐性场景。
- 检查账号是否在
mysql.db、mysql.tables_priv 等库表级权限表中留有残留记录(REVOKE 不自动清理这些,需手动 DELETE)
- 查
SHOW PROCESSLIST 和 performance_schema.threads,确认当前无该用户活跃连接
- 在低峰期执行,并提前在测试库验证脚本逻辑,避免误删
'root'@'localhost' 这类关键账号
mysql.db、mysql.tables_priv 等库表级权限表中留有残留记录(REVOKE 不自动清理这些,需手动 DELETE) SHOW PROCESSLIST 和 performance_schema.threads,确认当前无该用户活跃连接 'root'@'localhost' 这类关键账号 删之前建议先导出:
SELECT CONCAT('REVOKE ALL PRIVILEGES ON *.* FROM ''',user,'''@''',host,''';') FROM mysql.user WHERE user NOT IN ('root','mysql.sys') AND account_locked = 'Y';用存储过程自动清理锁定账号(MySQL 8.0+)
MySQL 不支持在事件(EVENT)里直接执行 DROP USER,但可以用存储过程封装 + 定时调用。注意:过程需 DEFINER 有 CREATE ROUTINE 和 DROP USER 权限。
DELIMITER $$
CREATE PROCEDURE cleanup_locked_users()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE u VARCHAR(32);
DECLARE h VARCHAR(255);
DECLARE cur CURSOR FOR
SELECT user, host FROM mysql.user WHERE account_locked = 'Y' AND user NOT IN ('root', 'mysql.sys');
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
<p>OPEN cur;
read_loop: LOOP
FETCH cur INTO u, h;
IF done THEN
LEAVE read_loop;
END IF;
SET @sql = CONCAT('DROP USER ''', u, '''@''', h, '''');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur;
END$$
DELIMITER ;
调用:CALL cleanup_locked_users()
⚠️ 坑点:MySQL 8.0.16+ 默认开启 partial_revokes,若账号有部分权限被 REVOKE 过,DROP USER 会失败,需先 REVOKE 再删。
为什么不用 EVENT 自动跑清理脚本
虽然可以建 EVENT 定期调用上面的过程,但生产环境不推荐:
-
EVENT 默认以 DEFINER 身份运行,若定义者账号权限被回收或密码过期,事件静默失效,无告警
- 清理操作不可逆,自动执行缺乏人工确认环节,出错即雪崩
- MySQL 的
event_scheduler 本身可能被意外关闭(比如配置热加载后未重载)
EVENT 默认以 DEFINER 身份运行,若定义者账号权限被回收或密码过期,事件静默失效,无告警 event_scheduler 本身可能被意外关闭(比如配置热加载后未重载) 更稳妥的做法是:写个 shell 脚本 + mysql -e "CALL cleanup_locked_users()",用 crond 控制频率,并把输出重定向到日志,配合监控检查日志更新时间和错误行数。
清理这事,核心不在“怎么删”,而在于“删谁”——没有登录日志,所有判断都是推测。宁可多留一周,也别删错一个正在用的监控账号。










