MySQL用户host字段是访问控制核心,决定IP匹配范围;同一用户名配不同host视为独立账号,需分别授权;GRANT必须显式指定host,漏写将默认为'%';防火墙、bind-address等底层配置优先于MySQL权限生效;DROP USER或REVOKE后须执行FLUSH PRIVILEGES刷新缓存。

MySQL 用户账号的 host 字段决定 IP 访问范围
MySQL 权限系统里,user 表中的 host 列不是辅助字段,而是访问控制的核心判断依据。同一个用户名(如 'app'),搭配不同 host 值,会被视为完全独立的账号:'app'@'192.168.1.100' 和 'app'@'192.168.1.%' 互不影响,权限需分别授予。
常见误区是以为只创建 'app'@'%' 就能覆盖所有场景——这会开放任意 IP 连接,且无法对内网、外网做差异化策略。
-
'app'@'localhost':仅允许本机 Unix socket 或 127.0.0.1 TCP 连接,不走网络栈 -
'app'@'192.168.1.5':精确匹配单个 IPv4 地址 -
'app'@'192.168.1.%':匹配该 C 类网段全部 IPv4(注意:不匹配 IPv6) -
'app'@'%.example.com':支持域名通配,但依赖 MySQL 的 DNS 反查(不推荐,性能差且不可靠)
GRANT 语句必须显式指定 host,不能省略
执行 GRANT 时若漏写 host,MySQL 默认补为 '%',这是高危默认行为。例如:GRANT SELECT ON db.* TO 'report' IDENTIFIED BY 'pwd'; 实际创建的是 'report'@'%',而非你预期的本地账号。
正确写法必须带引号并明确 host:
GRANT SELECT ON sales.* TO 'report'@'10.0.20.0/24' IDENTIFIED BY 'R3p0rt!2024'; GRANT INSERT, UPDATE ON orders.* TO 'api'@'172.16.5.10' IDENTIFIED BY 'ApiPass#1';
注意:'10.0.20.0/24' 是 MySQL 8.0+ 支持的 CIDR 语法;低版本只能用 '10.0.20.%' 模拟,但无法精确排除 10.0.20.255 这类地址。
防火墙与 bind-address 会先于 MySQL 权限生效
MySQL 的 host 匹配只发生在 TCP 连接已建立之后。如果连接根本连不上,问题大概率出在更底层:
-
bind-address在my.cnf中设为127.0.0.1,则 MySQL 只监听本地回环,外部 IP 无法建立 TCP 连接 - Linux
iptables或nftables未放行 3306 端口,SYN 包被丢弃,客户端超时 - 云平台安全组(如 AWS Security Group、阿里云 ECS 安全组)未配置入向规则,流量在宿主机前就被拦截
验证顺序建议:先用 telnet 192.168.1.10 3306 看端口是否可达;再确认 SELECT user, host FROM mysql.user; 中对应账号是否存在且 host 匹配;最后检查 SHOW GRANTS FOR 'user'@'host'; 是否包含所需权限。
REVOKE 和 DROP USER 不清理残留权限缓存
执行 DROP USER 'old'@'192.168.5.%'; 后,如果之前存在 'old'@'%' 账号,它仍有效;同理,REVOKE 不会自动刷新权限表缓存。
必须手动触发重载:
FLUSH PRIVILEGES;
否则新授权或删除操作不会立即生效。这个命令虽快,但它是全局锁,高并发写入期间可能短暂阻塞其他 DDL 操作。生产环境建议在低峰期执行,或改用 MySQL 8.0+ 的 ALTER USER ... REQUIRE NONE 配合证书限制,减少对 FLUSH PRIVILEGES 的依赖。
CIDR 写法、bind-address 配置、FLUSH PRIVILEGES 的必要性——这三个点最容易在排查时被跳过,结果花两小时查权限,实际是防火墙没开或者配置文件没重载。










