很多运维第一次做MySQL离线部署时,最容易踩的坑根本不是MySQL本身,而是“安装包没选对”“依赖库没查”“目录权限给错”这些事先看不见的问题。离线的意思是服务器连不上外网,一旦少了一个包或者少了一个库,你没法顺手yum install,只能抱着U盘来回跑,非常耽误时间。这篇我按自己实际操作过的流程,把离线环境里用二进制tar包部署MySQL的每一步写清楚,包括为什么要这样选、哪些命令会报什么错、报错之后该怎么处理,直接照着做就行。
先说一个总原则:离线部署MySQL,我推荐用官方“Linux - Generic”的二进制tar包,而不是rpm包,也不是源码编译。原因是rpm包往往绑定某个发行版的glibc版本和依赖体系,换到国产系统或者最小化安装的CentOS上容易缺依赖,而源码编译需要gcc、cmake、ncurses-devel一整套编译链,离线补依赖能把人补崩溃。通用二进制包只要glibc版本够,把libaio这些少量运行库准备好,解压就能跑,是最稳的一条路。
1. 离线场景下的安装包选型和传输要点
既然是“带图文步骤”,这一章把开始动手之前所有需要准备的事情讲透。这步做扎实,后面出错概率会降一半。
1.1 为什么离线环境要选“Linux - Generic”二进制包
MySQL官方下载页里,Linux下的安装包分好几类:Red Hat系的rpm bundle、Debian系的dpkg包、还有“Linux - Generic”的tar包。你需要找的是那个文件名类似mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz的通用包。
这个包的优点是它基本不挑发行版。官方在编译时已经用一套兼容性很好的glibc版本做好了静态链接,你只要保证操作系统glibc版本不低于它标注的glibc 2.12,绝大多数现代的CentOS 7/8、Rocky Linux、openEuler、麒麟都满足条件。它不像源码包那样需要编译工具链,也不像rpm包那样要求你的系统版本和打包基准系统完全一致。
选这个包还有个现实原因:国产化环境里,很多服务器装的是麒麟、统信或自己裁剪过的系统,直接用CentOS的rpm包经常碰到依赖不匹配。二进制包虽然要手动创建用户、写systemd文件,但胜在可控——出了问题你清楚问题在哪一层,而不是被包管理器莫名其妙地挡住。
1.2 下载、校验、传包
离线机器的网络是断开的,但你不必在离线机器上登录MySQL官网。正确做法是准备一台能上网的电脑或互为跳板的内网机器,下载好安装包,再用U盘、内网FTP或scp传到目标服务器。
我一般会多做一个动作:在联网机器上下载时,把官方页面的SHA256校验值一起保存下来。传到服务器后先校验,防止文件在传输过程中损坏。命令是这样用的:
bash复制sha256sum mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz
把输出的哈希值和官网公布的值对比,一致再继续。这一步看起来费事,但在没有外网的服务器上,一旦包坏了,解压时一堆莫名其妙的问题会让人误判成系统故障,排查成本远高于提前校验。
文件传到服务器后,假设放在/root/目录,先把压缩包挪到打算安装的目录:
bash复制ls -lh /root/mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz
看到文件在,再往下走。
1.3 提前检查三类依赖,别等mysqld报错再补
不少人的习惯是拿到包就解压,解压完直接执行mysqld --initialize,结果壳子弹一行“error while loading shared libraries: libaio.so.1”。我之前在几台最小化安装的服务器上都碰到过这个报错。
解决思路是:解压完之后,先不要急着初始化,用ldd命令看一下MySQL的二进制文件缺哪些动态库:
bash复制/usr/local/mysql/bin/mysqld --version
ldd /usr/local/mysql/bin/mysqld | grep "not found"
如果输出里有libaio.so.1之类的“not found”,说明系统缺运行库。CentOS/RHEL系的离线环境,建议提前准备好libaio和libaio-devel这两个rpm包;如果连numactl也被提示缺失,同样要补。麒麟、openEuler这些系统一般自带libaio,但最小化安装同样可能没有。
离线环境补依赖的办法只有一个:在有网机器上下载对应系统的rpm包,拷贝进去后用rpm -ivh安装。这里提醒一下,下载时注意系统和架构,x86_64的包不能安到aarch64机器上,强制装会损坏系统。
1.4 用有网机器做“依赖探测”的小技巧
如果你不确定目标服务器还缺什么,最有效的办法是先在一个和它同样系统版本、同样最小化安装的联网机器上做一遍完整的部署演练。在那台机器上执行到哪一步报缺什么,就yum install补什么,然后看它安装包时的依赖树,把所有依赖rpm统统下载下来。
bash复制yum install --downloadonly --downloaddir=/root/mysql-deps libaio libaio-devel numactl
这样一条命令就能把rpm包连同依赖关系一起拉下来,拷贝到离线机器后逐个安装,干净利落,不用靠猜。这个方法适用于任何离线软件部署,不只限于MySQL。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 文件系统层面的准备工作:用户、目录、安装路径
安装包就位、依赖确认没问题之后,开始动手在服务器上铺路。这一步不用急着解压到最终目录,先想清楚你的安装路径、数据目录规划,避免后期想调整时又要搬迁数据。
2.1 独立账号必须创建
MySQL官方明确不推荐用root账号直接启动mysqld,原因是安全风险过大。一旦MySQL被入侵,进程权限直接就是root,攻击者等于拿下了整台服务器。
创建专用账号的标准做法:
bash复制groupadd mysql
useradd -r -g mysql -s /sbin/nologin mysql
-r表示创建系统账号,-s /sbin/nologin表示这个账号不能交互登录,只能用来跑服务。这样即使MySQL被攻破,能拿到的权限也只是一个普通系统账号权限,破坏范围大大缩小。
创建完后确认一下uid和gid:
bash复制id mysql
输出类似uid=990(mysql) gid=988(mysql)就说明创建成功。后面的数据和目录全部要chown给这个用户。
2.2 解压、目录规划、软链接
解压安装包前,先确认目标目录所在分区空间够用。用df -h /usr/local看看空间,再用df -h /data看数据目录所在分区。数据文件会越来越大,这点一定要提前规划。
解压:
bash复制cd /usr/local
tar -xJvf /root/mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz
解压完会生成一个类似mysql-8.0.32-linux-glibc2.12-x86_64的目录。我习惯把这个目录后面加一个版本无关的软链接:
bash复制mv /usr/local/mysql-8.0.32-linux-glibc2.12-x86_64 /usr/local/mysql-8.0.32
ln -s /usr/local/mysql-8.0.32 /usr/local/mysql
为什么要用软链接?因为以后升级MySQL,只需要把新版本解压到同级的mysql-8.0.33目录,再把软链接指过去,服务配置路径不用改一处。数据库技术人最怕的就是目录路径写死在配置里,升级一次要改一堆文件。软链接这种思路在部署任何带版本号的软件时都适用。
接下来创建数据目录。常见的规划是/data/mysql:
bash复制mkdir -p /data/mysql
chown -R mysql:mysql /data/mysql
注意目录权限不能给755之外过宽的权限,MySQL数据目录属主必须是mysql用户。如果这里给了root权限,后面mysqld以mysql用户启动时就会报“Permission denied”,初始化阶段直接失败。
2.3 环境变量配置,但不建议写进全局
有些教程会教你把/usr/local/mysql/bin写进/etc/profile里的PATH。但我的建议是尽量不要动全局环境变量,只在自己的/root/.bashrc里加:
bash复制echo 'export PATH=/usr/local/mysql/bin:$PATH' >> /root/.bashrc
source /root/.bashrc
原因很简单:/etc/profile是全局的,上面加载了所有用户的PATH,如果哪天多个版本的MySQL工具混在一起,或者后装的软件也往PATH里塞了同名命令,冲突排查起来很麻烦。放当前用户就够了,平时运维也都用的是这个管理员账号。
验证:
bash复制mysql --version
能打印mysql Ver 8.0.32 for Linux on x86_64,说明工具链已经可用了。
3. 初始化数据目录和首启配置:最关键的十分钟
到这里,安装包已经解压,用户也建好了,下一步开始写配置文件并初始化数据目录。这个阶段决定MySQL能不能正常起来,命令的先后顺序、配置项含义都要清楚。
3.1 写一个够用且不过度的my.cnf
MySQL 8.0的配置读取顺序是/etc/my.cnf => /etc/mysql/... => basedir/my.cnf => 用户目录.my.cnf。离线部署我建议只用一个/etc/my.cnf,集中管理,别分散配置。
一个适合多数业务的初始化配置示例:
ini复制[mysqld]
user=mysql
basedir=/usr/local/mysql
datadir=/data/mysql
socket=/data/mysql/mysql.sock
pid-file=/data/mysql/mysql.pid
port=3306
server-id=1
character-set-server=utf8mb4
collation-server=utf8mb4_0900_ai_ci
log-error=/data/mysql/error.log
slow_query_log=ON
slow_query_log_file=/data/mysql/slow.log
long_query_time=2
max_connections=1000
innodb_buffer_pool_size=2G
重点说几个配置项:
datadir是数据目录,必须指向已经付给mysql用户的目录。socket不要用默认的/tmp/mysql.sock。因为/tmp目录可能会被系统临时清理,一旦socket文件被删,本地客户端就连接不上。数据目录里的socket文件跟着数据目录走,存活期稳定。innodb_buffer_pool_size一般设为物理内存的60%-70%,比如服务器16G内存,设10G左右。这里先给2G是为了确认服务能跑通,上线前再根据实际内存调整。- MySQL 8.0默认字符集就是utf8mb4,但明确写出来,是为了让后面接业务的同学一眼看到配置意图,避免业务库还是latin1字符集,中文乱码。
写好后检查一遍有没有语法错误。my.cnf的语法规则是“选项名=值”,每行一个,方括号里的[mysqld]表示这一块配置只作用于服务端。
3.2 第一次初始化,处理好临时密码
配置写好之后,开始初始化。这个动作的目的是在数据目录里生成MySQL的系统库、权限表等基础文件。8.0之前可以用mysql_install_db,8.0以后统一用mysqld --initialize。
执行:
bash复制/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --initialize --user=mysql
记住,这行命令需要在root下执行,因为默认先root读配置文件,内部再切换到mysql用户去运行初始化逻辑。如果命令执行成功,最后会看到一个明显的提示:
code复制[Note] [MY-010454] A temporary password is generated for root@localhost: df#QkYg3x!a9
这个临时密码是自动生成的,不带参数执行时不会输出到终端,在log-error里记录。要是不小心关掉了终端,还可以去打开配置的/data/mysql/error.log查:
bash复制grep "temporary password" /data/mysql/error.log
有个变种命令是mysqld --initialize-insecure,意思是生成一个空密码的root用户。内网离线环境图省事可以用,但强烈不建议。空密码的root账号在网络上就是一个裸奔的突破口,万一哪天内网被横向渗透,第一件事就是扫3306空密码。还是老老实实用临时密码这条路,后面马上修改。
初始化完成后,检查数据目录结构:
bash复制ls -l /data/mysql
看到mysql、sys、performance_schema等目录生成,说明数据目录已经初始化好了。如果ls出来报权限问题,或者里面文件owner不是mysql,先回看chown那一步是不是漏了。
再说一个新手必踩的坑:初始化这个动作只能做一次。如果第一次初始化失败,你带着残留的文件再执行一遍,会直接报:
code复制[ERROR] [MY-010457] --initialize specified but the data directory has files in it. Aborting.
这时候不能傻乎乎地再跑,要么把数据目录清空,要么换一个新目录。注意清空之前确认里面没有业务数据,命令是:
bash复制rm -rf /data/mysql/*
3.3 用日志纠错代替瞎猜
如果初始化过程中报错,别慌张,直接在error.log里看原因:
bash复制tail -50 /data/mysql/error.log
常见的几个报错原因,我列一下方便排查:
| 报错现场 | 大概率原因 | 解决动作 |
|---|---|---|
error while loading shared libraries: libaio.so.1 |
缺libaio | 安装依赖rpm包 |
[ERROR] Could not open required defaults file |
/etc/my.cnf路径错误或语法错误 |
修复配置 |
[ERROR] Can't find error-message file |
basedir设置错误 |
检查basedir是否指向真实MySQL目录 |
[ERROR] failed to set datadir to /data/mysql |
目录权限、属主不对 | 重新chown mysql |
排查时有个原则:看第一行和最后一行的[ERROR],中间全是废话。尾部日志最能说明问题。
4. 注册systemd服务,实现开机自启和标准管理
这一章是让MySQL正式成为一个“服务”。用systemd而不是mysqld_safe或nohup,原因很实在:systemd能管理进程的启停状态、崩溃自动拉起、开机自启,日志输出也统一走journald,运维同事看到的是标准的systemctl status mysql,不用去猜后端进程在哪。
4.1 编写mysql.service单元文件
在/etc/systemd/system/下创建mysql.service:
bash复制vim /etc/systemd/system/mysql.service
内容如下:
ini复制[Unit]
Description=MySQL 8.0
After=network.target
[Service]
Type=simple
User=mysql
Group=mysql
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf
Restart=on-failure
LimitNOFILE=65535
PrivateTmp=false
[Install]
WantedBy=multi-user.target
几个点要拆开讲一下:
Type=simple表示ExecStart启动的进程就是主进程。mysqld本身是常驻前台进程,所以用simple最直接。LimitNOFILE提高文件描述符上限。MySQL高并发下要打开大量文件句柄,默认1024绝对不够,这里直接设成65535。Restart=on-failure让MySQL意外退出时能自动拉起来,宕机后系统重启时也会跟随多用户模式自动启动。PrivateTmp=false是因为socket文件和临时文件路径如果需要访问/tmp,systemd默认的私有tmp机制会隔离,导致找不到。虽然我们把socket放在/data/mysql了,但保险起见还是关掉隔离。
写完执行:
bash复制systemctl daemon-reload
systemctl enable mysql
systemctl start mysql
可以看到systemctl enable mysql会创建一个/etc/systemd/system/multi-user.target.wants/mysql.service的软链接,说明开机自启已经挂上了。
4.2 验证启动状态和端口
启动之后一步步验证:
bash复制systemctl status mysql
如果显示active (running),恭喜,服务已经起来了。再用两条命令确认监听端口和socket文件:
bash复制ss -lntp | grep 3306
ls -l /data/mysql/mysql.sock
看到3306端口被mysqld监听,socket文件已生成,说明服务端一切正常。
此时你从局域网其他机器用telnet 目标IP 3306测一下端口通不通:
bash复制telnet 192.168.1.20 3306
能连通就说明网络层没问题,连不通就检查一下服务器的防火墙策略,下面会专门讲。
4.3 用日志追踪运行状态
systemd服务的日志统一用journald管理,排查问题非常方便:
bash复制journalctl -u mysql -n 100
这个命令能看到MySQL服务最近100行日志,包括启动过程、用户连接信息、错误栈。和/data/mysql/error.log结合起来,基本上所有运行问题都能定位。
5. 修改root密码、创建业务账号与加密连接细节
初始化完成时生成了一个随机临时密码,现在要正式接管这个数据库:改掉root的临时密码,新建业务账号,不然你连数据库都进不去。
5.1 用临时密码登录并修改root密码
先通过socket方式本地登录:
bash复制mysql -uroot -p -S /data/mysql/mysql.sock
输入初始化时生成的临时密码。如果提示:
code复制ERROR 1045 (28000): Access denied for user 'root'@'localhost'
说明密码输错了,回到error.log重新查。
进入mysql命令行后,第一件事修改root密码:
sql复制ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourNewStrongPassword';
MySQL 8.0默认密码校验插件是caching_sha2_password,密码强度要求默认比较高。建议密码至少12位,带大小写字母、数字和特殊字符。如果你设的密码太弱,会直接报:
code复制ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
不想受这个限制的话可以临时降低校验级别,但生产环境千万别干这事。弱密码就像门不锁,等到出事再后悔就晚了。
修改成功后退出重登一次,确认新密码生效:
bash复制mysql -uroot -p -S /data/mysql/mysql.sock
5.2 root账号为什么不该开远程
很多人装完MySQL后,习惯把root@localhost改成root@%方便远程连接。这是运维安全里最大的忌讳。root账号权限太大,相当于数据库的超级管理员,一旦远程被爆破,整个库都没有秘密可言。
正确的姿势是创建业务专属账号,并且只授予业务库必要的权限。
比如给一个叫appuser的账号,只允许从内网网段192.168.1.%连接,只授予appdb库的权限:
sql复制CREATE USER 'appuser'@'192.168.1.%' IDENTIFIED BY 'App@2024Strong';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER ON appdb.* TO 'appuser'@'192.168.1.%';
FLUSH PRIVILEGES;
这里192.168.1.%是允许访问的客户端网段,appdb.*是库名加表通配符。这样即使账号泄漏,攻击者也只能操作业务库,不能动其他数据库。
5.3 防火墙和安全组配置
端口3306监听了,账号也建好了,如果从应用服务器连不上MySQL,首先要查目标服务器的防火墙。
用systemd管理防火墙的环境,执行:
bash复制firewall-cmd --permanent --add-port=3306/tcp
firewall-cmd --reload
查看是否生效:
bash复制firewall-cmd --list-ports
如果服务器用的是iptables而非firewalld,则加一条规则:
bash复制iptables -I INPUT -p tcp --dport 3306 -j ACCEPT
这里有个细节:有些云服务器外还有一层安全组,即使服务器防火墙放行了3306,安全组没放行照样连不通。这种环境要先去云控制台的安全组规则里放行对应端口。
5.4 从应用服务器验证连接
在装好mysql客户端的应用服务器上执行:
bash复制mysql -uappuser -p -h 数据库IP -P 3306
如果报:
code复制ERROR 2003 (HY000): Can't connect to MySQL server on 'x.x.x.x' (111)
说明TCP层没通,查防火墙和安全组。如果报:
code复制ERROR 1045 (28000): Access denied for user 'appuser'@'应用IP'
说明账号授权主机范围没覆盖当前IP,改一下CREATE USER里的网段范围就能解决。
6. 部署完成后的杂项检查和运维经验
到这里,一套离线部署的MySQL其实已经能跑业务了。但这几年我帮别人收拾过很多次“环境跑起来但后面问题一堆”的烂摊子,所以最后这章把一些常被忽略的检查项和经验整理出来。
6.1 字符集校验
业务代码里中文乱码,90%以上是字符集问题。在MySQL命令行里执行:
sql复制SHOW VARIABLES LIKE 'character_set%';
重点看character_set_server和character_set_database是不是utf8mb4。MySQL 8.0默认就是utf8mb4,但如果你从旧版本迁移或配置文件里没写,也可能变成latin1。
如果发现不对,在my.cnf里补上上一章说的那两行,重启MySQL服务。可以在初始化建库时用:
sql复制CREATE DATABASE appdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
库一旦建好,字符集就不能随意改了,迁移库的代价非常大,所以这个检查放在第一优先级。
6.2 数据目录满了怎么办
离线部署的机器往往只分了一个大分区,数据目录和系统共用空间。MySQL运行一段时间后,binlog和undo log会占大量空间。建议在my.cnf里加上binlog过期时间参数:
ini复制binlog_expire_logs_seconds=604800
MySQL 8.0里,旧的expire_logs_days在8.0.14之后已经不建议用,推荐用binlog_expire_logs_seconds。上面配置是让binlog保留7天,之后自动清理。
空间检查命令:
bash复制df -h /data
du -sh /data/mysql/*
如果binlog目录特别大,也可以手动清理:
sql复制PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY;
注意这条命令执行前必须确认没有从库还在拉binlog,不然会切断复制链路,只能让从库重新同步。
6.3 系统SELinux是否影响MySQL
有些发行版默认开启SELinux,而且配置比较严格。启动服务时如果发现MySQL能start但连不上socket,或者数据写入报权限错误,就要检查SELinux。
查看状态:
bash复制getenforce
输出Enforcing表示开启。有两种处理方式:
- 临时放开:
setenforce 0 - 永久关闭:修改
/etc/selinux/config,把SELINUX=enforcing改成SELINUX=disabled
生产环境如果不确定能不能关SELinux,至少你要知道这个因素,排障时多个排查方向,不至于对着错误日志瞎猜很久。
6.4 我个人的部署后检查清单
下面这个清单是我每次装完MySQL必须过一遍的。你可以直接抄走:
bash复制# 1. 服务自启状态
systemctl is-enabled mysql
# 2. 进程存在,端口监听
ps -ef | grep mysqld
ss -lntp | grep 3306
# 3. 数据目录权限
ls -ld /data/mysql
# 4. 字符集
mysql -uroot -p -e "SHOW VARIABLES LIKE 'character_set%';"
# 5. 关键参数
mysql -uroot -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
mysql -uroot -p -e "SHOW VARIABLES LIKE 'max_connections';"
# 6. binlog过期策略
mysql -uroot -p -e "SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';"
# 7. 备份任务是否配置(至少要有mysqldump或xtrabackup的定时计划)
crontab -l
如果以上每一项都符合预期,这套MySQL才算真正交付,而不是“能启动就算完”。很多事故往往不在部署动作本身,而是在交付检查里漏掉了某些关键项,等到业务跑了几个月才发现字符集错了、binlog把磁盘塞满了。
最后分享一个这几年的心得:离线部署MySQL确实比在线多花一些时间在准备阶段,但只要你把“下载校验、依赖探测、目录规划、初始化、systemd注册、账号管控”这条链路走熟,碰上任何一家内网环境都能差不多二十分钟搞定。中间最容易翻车的永远是急躁,跳过检查直接跑初始化。拿到的包先校验,解压完先ldd,初始化前先确认权限,每一步花两分钟验证,后面就能省下两小时排查。这套流程里最有价值的不是哪条命令,而是“宁可多验证一步,不赌运气”的操作习惯。
