mysql中_是单字符通配符,未转义的db_1会匹配db01、dba1等30+库;必须用反引号包裹并双重转义为db\_1才安全,否则权限失控。

MySQL里_不是下划线,是单字符通配符
你写的GRANT SELECT ON `db_1`.*,MySQL根本不会把它当“db_1”这个库名来处理。只要没转义,_就等价于LIKE里的下划线——匹配任意一个字符。所以db_1实际会匹配db01、dba1、db-1、db?1……甚至db 1(空格也算一个字符)。这不是bug,是MySQL解析器的既定行为。
常见错误现象:
- 授权后发现用户能查
test_backup库,但你根本没授过这个库 - 运维脚本批量建库(
app_v1、app_v2…),结果app_v%意外匹配了app_x1、app_z2 - 用
SHOW DATABASES看到一堆陌生库,确认是权限误放导致的越权可见
GRANT语句里_必须双重转义才安全
正确写法只有一种:GRANT SELECT ON `db_1`.* TO 'u'@'h';。注意两点:反引号`不能少,反斜杠不能漏。
为什么必须这样?
- 没反引号:
db_1会被当成非法标识符,直接报错ERROR 1064 - 只有反引号没反斜杠:
`db_1`还是走通配逻辑,和不加引号效果一样 - 在Shell脚本或Ansible中执行时,得写成
db\_1——因为Shell先吃掉一层反斜杠,MySQL才能收到真正的_ - 别信“我们库名不用下划线”——只要命名规范允许
_,就存在被匹配风险;自动化部署脚本里grep -n '_'必须成为上线前必检项
通配符权限不叠加,而是“先命中就停止”
MySQL查权限不是取并集,而是从mysql.user→mysql.db→mysql.tables_priv顺序扫描,遇到第一条匹配记录就立刻返回结果。这意味着:
- 如果用户在
mysql.user里有Select_priv='Y',哪怕mysql.db里对db_1设了'N',也完全无效 - 同一用户在
mysql.db里有两条记录:Db='prod'(Select_priv='N')和Db='%'(Select_priv='Y'),实际权限是Y——因为%字典序靠前,先被匹配 -
REVOKE SELECT ON `db_1`.*不会影响全局权限;要真正收权,必须显式REVOKE SELECT ON *.*或删掉mysql.user里的对应行
比转义更靠谱的做法:绕过通配符本身
人工盯住每个_是否转义,长期来看不可靠。生产环境建议直接放弃通配符式授权。
实操路径:
- MySQL 8.0+:用
CREATE ROLE 'app_reader',然后GRANT SELECT ON `db_1`.* TO 'app_reader'(这里_在角色定义里不参与匹配,是纯字面量) - 所有应用账号不再直接受权,而是
GRANT 'app_reader' TO 'app_user'@'10.20.%',再SET DEFAULT ROLE 'app_reader' TO 'app_user'@'10.20.%' - 禁用
GRANT OPTION,防止用户自行扩散权限;检查SELECT User,Host,Grant_priv FROM mysql.user WHERE Grant_priv='Y',非DBA账号必须为N
真正麻烦的从来不是怎么写对一条GRANT,而是权限一旦写错,它可能静默生效几个月,直到某次误操作或渗透才暴露出来。











