mysql中user表host只支持精确匹配或前缀通配(如'%'),不解析'%'为通配符;db表host原生支持%和_通配符,按模式匹配生效;grant中反引号不阻止_被解析为单字符通配符,需转义或避免使用。

MySQL中user表和db表的Host匹配规则完全不同
很多人以为'u1'@'%'能覆盖'u1'@'192.168.1.%',其实完全不是一回事。user表的Host字段只做「前缀通配」或精确匹配:比如'%'匹配任意主机,'192.168.1.%'会被当作字面量字符串处理(不支持%通配),实际等效于'192.168.1.%'这个固定主机名——除非你显式用LIKE查询,否则它不会匹配192.168.1.100。
db表则相反:Host字段原生支持%和_通配符,且是按模式匹配生效的。执行USE mydb;后,MySQL会查db表里所有Db = 'mydb'且Host匹配客户端IP的行,取第一条(顺序由SELECT结果集顺序决定,无隐含优先级)。
- 不要在
user表里写'u1'@'192.168.1.%'指望它匹配子网——它只认字面值,得写成'u1'@'192.168.1.100'或干脆用'u1'@'%' -
db表里Host = '192.168.1.%'是有效的,且会匹配该网段所有IP - 权限最终生效 =
user表全局权限 ∪db表匹配到的库级权限(仅对USE后的库生效)
为什么GRANT ... ON `db_1`.*会意外授权给db01、dba1等库
因为反引号包裹的库名在GRANT语句中**不阻止通配符解析**。MySQL解析时先把`db_1`当作文本,再对其中的_按模式匹配规则解释为“任意单字符”。所以`db_1`.*实际等价于db?1(正则类比),命中范围远超预期。
- 真实风险示例:
GRANT SELECT ON `log_2025`.* TO 'reader'@'%';→ 同时授予log02025、log-2025、logx2025等库权限 - 修复方式:用
ESCAPE转义或改用精确匹配语法:GRANT SELECT ON db\_2025.* TO 'reader'@'%';(注意此处下划线前加反斜杠,且不加反引号) - 更稳妥的做法是避免在库名含
_时用GRANT直接写库名,改用mysql.db表手动INSERT并FLUSH PRIVILEGES;
多个db表记录匹配同一用户+数据库时,谁生效
没有“优先级”概念,只有“第一条命中即止”。MySQL按mysql.db表的物理存储顺序(通常是插入顺序)逐行扫描,一旦找到User、Db、Host三者都匹配的记录,就停止搜索并应用其权限。后续更具体的记录(如Host更精确)完全被忽略。
- 典型陷阱:先执行
GRANT SELECT ON foo.* TO 'u'@'%';,再执行GRANT INSERT ON foo.* TO 'u'@'192.168.1.%';→ 后者不会叠加INSERT权限,因为'u'@'%'已匹配,第二条根本没机会生效 - 验证方法:
SELECT Host, Db, Select_priv, Insert_priv FROM mysql.db WHERE User='u' ORDER BY Host;,观察返回顺序 - 解决办法:删掉宽泛匹配的旧记录,或确保高优先级记录(如具体IP)先插入;也可用
DROP USER+ 重建用户来清空历史残留
权限冲突最隐蔽的来源:SQL SECURITY DEFINER vs INVOKER
存储过程或函数内部执行的SQL,权限检查对象不是调用者,而是定义者(DEFINER)或调用者(INVOKER),这会导致表面授权正常,运行时报错或越权。
- 默认是
SQL SECURITY DEFINER,即用DEFINER账户的权限执行过程体内的语句——哪怕调用者只有EXECUTE权限,也能间接操作DEFINER有权访问的任何表 -
SQL SECURITY INVOKER才真正按调用者权限检查,但要求调用者对过程中涉及的每张表都有对应DML权限,否则报ERROR 1142 - 检查当前过程定义:
SHOW CREATE PROCEDURE proc_name;,重点看DEFINER和SQL SECURITY字段 - 生产环境强烈建议显式声明
SQL SECURITY INVOKER,避免权限绕过
实际调试时,别只信SHOW GRANTS——它只显示GRANT语句授予的权限,不反映db表匹配逻辑、DEFINER上下文或通配符误匹配的真实效果。最可靠的方式是用目标用户连接后,手动USE库、SELECT表、CALL过程,边试边查mysql.user和mysql.db底层数据。











