最可靠方式是用create user创建'用户'@'ip段%'格式账号,如'app'@'192.168.1.%',并必须删除冗余的'@'%'账号,授权时显式指定完整host,再配合防火墙与bind-address加固。

直接用 CREATE USER 绑定内网 IP 段最可靠
MySQL 没有全局 IP 白名单开关,唯一可控的方式是把用户账号写成 'user'@'192.168.1.%' 这种形式。它不是注释,而是强制匹配条件:MySQL 启动后会按 user@host 二元组查表,只允许该 host 模式匹配的连接进来。
注意:% 是通配符,不是 CIDR;'user'@'192.168.1.0/24' 会直接报错 ERROR 1290 (HY000),MySQL 根本不识别斜杠写法。
- 单个 IP:用
'app'@'10.0.5.22' - C 段网段(如 192.168.1.0–192.168.1.255):用
'app'@'192.168.1.%' - B 段粗略匹配(如 10.0.0.0–10.0.255.255):用
'app'@'10.0.%.%',但会误收10.0.999.1这类非法地址(MySQL 不校验) - 别漏掉
'app'@'localhost'和'app'@'127.0.0.1'—— 它们是两个账号,前者走 socket,后者走 TCP,权限需分别授权
必须删掉 'user'@'%',否则限制无效
常见现象:改完 host 字段,还是能从任意 IP 登录。根本原因不是语法错,而是账号冲突:'user'@'%' 和 'user'@'192.168.1.%' 是两个独立账号。只要前者存在,客户端就可能匹配到它(尤其 DNS 解析慢或顺序混乱时)。
FLUSH PRIVILEGES 不解决这个问题——它只重载权限缓存,不删除账号。
- 先查冗余账号:
SELECT user, host FROM mysql.user WHERE user = 'app'; - 显式删掉宽泛账号:
DROP USER 'app'@'%'; - 再确认只剩目标账号:
SELECT user, host FROM mysql.user WHERE user = 'app'; - 如果已有用户不能删(比如生产环境 root),只能用
RENAME USER 'app'@'%' TO 'app'@'192.168.1.%';,但 MySQL 8.0+ 才支持该语法
GRANT 必须带完整 @host,否则权限授错地方
执行 GRANT SELECT ON mydb.* TO 'app'(漏写 @'192.168.1.%')是高危操作。MySQL 会默认按当前会话的 USER() 返回的 host 推断目标账号,极大概率落到 'app'@'%' 上,等于白忙活。
- 永远显式写出完整标识:
GRANT SELECT, INSERT ON mydb.* TO 'app'@'192.168.1.%'; - 授权后必须
FLUSH PRIVILEGES;,虽然多数场景自动重载,但显式刷新更稳妥 - 验证权限是否生效:
SHOW GRANTS FOR 'app'@'192.168.1.%';
防火墙 + bind-address 是必要补充,不是替代方案
仅靠 MySQL 账号限制不够稳健:万一误配、密码泄露、或有人拿到 root 权限,就彻底失守。所以必须叠加网络层防护。
- 系统防火墙(如 firewalld)加规则:
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port port="3306" protocol="tcp" accept' - MySQL 配置文件里设
bind-address = 192.168.1.10(监听具体内网 IP),而非0.0.0.0或留空;配合skip-name-resolve关闭 DNS 解析,避免延迟和绕过风险 - 云环境优先用安全组(Security Group),比数据库内建权限更前置、更难绕过
真正容易被忽略的是测试环节:改完之后,必须从目标 IP(如 192.168.1.50)和非目标 IP(如公网机器或另一网段)分别实测连接,不能只信配置没报错。











