前阵子帮一个朋友排查数据库连接问题,他那边情况挺典型的:应用服务器和数据库服务器都在内网,本机连 MySQL 一点问题没有,换台机器用客户端工具一连就报错,提示什么Host 'xxx.xxx.xxx.xxx' is not allowed to connect to this MySQL server。折腾了半天,最后发现就是远程登录权限没开,root 用户默认只绑定了 localhost。这类问题在刚接触 MySQL 运维的同学里太常见了,尤其是从 Windows 转过来、刚在 Linux 上用 rpm 装完 MySQL 5.7 或者 8.0 的朋友,装完第一步就是被远程连接卡住。
我整理了一下平时工作中开放 MySQL 远程登录权限的完整操作命令和思路,从权限模型讲起,把授权命令、配置修改、防火墙放行这些环节一次说清楚。这篇内容适合刚装完 MySQL 想从别的机器连过来、或者公司内网需要多台服务器共享数据库的开发同学参考,老手也可以直接翻到后面的排查部分,看看有没有踩过一样的坑。
1. 内容整体设计与思路拆解
先理清一个概念:MySQL 的“远程登录权限”,本质上是两件事的组合。第一件事是 MySQL 账号本身有没有被允许从非本机地址登录,这个由用户表里的 host 字段决定。第二件事是 MySQL 服务端有没有监听外部网络接口,这个由配置文件里的 bind-address 参数决定。两个条件缺一不可,只改账号不改监听地址,或者只改监听不改账号,都会导致远程连接失败。
很多人喜欢直接拿 root 账户开远程,我一般不建议这么干。MySQL 的权限模型设计得很细,一个账户由user和host联合组成主键,'root'@'localhost'和'root'@'192.168.1.%'是两个完全独立的账户,权限可以完全不同。生产环境里更稳妥的做法是新建一个专门的应用账号,只授权特定库,host 写成应用服务器的 IP 或者网段,这样即使账号泄露,攻击面也控制在单个数据库范围内。
整个操作的步骤拆解下来大概是这个顺序:先确认服务端监听状态,再创建或者修改账号的 host 范围,接着刷新权限让配置生效,然后检查系统防火墙和 MySQL 端口,最后用客户端工具从远程验证连接。每一步都有对应的验证命令,前后顺序最好不要颠倒,否则出了问题很难定位是哪一层没通。
还有一个很多人忽略的点:MySQL 8.0 和 5.7 在授权语法上有差异。5.7 及更早版本可以直接用一条GRANT ALL PRIVILEGES ON *.* TO 'user'@'host' IDENTIFIED BY 'password'同时完成创建用户和授权,但 8.0 把创建用户和授权拆开了,必须先CREATE USER,再单独GRANT。如果拿着旧版语法在 8.0 上执行,会直接报语法错误,这个兼容性坑我见过不止一次。后面我会把两套命令都写出来,方便对照。
2. 完整授权流程与命令实战
2.1 第一步:确认 MySQL 监听地址
操作之前先看看服务器上 MySQL 到底监听了什么地址。在服务器上执行:
netstat -tlnp | grep 3306输出结果里如果看到127.0.0.1:3306,说明 MySQL 只监听了本机回环地址,外部网络请求根本到不了 MySQL 进程这一层,这种情况改账号权限也没用,必须先改配置文件。如果看到0.0.0.0:3306或者:: :3306,说明监听没问题,问题大概率出在账号授权层面。
监听地址的设置在配置文件里,Linux 上一般是/etc/my.cnf或/etc/mysql/mysql.conf.d/mysqld.cnf,Windows 上一般是安装目录下的my.ini。找到[mysqld]段落,看一下有没有bind-address这一行。默认安装经常会设置为127.0.0.1,把它改成:
[mysqld] bind-address = 0.0.0.00.0.0.0表示监听所有网络接口。如果服务器有多个网卡,想只监听内网网卡,也可以直接写成那个网卡的 IP 地址,比如bind-address = 192.168.1.10,这样更安全一些。改完配置需要重启 MySQL 服务才能生效:
systemctl restart mysqld重启之后再用netstat确认监听地址已经变化。这一步是整个远程登录的底层基础,很多人折腾半天账号权限,其实第一步监听就没过。
2.2 第二步:MySQL 5.7 授权命令
MySQL 5.7 的授权在一条语句里就能完成。先用 root 登录 MySQL(这里在服务器本地操作):
mysql -uroot -p登录后执行授权命令。举个例子,创建一个名为appuser的账号,密码是App@2024,允许它从192.168.1.0/24这个网段的任何机器连接,并且拥有对appdb库的全部权限:
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'192.168.1.%' IDENTIFIED BY 'App@2024'; FLUSH PRIVILEGES;注意这里的 host 写法:'192.168.1.%'用百分号做通配符。如果只想让一台机器连,就写具体的 IP,比如'appuser'@'192.168.1.88';如果想放开所有地址,就写'appuser'@'%'。百分号在 MySQL 的 host 匹配里代表任意字符序列,类似模糊匹配。
授权完可以验证一下:
SELECT user, host, authentication_string FROM mysql.user WHERE user = 'appuser';从这个输出能直观看到用户创建情况。%和具体的 IP 都是独立的账户记录,用哪条连数据库,MySQL 就按哪条记录的权限来校验。
2.3 第三步:MySQL 8.0 授权命令
MySQL 8.0 的语法类似,但步骤更明确。同样创建账号并授权:
CREATE USER 'appuser'@'192.168.1.%' IDENTIFIED BY 'App@2024'; GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'192.168.1.%'; FLUSH PRIVILEGES;8.0 里CREATE USER里的IDENTIFIED BY指定密码,GRANT只负责授权,不再负责创建用户。如果你想在 8.0 里一次性创建用户并授权,可以直接用CREATE USER ... IDENTIFIED BY ...再跟进GRANT,但千万别把 5.7 的旧语法原封不动搬过来。
还有一个 8.0 特有的注意点:默认的认证插件是caching_sha2_password。如果你的客户端工具版本太老(比如有的老版本 Navicat、旧版 JDBC 驱动),可能不支持这个认证插件,报错信息通常是Authentication plugin 'caching_sha2_password' cannot be loaded。遇到这种情况有两个解决办法:一是升级客户端工具或驱动;二是在创建用户时改成mysql_native_password认证方式:
CREATE USER 'appuser'@'192.168.1.%' IDENTIFIED WITH mysql_native_password BY 'App@2024';或者是我的个人建议,尽量升级客户端,而不是降级认证插件,因为caching_sha2_password安全性更好,密码传输也做了加密处理。但如果是老系统临时过渡,改认证插件也是业内常见的做法。
2.4 第四步:权限刷新与回收
授权之后执行FLUSH PRIVILEGES是为了让授权表的改动立即生效。这里多说一句,用GRANT、CREATE USER这类语句修改权限之后,其实不需要刷新就会自动生效了,但FLUSH PRIVILEGES主要作用是处理那些直接修改mysql.user表记录的情况。保险起见,执行一下也不亏,尤其当你改了系统表之后。
权限回收也是常见操作。比如发现appuser权限过大,只保留 SELECT 权限:
REVOKE ALL PRIVILEGES ON appdb.* FROM 'appuser'@'192.168.1.%'; GRANT SELECT ON appdb.* TO 'appuser'@'192.168.1.%'; FLUSH PRIVILEGES;删除账号用DROP USER:
DROP USER 'appuser'@'192.168.1.%';这里有个小细节:执行DROP USER之前最好确认一下当前有没有会话正在使用这个账号,否则删了之后,已有的连接不会立即断开,但新连接全部会被拒绝,这可能会对线上应用产生预期外的影响,操作前最好先看一下SHOW PROCESSLIST的输出。
3. 从安装到远程连接的系统配置
这个章节写给准备完整走一遍“安装 MySQL 并开放远程登录”流程的朋友。实际操作中,很多坑不是授权命令的问题,而是前面的安装和服务配置环节就埋下了隐患。
3.1 Linux 下 rpm 安装后的必要调整
用 rpm 方式安装 MySQL 5.7 或者 8.0,安装完成后默认数据目录是/var/lib/mysql,配置文件是/etc/my.cnf。装完第一步要初始化:
mysqld --initialize注意不是mysql_install_db,MySQL 5.7.6 以后那个脚本已经被废弃了。初始化完成之后,临时密码会打印在错误日志里:
grep 'temporary password' /var/log/mysqld.log用这个临时密码登录 root,系统会强制要求先改密码才能做其他操作:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPassword@123';连接过程里如果出现[ERROR] [MY-014060] [Server] Invalid MySQL server upgrade这种报错,多半是数据目录版本和服务版本不匹配,比如拿 8.0 的服务去读 5.7 初始化的数据目录。解决办法是备份数据后清空/var/lib/mysql重新初始化,或者确认初始化工具和启动的服务版本一致。这是我见过 rpm 安装最容易翻车的地方。
改完密码、确认本机能登录之后,再回到第二章节的授权步骤操作。顺序上别乱:初始化、改密码、确认监听、授权、放行防火墙。
3.2 Docker 部署 MySQL 的端口映射细节
容器化部署的场景也很多,有的人直接用docker run,有的人用docker compose。Docker 里跑 MySQL,远程登录的注意点会多一层,核心是端口映射和容器网络。
命令行方式部署:
docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=MyRoot@2024 \ -v /data/mysql:/var/lib/mysql \ mysql:8.0这里-p 3306:3306把容器内 3306 端口映射到宿主机 3306。如果宿主机 3306 被占用了,可以换一个宿主机端口,比如-p 33060:3306。这种情况下,客户端连接时要连的是 33060,而不是默认的 3306。
用 docker compose 的话,docker-compose.yml核心配置长这样:
services: mysql: image: mysql:8.0 container_name: mysql8 ports: - "33060:3306" environment: - MYSQL_ROOT_PASSWORD=MyRoot@2024 volumes: - /data/mysql:/var/lib/mysql restart: always启动后用docker compose up -d。需要注意,Docker 容器里的 MySQL 通常默认就绑定了0.0.0.0,所以 bind-address 一般不用管,但端口映射到宿主机之后,要保证宿主机防火墙放行了映射出来的那个宿主机端口。
另外一个常见问题是拉镜像失败。docker pull mysql:8.0报类似failed to decode referrers index之类的错误,常见原因是镜像源不稳定,或者本机 Docker 版本和镜像仓库的 API 不兼容。可以先试试用国内镜像加速源,或者改用带完整 tag 的镜像名,比如mysql:8.4.11,避免默认 tag 指向的 manifest 结构带来的兼容性问题。
容器内的 MySQL 想连本机的另一个 MySQL 服务,也不建议用127.0.0.1,因为容器网络是独立的,要用宿主机在内网的实际 IP。
3.3 防火墙、安全组与端口连通性
授权命令写对了,监听地址也没问题,远程还是连不上,下一步就要查防火墙。Linux 上主流的是 firewalld 和 ufw 两套。
CentOS 7/8 用 firewalld,放行 MySQL 端口:
systemctl status firewalld firewall-cmd --zone=public --add-port=3306/tcp --permanent firewall-cmd --reloadUbuntu 用 ufw:
ufw allow 3306/tcp ufv reload云服务器的话,除了系统防火墙,还要看云平台的安全组规则。很多云厂商默认安全组只放行了 80、443、22,3306 是禁的,需要登录控制台手动添加入方向规则。我排查过不少“授权没问题、防火墙也开了但就是连不上”的案例,最后都发现是安全组没放行。
端口连通性检查用telnet或者nc:
telnet 192.168.1.10 3306如果端口通了,一般会看到类似Connected to 192.168.1.10的提示。如果没通,要么是被防火墙拦截,要么是 MySQL 根本没在监听。这一步能快速定位问题到底在服务端还是网络层。
4. 常见问题与排查技巧实录
很多远程连接失败的场景我在日常工作里反复遇到过,这里整理成速查表,方便大家直接对照。大家可以根据报错的关键词快速定位问题方向。
4.1 报错速查:连接类问题
| 报错关键词或场景 | 根本原因 | 解决办法 |
|---|---|---|
Host 'x.x.x.x' is not allowed | 用户 host 没有包含该 IP | 授权当前 IP,或扩大 host 范围 |
Access denied for user | 密码错误或用户不存在 | 重新确认账号密码,检查 host 匹配 |
Can't connect to MySQL server | 端口不通或服务未监听 | 查看监听地址,检查防火墙和安全组 |
Authentication plugin cannot be loaded | 客户端不支持8.0默认认证插件 | 升级客户端,或改用 mysql_native_password |
SSL connection error | 客户端与服务器 SSL 配置不一致 | 客户端连接加--skip-ssl,或统一 SSL 配置 |
Unknown database | 授权库名与实际创建库名不一致 | SHOW DATABASES查看,确认库名 |
这里特别说一下SSL connection error。MySQL 8.0 编译安装版本默认是开启 SSL 的,某些旧版客户端工具或者特定驱动在握手阶段就会崩。如果内网环境链路本身就安全,可以在客户端连接时加参数跳过 SSL:
mysql -h 192.168.1.10 -u appuser -p --skip-ssl或者在服务器上针对特定用户设置连接时不强制要求 SSL:
ALTER USER 'appuser'@'192.168.1.%' REQUIRE NONE; FLUSH PRIVILEGES;4.2 权限不生效的隐蔽场景
有一种情况比较隐蔽:你已经授权了,防火墙也放行了,但客户端连接后SHOW GRANTS发现权限确实给了,操作表却报SELECT command denied。这时候十有八九是 MySQL 的 host 匹配顺序问题。
MySQL 在匹配用户时,从mysql.user表里找 host 最精确的记录。比如你有一个'appuser'@'%'的账号权限很小,还有个'appuser'@'192.168.1.%'的账号权限很大,客户端从192.168.1.88连接时,MySQL 不一定会用带通配符的那个,而是按规则排出一个匹配顺序。判断方法是登录后执行:
SELECT CURRENT_USER();看返回的结果是哪条记录。如果想要精确控制,建议把所有相关账号梳理一遍,把重复的、模糊匹配的账号删掉,保留最明确的那条授权记录。生产环境里账号一多,这个坑很容易踩。
4.3 服务无法启动与数据目录异常
结合前面提到的[ERROR] [MY-014060]报错,再补充一个常见问题:MySQL 服务无法启动。用 rpm 安装的 MySQL 5.7,启动时报net start mysql 服务无法启动(Windows 环境)或者 systemctl 启动失败,多半是数据目录权限不对,或者 my.cnf 里有非法配置项。
Linux 上可以先查看错误日志:
tail -n 100 /var/log/mysqld.log如果提示/var/lib/mysql权限不足,就执行:
chown -R mysql:mysql /var/lib/mysql如果提示配置参数不认识,用mysqld --verbose --help检查参数是否真的被支持,有时候从网上复制了一段配置文件,但版本不匹配,直接启动失败。这个我在 MySQL 8.4 LTS 版本上遇到过,网上很多 5.7 的配置项在 8.4 里已经改了默认值或者被移除了。
4.4 “改完权限连不上”的特殊场景:本机 root 也被锁
开放远程权限还会出现一个副作用:如果你操作失误,把 root 的 host 改错了,比如不小心删掉了'root'@'localhost'或者把 root 的 host 改成了'%',可能导致本机 root 也登录不了。
我之前遇到过类似情况,解决办法是使用--skip-grant-tables模式启动 MySQL 来修复。当然这是一个有风险的操作,因为跳过权限表意味着所有人都能无密码访问,仅在紧急恢复和确认无外部访问时使用。
具体操作是这样的:
systemctl stop mysqld mysqld_safe --skip-grant-tables &然后用无密码方式登录:
mysql -uroot登录后先执行FLUSH PRIVILEGES;,让权限表生效,再修复用户:
UPDATE mysql.user SET host = 'localhost' WHERE user = 'root' AND host = '%'; FLUSH PRIVILEGES;改完重启 MySQL 服务。这个方法救急很管用,但操作时一定确保服务器处于可控网络环境,不然等于把数据库裸奔在外面。
5. 实操中的几个关键认知
做数据库运维这几年,我有几个比较深的体会,分享出来供大家参考。
第一个体会是:权限最小化原则放在任何时候都不过时。给应用账号授权时,能只授一个库就绝不给所有库,能用具体 IP 就不用%。有些开发同学图省事,直接GRANT ALL PRIVILEGES ON *.* TO 'appuser'@'%',这确实从任何机器都能连,但也意味着这台数据库对所有能访问 3306 端口的人敞开了所有库。安全问题从来不是小事,一旦出问题,连补救的余地都很小。
第二个体会是:写授权命令之前,先确认 MySQL 版本。5.7 和 8.0 的授权差异只在语法细节上,但就是这类细枝末节的差异最容易让人抓狂。我有一个习惯,登录 MySQL 后第一件事就是执行SELECT VERSION();,把版本号确认清楚再动手。另外,对于 MySQL 8.4 LTS 这类新版本,建议先看官方文档确认是否有新增的安全特性,比如 8.4 里对caching_sha2_password的调整,以及对部分旧参数的限制,避免拿旧经验套新版本。
第三个体会是:FLUSH PRIVILEGES之后不要惊慌。很多教程把这条命令说成是“授权后必须执行”,但从 MySQL 5.7 开始,GRANT这类语句已经会自动更新权限表了,你加这一句只是求个心安。倒是如果你直接操作了mysql.user表,比如用UPDATE修改 host,这个情况下不刷新权限是绝对不会生效的。
第四个体会是关于排查顺序。远程连不上,我的排查顺序永远是:网络通不通(ping+telnet3306)→ 服务监不监听(netstat)→ 账号授权对不对(SELECT CURRENT_USER()+SHOW GRANTS)→ 防火墙放没放。按照这个顺序一层层查,大部分问题五分钟内就能定位。千万别一上来就盯着授权命令反复改,很可能问题根本不在那一层。
这些经验不算新鲜,但每一条背后都是实际踩过的坑换来的。大家如果按照文章里的步骤操作完还有问题,不妨回到这个排查表里对照一下,多半能找到答案。