应过滤 mysql.* 内置账号和空用户名,用 SELECT User, Host FROM mysql.user WHERE User NOT LIKE 'mysql.%' AND User != '';MySQL 8.0+ 需结合 account_locked 和认证插件判断登录状态,并通过登录时间、权限范围、角色绑定三方面交叉验证账号是否冗余。

查出所有非系统账号,避开 mysql.* 表误操作
直接 SELECT 所有用户时容易混入 mysql.session、mysql.sys 这类内置管理账号,它们不是你创建的,删了会导致系统功能异常。正确做法是过滤掉主机为 'localhost' 且用户名以 'mysql.' 开头的记录,同时排除空用户名(部分旧版本残留)。
- 用
SELECT User, Host FROM mysql.user WHERE User NOT LIKE 'mysql.%' AND User != '';列出人工创建的账号 - 别依赖
SHOW GRANTS单独查——它不显示 host,而'user'@'10.%.%.%'和'user'@'%'是两个权限主体 - 注意:MySQL 8.0+ 的
mysql.user表结构变了,plugin字段不再决定是否可登录,得看account_locked和认证插件是否支持
判断账号是否真冗余:看登录时间 + 权限粒度
只看“没被显式授权”不等于闲置。有些账号靠角色继承权限,或只在特定应用连接池里用,一删就断连。关键得交叉验证三件事:最近登录行为、实际授予的权限范围、是否绑定角色。
- 查最近登录:
SELECT user, host, last_seen FROM performance_schema.accounts WHERE user != '' AND last_seen (需开启 <code>performance_schema且setup_actors已启用) - 检查权限是否全为
USAGE:SELECT * FROM mysql.role_edges WHERE TO_HOST = '%';配合SELECT * FROM mysql.role_edges WHERE FROM_USER = 'xxx';看角色链路 - 避免误删仅拥有
SELECT权限但用于监控脚本的账号——它们通常不执行写操作,但删除后报警会失效
安全删除账号前必须停服 + 检查连接池配置
MySQL 不会主动踢掉已建立的连接,删账号后老连接还能继续跑,直到超时或主动断开,这期间可能产生脏数据或权限绕过(比如用旧连接执行高危语句)。真正安全的清理必须和应用侧协同。
- 先在应用端把对应账号从连接池配置(如
druid.filters、spring.datasource.hikari.connection-test-query)里注释掉,重启服务 - 再用
SELECT ID, USER, HOST, COMMAND, TIME FROM information_schema.PROCESSLIST WHERE USER = 'xxx';确认无活跃连接 - 最后执行
DROP USER 'xxx'@'yyy';;不要用DELETE FROM mysql.user—— 这样不会清理mysql.db或mysql.tables_priv里的残留权限行
自动化审计脚本要避开 root@localhost 的权限陷阱
用 shell 脚本定期跑权限检查时,如果连接账户是 root@localhost,默认走 socket 认证,但脚本里若写成 -h 127.0.0.1 就变成 TCP 连接,可能因 skip-networking 或防火墙失败;更麻烦的是,某些部署里 root@localhost 并没有 SELECT 权限查 mysql.user(靠 SUPER 绕过),而普通审计账号需要显式授权。
- 脚本中统一用
--socket=/var/run/mysqld/mysqld.sock避免网络层干扰 - 审计账号至少要有:
SELECT ON mysql.user、SELECT ON mysql.db、SELECT ON performance_schema.accounts - 别在脚本里硬编码密码,用
~/.my.cnf配置段 +chmod 600,否则审计日志可能泄露凭据
真正难的不是删账号,是确认这个账号背后有没有某个凌晨三点跑的备份脚本、某个嵌在硬件设备固件里的静态配置、或者某个被遗忘的 Docker Compose 文件里的 environment 变量——这些地方改漏一个,清理就变成事故。


















