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的必要性——这三个点最容易在排查时被跳过,结果花两小时查权限,实际是防火墙没开或者配置文件没重载。
