权限报错不会记录在慢日志或错误日志中,因权限校验发生在执行前;慢日志仅记录成功执行且超时的语句,错误日志即使调高 verbosity 也缺乏sql、库表等关键信息;定位需启用 general_log 并配合错误日志时间戳分析。

权限报错本身不会直接写进慢日志或错误日志——MySQL 默认不记录“拒绝访问”这类操作的完整上下文,光开日志没用,得配对启用审计逻辑。
为什么 slow_query_log 对权限报错完全无效
慢日志只捕获执行时间超阈值的 成功执行 的查询,SELECT、UPDATE 等语句在权限校验失败阶段就被拦下了,根本不会进入执行器,更不会计时。所以即使开了 slow_query_log=ON,也看不到 ERROR 1142 (42000): SELECT command denied to user... 这类报错的源头。
-
long_query_time再设成 0 也没用:权限检查早于耗时判定 - 慢日志里出现的语句,说明它已通过权限验证,问题不在权限层
- 想靠慢日志反查谁被拒了,方向错了
log_error_verbosity 和 log_warnings 能记下权限错误吗
MySQL 5.7+ 中,log_error_verbosity=3(默认是 2)会让错误日志包含更多客户端上下文,但权限拒绝仍只记一行简略信息,例如:
2024-04-10T09:23:41.123456Z 123 [Note] Access denied for user 'app_user'@'10.0.1.5' (using password: YES)
它不记录具体执行的 SQL、数据库名、表名,甚至不区分是 SELECT 还是 INSERT —— 信息粒度太粗,定位不到实际出问题的操作。
-
log_warnings=2(已弃用)在 8.0+ 不起作用,别配 - 仅靠错误日志,你只能知道“谁连不上”,不知道“想干什么被拦”
- 如果应用用的是连接池,IP 和线程 ID 还会复用,更难关联到具体业务请求
真正能定位权限操作的唯一办法:启用 general_log 并过滤
general_log 会记录所有到达 server 的语句(含失败的),包括权限检查前的原始请求。虽然它有性能开销,但在排查权限问题时是不可替代的。
- 临时开启:
SET GLOBAL general_log = ON;,日志默认写入hostname.log(路径由general_log_file控制) - 必须同时设
log_output='FILE'(不能只设'TABLE',否则mysql.general_log表可能因权限问题写不进去) - 日志内容示例:
# Time: 2024-04-10T09:25:01.789123Z # User@Host: app_user[app_user] @ 10.0.0.123 [10.0.0.123] Id: 456 # Query: SELECT * FROM orders WHERE status = 'pending'
看到这行,再结合错误日志里同一时刻的 Access denied,就能确认是这条 SELECT 因缺少 SELECT 权限被拒。
- 不要长期开着
general_log:IO 压力大,日志体积暴涨 - 排查完立刻关:
SET GLOBAL general_log = OFF; - 如果用的是 MySQL 8.0.14+,可考虑
audit_log插件,但它需要额外安装、配置策略,且默认也不记录 SQL 文本(需开启audit_log_include_accounts+ 设置策略匹配)
权限报错常被忽略的关键点:代理用户与角色继承
很多权限问题不是用户直连时发生的,而是通过 PROXY 用户或 ROLE 授予的权限没生效。比如:
- 用户
dev@%有SELECT权限,但应用用的是app@localhost并SET ROLE dev—— 如果没显式GRANT ROLE dev TO app@localhost,角色不会自动激活 - 使用
mysql_native_password认证插件时,某些客户端会隐式触发PROXY权限检查,但错误日志里不提示“proxy”字样 -
SHOW GRANTS FOR CURRENT_USER显示的是登录身份的权限,不是CURRENT_ROLE()当前激活角色的权限,容易误判
查权限时,一定用 SELECT * FROM mysql.role_edges 和 mysql.role_routines 看角色实际继承链,别只信 SHOW GRANTS 的第一眼结果。











