不安全,且极易误授权限;mysql将未转义的下划线\_视为单字符通配符,导致grant语句意外匹配多个数据库,必须用反斜杠\转义并以反引号包裹库名。

MySQL库名里用下划线_会被当通配符匹配
直接说结论:**不安全,且极易误授权限**。MySQL在解析数据库名时,会把未转义的_当作“单字符通配符”,和LIKE语句里的行为一致——这不是bug,是设计如此,但绝大多数人不知道。
比如你执行:GRANT SELECT ON `db_1`.* TO 'appuser'@'%';
你以为只给了db_1库的权限,实际上db01、dba1、db-1、db?1……所有第二个字符是任意单字符、结尾是1的库,全都被匹配到了。
- 一个
_可能扩大权限至 30+ 个意外库(按常见命名字符集估算) - 两个
_(如db_1_2)→ 匹配数≈30×30=900,权限失控风险指数级上升 - 若这些库中混有测试库、备份库或含敏感字段的旧业务库,后果就是越权读取
正确写法:必须用反斜杠\转义下划线
MySQL支持用\对_和%做字面量转义,这是唯一可靠的方式。
正确授权示例:GRANT SELECT ON `db\_1`.* TO 'appuser'@'%';
注意:反斜杠在SQL字符串中本身需被MySQL解析,所以命令行或脚本中要写成两个反斜杠\(例如Shell中),但在MySQL客户端内直接执行,一个\即可生效。
- 必须用反引号
`包裹库名,否则转义无效(无引号时db\_1会被当成非法标识符报错) - 不要依赖“我库名只用字母数字”来侥幸——只要命名规范允许
_,就存在被误匹配的可能 - 自动化部署脚本中务必检查所有
GRANT语句,grep`.*_.*`+ 手动验证是否已转义
权限匹配顺序决定“谁说了算”,不是越具体越优先
MySQL权限验证是**从上到下、先到先得**,不是“最精确匹配胜出”。它依次检查:mysql.user(全局)→ mysql.db(库级)→ mysql.tables_priv(表级)→ mysql.columns_priv(列级)。一旦某一层找到匹配项(哪怕只是'%'@'%'这种宽泛主机),就立即停止向下查。
- 这意味着:如果用户同时有
SELECT ON *.*(全局)和SELECT ON `db\_1`.*(库级),前者会拦截所有请求,后者完全不生效 - 撤销权限时,
REVOKE SELECT ON `db\_1`.*不会影响全局权限;必须显式REVOKE SELECT ON *.*才能真正收权 - 用
SHOW GRANTS FOR 'user'@'host';看到的权限列表,是MySQL当前实际生效的组合结果,不是“所有授予过的权限”
生产环境建议:禁用通配符式授权,改用角色+白名单
靠人工盯住每个_是否转义,长期来看不可靠。更稳妥的做法是绕过通配符问题本身。
- 用
CREATE ROLE(MySQL 8.0+)定义角色,如app_reader,只授明确库名:GRANT SELECT ON `db_1`.* TO app_reader; - 应用账户不直接受权,而是
GRANT app_reader TO 'appuser'@'%';,后续增删库只需调整角色,不碰用户 - 旧版本(5.7)可用脚本批量生成严格库名授权语句,避免手写;同时禁止在
GRANT中出现任何_或%字符 - CI/CD流水线中加入SQL语法扫描,对含
GRANT.*ON.*`.*_.*`但无\_的语句直接阻断发布
真正麻烦的从来不是记不住转义规则,而是权限条目散落在不同时间、不同人、不同脚本里——等发现db_prod被db_test的通配授权意外覆盖时,往往已经晚了。










