最近在整理一套数据库标准化部署手册,把 MySQL 单实例和多实例的安装部署又重新捋了一遍。说句实话,做数据库运维这些年,真正出问题的往往不是高深的主从复制、分库分表,而是最基础的安装部署阶段:目录权限不对、数据目录塞进了系统盘、多实例的端口和 socket 文件互相干扰、初始化之后密码找不到了。这些基础环节一旦埋雷,后面排查起来特别被动。
这篇东西不是把官方文档再抄一遍,而是从我实际部署经验里提炼的一套可落地流程,覆盖 MySQL 单实例安装、多实例规划与部署、启动配置、常见故障排查。重点说清楚两件事:每一步操作背后的原因是什么,以及在正式环境里哪些细节必须提前卡死。适合刚接触 MySQL 安装部署的人,也适合已经会装但想搞清楚多实例管理逻辑的工程师参考。
1. 单实例还是多实例:先想清楚再动手
1.1 单实例和多实例到底差在哪
MySQL 的单实例,就是一个 MySQL 服务进程对应一套数据目录、一套配置文件、一组端口和 socket 文件。默认端口 3306,数据目录通常是/var/lib/mysql或者你自己指定的路径。它是一台服务器上最常规的部署形态,逻辑清晰,资源全部归一个数据库用。
多实例则是同一台物理机或虚拟机上,同时运行多个 MySQL 进程。每个进程都有独立的basedir、datadir、port、socket、pid-file,甚至可以根据业务重要性分配不同的内存和 CPU 资源。同一个my.cnf里用多个[mysqldN]配置段来隔开。打个比方:单实例是一套房子里只住一家人,多实例是一套房子里隔出几个独立的房间,各有各的门锁和活动空间,但水电煤是共享的。
很多新手容易把多实例和集群混淆。多实例只是多个独立的 MySQL 进程共用一台机器,实例之间没有主从关系,没有数据同步,它们各自独立存储数据。主从复制、读写分离是在这些实例之上额外配置的复制链路,两码事。
1.2 什么样的业务适合多实例
多实例的典型场景我归纳过,主要就是下面几种:
- 服务器资源有限,比如手头只有一台 32G 内存的机器,但有三套不同业务系统的库要放,每套还不想互相影响,那就拆成三个实例,分别限制内存和连接数。
- 测试环境经常要跑多个 MySQL 版本,或者同一个版本但不同参数配置的实例,多实例能让你在物理机不变的前提下快速起多套环境。
- 云主机规格是有上限的,某些托管数据库服务又比较贵,内部团队做数据服务时用多实例能在成本和隔离之间找个平衡。
不适合上多实例的情况也很明显:如果单实例的 IO 压力已经接近磁盘瓶颈,或者单个业务日增数据量很大,此时加实例只是把压力分到多个进程里,但底层的 CPU、内存、磁盘 IO 依然是共享的,谁打满就大家一起遭殃。资源充足且单一业务量大的场景,一台机器一个实例往往是更稳的选择。
1.3 选它之前先算这三笔账
我每次在做容量规划时都会先算三笔账。第一笔是 CPU 账:MySQL 对 CPU 的利用率通常集中在几个特定线程上,比如主线程、刷脏线程、网络 IO 线程,多实例能让多核 CPU 的利用率更均衡。但核数不多时,实例超过核数反而会导致争抢,计算时看top里每个实例的 CPU 占用就好。
第二笔是内存账:innodb_buffer_pool_size是 MySQL 最大的内存消费者。我的习惯是先算单机总内存,减去系统、其他中间件、文件缓存预留后,再按比例拆给各实例。比如 32G 内存的机器,系统预留 6G,剩下 26G 分给 3 个实例,一般给 8G、8G、10G,宁可 buffer pool 给小一点,也别因为配置太贪导致内存不足触发 OOM。
第三笔是端口和路径账:每多一个实例,就要多一个端口、多一对 socket 和 pid 文件路径、多一个错误日志文件。端口规划要写在文档里,避免某个实例偷偷占用了别的中间件的端口,排查时半天发现原来是端口互撞。
这三笔账算清楚,再往下走就不慌。
2. 单实例安装部署全流程
2.1 安装方式怎么选
MySQL 的安装方式常见的有三种:官方编译好的二进制包、源码编译、系统包管理器安装。我个人的建议很直接:生产环境优先用官方二进制包,除非有特殊定制需求才走源码编译。原因也不复杂,官方二进制包是编译好并且经过大量兼容性测试的,部署时只需要解压、配置、初始化三步;源码编译虽然也能装,但编译耗时不说,编译参数和不同 CPU 指令集的兼容性后面都是隐性维护成本。
系统包管理器安装(比如 yum 安装)省事,但仓库里的版本可能滞后,而且 MySQL 官方库和系统自带库之间有时会有冲突。对大多数从零部署的场景,官方二进制包里的tar包就够了。
前置准备往往是新手最容易忽略的环节,但恰恰是安装成败的关键:
# 1. 检查操作系统信息,确认 glibc 版本是否满足要求 ldd --version # 2. 安装依赖库 yum install -y libaio numactl-libs这里有个非常典型的坑:如果libaio没装,执行mysqld --initialize时会直接报error while loading shared libraries: libaio.so.1,看起来像 MySQL 程序坏了,实际就是缺依赖。
2.2 数据目录规划与 my.cnf 核心参数
准备安装前必须先把目录规划好,不能偷懒全部丢在系统盘。用一个实际案例来说明,假定 MySQL 安装在/usr/local/mysql,数据目录放在/data/mysql/data,日志目录放在/data/mysql/logs:
# 1. 创建 mysql 用户和用户组,用于运行 mysqld 进程 groupadd mysql useradd -r -g mysql -s /bin/false mysql # 2. 创建三个基础目录 mkdir -p /usr/local/mysql mkdir -p /data/mysql/data mkdir -p /data/mysql/logs # 3. 给 mysql 用户分配目录所有权 chown -R mysql:mysql /usr/local/mysql chown -R mysql:mysql /data/mysql目录规划的核心原因有两点:第一,数据目录和系统目录分开,避免系统盘空间被数据库日志撑爆后直接拖垮操作系统;第二,单独的日志目录便于日志搜集和排查,也方便做磁盘容量监控。
安装包解压到/usr/local/mysql下之后,需要准备配置文件。这里我直接给一份经过实践验证的配置模板,注意里面的几个参数要按机器实际情况改:
[mysqld] # 基础路径配置 basedir = /usr/local/mysql datadir = /data/mysql/data socket = /data/mysql/mysql.sock pid-file = /data/mysql/mysql.pid log-error = /data/mysql/logs/error.log # 连接与端口配置 port = 3306 bind-address = 0.0.0.0 max_connections = 500 # InnoDB 存储引擎核心参数 innodb_buffer_pool_size = 8G innodb_log_file_size = 512M innodb_flush_log_at_trx_commit = 1 innodb_file_per_table = ON # 字符集 character-set-server = utf8mb4 collation-server = utf8mb4_general_ci # 二进制日志(建议一开始就打开,方便以后做时间点恢复) server-id = 1 log-bin = /data/mysql/logs/mysql-bin binlog_format = ROW [client] socket = /data/mysql/mysql.sock这里重点解释几个关键参数。basedir和datadir必须和实际目录一致,不然后面初始化时会报错。innodb_buffer_pool_size是 InnoDB 用于缓存数据和索引的内存区域,建议设置为物理内存的 50% 到 70% 之间,但前提是机器上只有 MySQL 在跑。innodb_log_file_size决定 InnoDB 的 redo 日志大小,太小会导致日志频繁切换,影响写入性能,太大又会让崩溃恢复时间过长,512M 是相对稳妥的起点。
innodb_flush_log_at_trx_commit这是一个很值得记住的参数。取值为 1 时,每次事务提交都会刷盘,最安全但性能开销高;取值为 2 时,事务提交只写操作系统缓存,每秒刷一次盘,性能好一点,但机器宕机会丢最多一秒的数据。对于正式环境我一般建议保持 1,测试环境为了压性能可以临时改成 2。
2.3 初始化、启动与安全加固
配置写好之后,执行初始化。这一步的目的是在数据目录中生成 MySQL 系统库和初始数据。MySQL 8.0 用--initialize或--initialize-insecure,区别在于前者会在日志里生成一个随机临时 root 密码,后者生成的 root 用户没有密码,适合初始化后马上通过 socket 连接去改密码的场景。
# 1. 切换目录并执行初始化 cd /usr/local/mysql mkdir -p /data/mysql/data chown mysql:mysql /data/mysql/data bin/mysqld --defaults-file=/etc/my.cnf --initialize-insecure --user=mysql # 2. 如果初始化成功,数据目录下会出现 mysql、performance_schema 等目录 ls -l /data/mysql/data # 3. 启动 mysqld bin/mysqld --defaults-file=/etc/my.cnf --user=mysql &这种后台直接拉起的方式方便测试,但正式环境不推荐。用 systemd 做服务托管,才能保证开机自启、进程异常退出后自动拉起。
在/etc/systemd/system/mysql.service写入如下配置:
[Unit] Description=MySQL Server After=network.target [Service] Type=forking User=mysql Group=mysql PIDFile=/data/mysql/mysql.pid ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s TERM $MAINPID PrivateTmp=false [Install] WantedBy=multi-user.target之后执行:
systemctl daemon-reload systemctl enable --now mysql启动后连库验证:
# 使用 socket 连接,因为刚初始化完,root 没有密码 mysql -uroot --socket=/data/mysql/mysql.sock # 在 MySQL 内部确认版本、端口、数据目录 SELECT VERSION(); SHOW VARIABLES LIKE 'port'; SHOW VARIABLES LIKE 'datadir'; # 立即修改 root 密码,并创建一个普通业务账号 ALTER USER 'root'@'localhost' IDENTIFIED BY '强密码'; CREATE USER 'app'@'%' IDENTIFIED BY '业务密码'; GRANT ALL PRIVILEGES ON appdb.* TO 'app'@'%'; FLUSH PRIVILEGES;安全加固这一步不能省。很多线上事故都源于装完库直接用空密码 root 上生产,等于把数据库大门敞开着。密码规则最好满足长度、大小写、特殊字符要求,业务账号的权限按最小化原则,不要随手给ALL PRIVILEGES ON *.*。
3. 多实例部署:从架构到落地
3.1 多实例适用场景与资源预算
前面说过多实例的核心目的是在有限硬件上把资源利用率提上去。实际部署前要规划三件事:实例数量、每个实例的资源上限、端口与目录的约定规则。
实例数量不要拍脑袋定。我见过有人把 4 核 8G 的机器硬拆成 5 个实例,结果每个实例一启动就进入资源争抢状态,最后连连接都经常超时。我的经验是:实例数尽量不要超过 CPU 物理核数,且要预留至少 1 核给操作系统和监控进程。比如 4 核机器最多起 3 个实例,8 核机器最多起 6 个实例,当然还要看业务并发量。
内存方面,建议给每个实例设置innodb_buffer_pool_size后,再预留部分内存给操作系统的 page cache,避免整机进入 swap。最简单的算法是:
预算内存 = 系统总内存 - 系统预留(4G~6G) - 其他进程内存 - page cache预留(2G~4G) 每个实例 = 预算内存 / 实例数比如一台 32G 机器跑 3 个实例,系统预留 6G,其他进程占 2G,page cache 预留 4G,剩 20G 分给 3 个实例,可以按 6G、6G、8G 分配。如果某个实例是核心业务库,可以给它多分一些;测试环境实例给 2G 就够跑。
3.2 目录、端口与配置拆分
多实例的目录结构要有规律可循。我的习惯是每个实例独立一套目录,统一放在/data/mysqlN/下面,这样看目录名就能猜出实例端口和用途。比如端口 3306 的实例目录是/data/mysql3306,端口 3307 的实例目录是/data/mysql3307:
/data/mysql3306/ ├── data/ # 数据文件 ├── logs/ # 错误日志、慢查询日志、binlog ├── mysql.sock # socket 文件 └── mysql.pid # pid 文件如果一台机器上同时跑多实例,配置文件就不要全部堆在/etc/my.cnf里了。MySQL 支持同一份 my.cnf 里写多段配置,最常见的是用mysqld_multi工具来管理,它的配置格式是[mysqldN],N 就是实例编号。
端口规划也很关键,默认 3306 给第一个实例,后续实例通常是 3307、3308、3309,避免和其他中间件的端口冲突。以 3306 和 3307 两个实例为例,配置文件可以合并写到/etc/my.cnf中:
[mysqld_multi] mysqld = /usr/local/mysql/bin/mysqld mysqladmin = /usr/local/mysql/bin/mysqladmin user = root [mysqld1] port = 3306 datadir = /data/mysql3306/data socket = /data/mysql3306/mysql.sock pid-file = /data/mysql3306/mysql.pid log-error = /data/mysql3306/logs/error.log server-id = 1 [mysqld2] port = 3307 datadir = /data/mysql3307/data socket = /data/mysql3307/mysql.sock pid-file = /data/mysql3307/mysql.pid log-error = /data/mysql3307/logs/error.log server-id = 2注意这里[mysqld1]和[mysqld2]是给mysqld_multi识别用的,其中第二个实例的log-error必须显式指定到自己的日志文件,不能和第一个实例共用,否则两个进程同时写同一个日志文件,排查问题时会看到内容混杂在一起,非常容易误判。
4. 实操:从单实例扩展到多实例
4.1 初始化第二个实例
假设机器上已经有一个 3306 实例在跑,现在要新增一个 3307 实例。第一步是创建目录并设置权限:
# 1. 创建数据目录、日志目录、socket/pid 文件目录 mkdir -p /data/mysql3307/data mkdir -p /data/mysql3307/logs chown -R mysql:mysql /data/mysql3307 # 2. 初始化 3307 实例的数据目录 /usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf \ --initialize-insecure --user=mysql --datadir=/data/mysql3307/data这里有个细节:因为[mysqld2]段指定了datadir,所以在命令行里还需要显式带上--datadir,这是为了确保初始化时使用的就是 3307 实例的数据目录,防止误把参数搞混。
初始化成功后,启动 3307 实例:
/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf \ --user=mysql --socket=/data/mysql3307/mysql.sock \ --port=3307 --datadir=/data/mysql3307/data &这样手动启动的方式适合验证配置是否生效,但正式管理不建议这么干,应该用mysqld_multi或 systemd。
4.2 用 mysqld_multi 统一管理实例
mysqld_multi是 MySQL 官方自带的多实例管理工具,用它可以统一启动、停止、查询多个实例,避免了手动维护一堆进程 PID 的麻烦。
起实例的方式:
/usr/local/mysql/bin/mysqld_multi --defaults-file=/etc/my.cnf start 1-2这条命令会启动配置文件中[mysqld1]和[mysqld2]两个实例。停止实例用stop 1-2,查看状态用report 1-2。操作时建议指定--defaults-file,一来避免它读取系统其他位置的配置文件,二来解决多实例配置段读取不到的问题。
需要注意的是,如果实例正是通过mysqld_multi启动的,那么停止时它会用配置里的mysqladmin命令登录上去再执行 shutdown。这时[mysqld_multi]段下的user = root就必须是能登录 MySQL 的账号,最简单的做法是给本地 root 设置密码后,再准备一个专用的管理账号,权限只需要 shutdown 相关权限。如果密码不对,停止时会报mysqladmin: command failed。
所以更稳妥的多实例管理方式,其实是给每个实例分别创建一个 systemd unit 文件,把进程隔离做得更彻底。独立 unit 文件的方式虽然文件多了些,但每个实例的出问题、重启、查看日志都是完全隔离的,管理起来更符合 Linux 运维习惯。
拿 3307 实例举例,在/etc/systemd/system/mysql3307.service写入:
[Unit] Description=MySQL Server 3307 After=network.target [Service] Type=forking User=mysql Group=mysql PIDFile=/data/mysql3307/mysql.pid ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --socket=/data/mysql3307/mysql.sock --port=3307 --datadir=/data/mysql3307/data ExecStop=/bin/kill -s TERM $MAINPID Restart=on-failure [Install] WantedBy=multi-user.target用 systemd 管理的好处是实例之间日志独立,且如果某个实例因为异常退出,Restart=on-failure能自动拉起。后来我维护的几台多实例机器,已经很少再用mysqld_multi统一启停了,都是各自的 unit 文件独立管理,故障定位效率高很多。
4.3 多实例验证与日常维护命令
实例都起来后,不要直接跑业务,先做一轮连通性和数据逻辑验证。
# 1. 分别查看两个实例的进程是否存在,端口是否监听 ss -lntp | grep -E '3306|3307' # 2. 分别登录两个实例,确认端口和数据目录一致 mysql -uroot -p --socket=/data/mysql3306/mysql.sock \ -e "SHOW VARIABLES LIKE 'port'; SHOW VARIABLES LIKE 'datadir';" mysql -uroot -p --socket=/data/mysql3307/mysql.sock \ -e "SHOW VARIABLES LIKE 'port'; SHOW VARIABLES LIKE 'datadir';" # 3. 查看实例运行状态 mysqladmin -uroot -p --socket=/data/mysql3307/mysql.sock ping日常维护时,如果遇到需要临时关闭 3307 实例做维护的情况,可通过 socket 关闭:
mysqladmin -uroot -p --socket=/data/mysql3307/mysql.sock shutdown用 socket 而不是 TCP 端口来执行管理员操作,是避免网络异常时管理员无法登录的保底手段,数据库维护操作能走 socket 就别走 TCP。
5. 安装部署常见故障排查实录
5.1 初始化失败的四类经典报错
第一类:缺少共享库。初始化时报error while loading shared libraries: libaio.so.1,基本就是系统缺libaio。处理方式简单,装上就行。
第二类:数据目录非空。MySQL 初始化时发现目标目录下有文件就中止,报[ERROR] --initialize specified but the data directory has existing files, aborting.。这通常是因为目录根路径之前被当作别的用途用过,或者卸载旧 MySQL 后残留了文件。处理时必须先确认目录里没有需要的数据,然后把目录清空再重新初始化。绝对不能图省事直接换一个空目录,除非你真的确认旧目录里没有数据。
第三类:目录权限问题。报错信息里常见Cannot open as root或Permission denied。我的排查顺序很固定:先看/data/mysql{3306|3307}是不是mysql:mysql属主,再看把目录权限误设成 777 没有。生产环境不建议把数据目录设成 777,权限过大反而掩盖了后续真正的问题。
第四类:配置文件路径错误。mysqld: [ERROR] unknown variable 'defaults-file=/etc/my.cnf'这种不会出现,但会出现在你指定的--defaults-file路径不存在或者没有读取权限时。更隐蔽的是/etc/my.cnf本身存在,但basedir或datadir和你解压、创建的路径对不上,初始化时日志里会明确写出来,要逐行核对。
5.2 启动失败与连接失败问题速查
我把日常运维中真正高频的启动和连接问题归纳成了一张速查表:
| 现象 | 常见原因 | 处理建议 |
|---|---|---|
Can't connect to local MySQL server through socket | socket 文件路径不对,或 mysqld 还没启动成功 | 先看错误日志,再用--socket显式指定正确路径重试 |
Address already in use | 端口被其他进程占用 | 用ss -lntp确认占用进程,换端口或停掉冲突进程 |
ERROR 2003 (HY000) | 网络不通、bind-address 设置限制或防火墙拦截 | 确认目标端口可达,查看 bind-address 和防火墙规则 |
| 进程反复重启 | 数据目录损坏或配置里 pid-file 路径不存在 | 检查错误日志,确认相关目录存在且属主正确 |
mysqld_multi stop失败 | 管理账号密码错误或无 shutdown 权限 | 给管理账号赋SHUTDOWN权限,或改用 mysqladmin shutdown |
启动失败时第一反应该是去看错误日志,而不是反复启停。日志路径通过配置文件的log-error指定,没有指定时默认在数据目录下,文件名为hostname.err。我见过太多人面对一个起不来的 MySQL,操作了十分钟各种重启,最后发现日志里第一行就写着磁盘满了,白白浪费时间。
5.3 多实例特有的坑与规避方案
多实例部署最容易出的问题,几乎都集中在“配置文件读取”和“资源隔离”两块。
第一个坑是实例共用了一份错误日志。这个问题前面提过,实例独立进程但共用log-error文件路径时,日志会交错写入,排查某个实例的崩溃记录几乎不可能。解决方案就是每个实例独立的log-error、独立的 socket、独立的 pid 文件,这是在配置文件阶段就能避免的。
第二个坑是mysqld_multi读不到配置段。这个现象很典型:明明在/etc/my.cnf里写了[mysqld2],但启动 3307 实例时它报Unknown option或者直接当成没有配置。大概率是 MySQL 编译时默认的配置文件搜索路径顺序不是从/etc/my.cnf开始的,或者配置段名写错了。解决方法是启动时显式指定--defaults-file=/etc/my.cnf,同时把段名改成[mysqldN]的数字编号要连续,不要跳号。
第三个坑是资源隔离失效。多实例只是进程隔离,不是容器隔离。一个实例里的长事务、大查询,照样会把磁盘 IO 和 CPU 打满,影响同机器上其他实例。规避思路有两个层面:配置层面,给每个实例设置max_connections、innodb_buffer_pool_size,有条件时用cgroup限制 CPU 配额;运维层面,日常盯牢各实例的负载,一旦发现某个实例把整机资源吃满,优先通过 kill 掉对应的会话或迁移该实例来止损。
多实例方案的稳定程度,很大程度上取决于你有没有在部署前把资源和日志规划做好。我自己经历过一次印象很深的故障:某天一台机器上两个实例里的慢查询把磁盘 IO 打满,连带着第三个实例的写入全部超时,而日志文件又是共用的,排查了近两小时才发现是另一个实例里一个本来不该跑的定时任务在疯狂查表。后来把所有实例的日志、专用账号、资源限制全部拆干净之后,这种问题再没出现过。
在实际操作中,我的体会是:安装 MySQL 本身不难,难得是把目录、权限、配置、日志、端口这些基础项一次都做对。单实例部署关注的是目录规划和参数合理性,多实例部署关注的是隔离和资源预算。只要这些基础工作踏实了,后面无论是做主从复制、读写分离,还是换机器迁移数据,都会顺畅很多。
最后再分享一个小技巧:多实例机器上,建议给每个实例准备一个 shell 别名,类似alias mysql3307='mysql -uroot -p --socket=/data/mysql3307/mysql.sock'。别小看这点投入,多实例切换操作频率很高,每次手打一长串 socket 参数既容易打错,也会让自己烦躁。加个别名,日常操作舒服很多。