create user后必须立即执行revoke all privileges on .,因新用户虽默认仅含usage权限,但老版本或缓存可能导致隐式元数据访问;再显式授予最小必要库表级权限,并额外revoke select on information_schema.*以阻断信息泄露。

CREATE USER 后必须立刻 REVOKE ALL PRIVILEGES ON *.*
新建用户默认只有 USAGE 权限,看似安全,但老版本或权限缓存可能导致意外可读元数据(比如 information_schema.tables),甚至继承系统库的隐式访问能力。不执行这步,SHOW GRANTS 看起来干净,实际行为可能越权。
- 必须写全:
REVOKE ALL PRIVILEGES ON *.* FROM 'app_user'@'10.20.30.%'—— 漏掉ON *.*语法无效 - 这条语句需要执行者本身有
SUPER或SYSTEM_USER权限,普通 DBA 账号可能无权执行,得提前确认 - MySQL 8.0.29+ 可配合
show_compatibility_56=OFF,但显式REVOKE SELECT ON information_schema.*更可靠
GRANT 必须精确到库和表,禁用 ON *.*
GRANT SELECT ON *.* 是最危险的快捷方式,等同于把整个 MySQL 实例裸露出去。哪怕只错一次,攻击者就能执行 SELECT * FROM mysql.user 或触发 LOAD_FILE()(若未禁用 FILE 权限)。
- 正确写法:
GRANT SELECT, INSERT, UPDATE ON `app_db`.`orders` TO 'app_user'@'10.20.30.%' - 如果真要授权整个库,用
app_db.*,不是*.*;字段级权限虽少用,但对敏感列值得考虑:GRANT SELECT (id, name) ON `app_db`.`users` TO 'app_user'@'10.20.30.%' -
GRANT OPTION绝对禁止——它允许账号把权限转授出去,ORM 自动建表脚本一旦误配,权限就扩散失控
host 匹配失败是连不上最常见的原因
MySQL 把 'user'@'localhost' 和 'user'@'127.0.0.1' 当作两个完全独立账号。本地测试走 Unix socket(localhost),但程序用 TCP 连接(127.0.0.1 或 IP),结果权限没生效。
- 应用部署在远程服务器?用具体 IP 段:
'app_user'@'192.168.10.%',别用%,更别漏写 host - strict 模式下(MySQL 5.7+ 默认),
GRANT前必须先CREATE USER,否则报错ERROR 1410,不是权限问题,是语法拒绝 -
FLUSH PRIVILEGES不需要——MySQL 5.7.6+ 会自动重载,加了反而可能掩盖 host 匹配失败或连接池复用旧连接的问题
只读账号也能 INSERT?那是权限没清干净
MySQL 没有“只读角色”概念。SELECT 权限不会自动排除 INSERT、UPDATE,更危险的是:如果用户已有 USAGE(创建时默认赋予),再 GRANT SELECT 并不会自动撤回其他潜在能力。
- 必须先
REVOKE ALL PRIVILEGES ON *.*,再逐条GRANT SELECT ON `report_db`.* - 查视图需要额外
SHOW VIEW权限,否则报错ERROR 1142 (42000): SELECT command denied to user - 权限改完立刻测试:用该账号连接后执行
SELECT 1;、INSERT INTO ...、SHOW DATABASES;,验证是否真受控
实际配置中,最容易被忽略的是 information_schema 的显式回收和 host 的精确匹配——前者让权限看起来干净,后者决定权限是否真正生效。











