1. 为什么要在云主机上自建数据库
把 MySQL 装到云主机的 EC2 实例上,这件事听起来像是"把冰箱搬进已经带厨房的房子里",毕竟云厂商都提供了托管数据库服务。但我这些年经手的项目里,自建 MySQL 的比例依然不低,原因很实在:成本可控、参数可调、迁移自由,尤其是中小规模业务或者内部系统,一台中等配置的实例跑 MySQL 完全够用,账单还比托管服务便宜一大截。
这篇内容面向的是需要在一台云主机上从零把 MySQL 跑起来、并且要让它稳定跑下去的人。你会看到的不只是几条安装命令,而是从实例选型、系统准备、安装方式取舍、初始化安全配置,到远程访问、参数调优、备份恢复、日常巡检的完整链路。每一步我都会说清楚"为什么这么做",因为照着命令敲一遍谁都会,出问题的时候知道往哪看才是分水岭。
需要先明确一个前提:下面所有操作都基于主流的 Linux 发行版环境,具体命令以常见的包管理体系和官方仓库为准。不同发行版细节有差异,我会在关键处标注。另外,云主机和本地虚拟机的最大区别在于网络边界和磁盘性能,这两点会贯穿全文,很多坑都出在这里。
2. 动手前的整体设计与选型考量
2.1 实例规格与存储类型怎么定
很多人上来就开一台最低配实例装 MySQL,跑两天发现查询慢得离谱,回头怪数据库不行。实际上 MySQL 的性能瓶颈往往先出现在磁盘 IO 上,而不是 CPU。我的经验是:系统盘和数据盘分开,数据盘单独挂载一块,方便后续扩容和快照备份,也避免系统重装时误伤数据。
规格方面给一个粗略的参考区间,具体要结合你的数据量和并发来定:
| 场景 | vCPU | 内存 | 数据盘类型 | 说明 |
|---|---|---|---|---|
| 学习测试、小型内部系统 | 2 核 | 4GB | 通用型云盘 50GB | 够跑,别指望高并发 |
| 中小业务、日活几千 | 4 核 | 8GB | 高性能云盘 100GB | 建议内存不低于数据热集大小 |
| 读写较重的业务 | 8 核以上 | 16GB 起 | 高 IO 云盘 | 关注 IOPS 上限而非容量 |
内存这块有个常被忽略的点:MySQL 的 InnoDB 缓冲池(innodb_buffer_pool_size)通常设置为物理内存的 50% 到 70%。如果你只给 2GB 内存,缓冲池撑死 1GB 出头,稍微大点的表就频繁读盘,性能自然上不去。所以内存不是"够系统跑就行",而是要给数据库留足缓存空间。
存储类型上,云盘一般分通用型和高性能型,后者 IOPS 更高但单价更贵。判断标准很简单:如果你的业务有大量随机读写(比如订单、用户中心这类),选高性能;如果是日志归档、报表类偏顺序读写,通用型足够。别在存储上省小钱,磁盘 IO 打满导致的慢查询,排查起来能耗掉你一整天。
2.2 安装方式的三条路线对比
在 Linux 上装 MySQL,常见有三条路:系统包管理器直接装、官方仓库装、二进制包手动部署。我做个对照:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 系统自带源 | 一条命令搞定 | 版本旧,可能被替换成分支版本 | 快速验证、不挑版本 |
| 官方仓库 | 版本新、升级方便、依赖自动处理 | 需要额外配置仓库 | 绝大多数生产场景 |
| 二进制包 | 完全可控、可多版本共存 | 手动配置多、依赖要自己装 | 特殊版本需求、隔离部署 |
我个人的默认选择是官方仓库。原因很直接:版本可控,yum或apt能帮你处理依赖和后续小版本升级,出问题也有官方文档兜底。系统自带源里经常是分支版本或者老版本,某些新特性用不上,遇到兼容性问题还容易被绕进去。二进制包部署适合有明确版本锁定需求的团队,但对新手不友好,第一次搞容易在依赖和权限上卡住。
提示:无论走哪条路,装之前先确认系统里没有残留的旧版本数据库,否则端口冲突、数据目录混乱会让你怀疑人生。卸载旧版本时记得连配置文件和数据目录一起清理。
2.3 网络与安全组的前置规划
云主机和本地机器最大的不同,就是安全组这层虚拟防火墙。MySQL 默认端口 3306,如果你打算从外部连进来,安全组必须放行对应来源 IP。这里有个原则:能限定来源就绝不全开。把 3306 对0.0.0.0/0开放,等于把数据库门口挂个"欢迎光临",扫描器几分钟就能找上门。
我的做法是:应用服务器和数据库在同一内网时,安全组只放行内网网段;需要本地开发机直连时,临时把自己的出口 IP 加进去,用完删掉。如果团队人多,考虑走跳板机或者内网打通,别图省事全开。
另外要提前想清楚数据目录放哪。默认安装会把数据放在系统盘,我建议单独挂载数据盘后,把数据目录迁过去。这样做的理由有三:系统盘通常较小且扩容麻烦;数据盘可以独立做快照;系统出问题重装时数据不受影响。挂载和迁移的具体操作后面会讲。
3. 系统准备与安装实操全流程
3.1 系统初始化与依赖补齐
拿到一台新实例,第一件事不是急着装数据库,而是把系统基础环境理顺。先更新系统包索引,确保后续安装拿到的是最新依赖:
# 以常见的 RPM 系发行版为例 sudo yum update -y # Debian 系则为 sudo apt update && sudo apt upgrade -y然后确认几个基础工具在位:wget、curl、vim、tar,以及时间同步服务。时间同步这件事很多人忽略,但数据库的事务时间戳、主从复制、日志排查全都依赖准确的系统时间。云主机一般自带时间同步,但最好确认一下服务是开着的:
timedatectl status如果时间不同步,后面排查"为什么这条数据的时间对不上"时会非常痛苦。我踩过一次坑:实例时间漂了几分钟,导致应用侧的时间校验逻辑误判,查了大半天才发现是系统时间的问题。
接着处理数据盘。假设新挂载的云盘设备是/dev/vdb(具体设备名以实际为准,用lsblk确认),先格式化再挂载:
# 查看块设备,确认新盘设备名 lsblk # 格式化为 ext4(也可选 xfs,视团队习惯) sudo mkfs.ext4 /dev/vdb # 创建挂载点 sudo mkdir -p /data # 挂载 sudo mount /dev/vdb /data挂载完还要写进/etc/fstab实现开机自动挂载,否则重启后数据盘就"消失"了。这里有个关键细节:fstab 里建议用 UUID 而不是设备名,因为云主机的设备名在重启后可能变化,用设备名挂载失败会导致系统进紧急模式。用blkid查 UUID:
sudo blkid /dev/vdb # 输出类似:/dev/vdb: UUID="xxxx-xxxx" TYPE="ext4"然后在/etc/fstab追加一行(把 UUID 换成实际值):
UUID=xxxx-xxxx /data ext4 defaults,nofail 0 2nofail这个参数很实用,意思是挂载失败也不阻塞系统启动,避免因为一块盘的问题导致整台机器起不来。
3.2 通过官方仓库安装 MySQL
以 RPM 系为例,先添加官方仓库。不同大版本的仓库包不同,这里以常见的 8.0 系列为例(具体包名以官方页面为准):
# 下载并安装官方仓库配置包 sudo yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm # 确认仓库已启用 sudo yum repolist enabled | grep mysql仓库配好后直接安装服务端:
sudo yum install -y mysql-community-serverDebian 系的操作逻辑类似,先下载对应的deb配置包用dpkg -i安装,再apt update后安装mysql-server。安装过程中如果提示 GPG 密钥确认,按提示导入即可。
安装完成后,启动服务并设置开机自启:
sudo systemctl start mysqld sudo systemctl enable mysqld sudo systemctl status mysqld这里有个新手常卡住的点:首次启动后 root 密码在哪。8.0 系列在初始化时会生成一个临时密码,写在错误日志里。用下面的命令捞出来:
sudo grep 'temporary password' /var/log/mysqld.log拿到临时密码后立刻登录并修改,临时密码有有效期限制,拖太久会失效。登录:
mysql -u root -p进去后第一件事改密码,8.0 默认有密码强度策略,太简单的密码会被拒绝:
ALTER USER 'root'@'localhost' IDENTIFIED BY '你的强密码';注意:密码里别用特殊字符导致 shell 转义出问题,也别用纯数字或常见词。如果确实需要弱密码策略(比如内网测试环境),可以调整
validate_password相关参数,但生产环境不建议关。
3.3 数据目录迁移到独立数据盘
前面挂了/data,现在把 MySQL 的数据目录迁过去。默认数据目录一般是/var/lib/mysql。操作前先停服务,避免文件被占用导致拷贝不完整:
sudo systemctl stop mysqld # 拷贝数据(保留权限属性) sudo cp -a /var/lib/mysql /data/mysql # 确认拷贝完整后,原目录改名备份 sudo mv /var/lib/mysql /var/lib/mysql.bak然后修改配置文件。配置文件位置因发行版而异,RPM 系通常在/etc/my.cnf,Debian 系在/etc/mysql/mysql.conf.d/mysqld.cnf。在[mysqld]段落下加入:
[mysqld] datadir=/data/mysql socket=/data/mysql/mysql.sock改完还要处理一个容易被忽略的环节:SELinux。如果系统开了 SELinux,新目录的安全上下文不对,服务会起不来。要么给新目录打上正确的上下文标签,要么在确认安全的前提下调整策略。查看状态:
getenforce如果是Enforcing,给新目录设置上下文:
sudo semanage fcontext -a -t mysqld_db_t "/data/mysql(/.*)?" sudo restorecon -Rv /data/mysqlsemanage命令如果不存在,需要先装policycoreutils-python-utils之类的工具包。这一步不做,日志里会报权限拒绝,服务反复重启,很多人会误以为是配置文件写错了。
配置改完启动服务,验证数据目录是否生效:
sudo systemctl start mysqld mysql -u root -p -e "SHOW VARIABLES LIKE 'datadir';"输出指向/data/mysql/就说明迁移成功了。确认无误后再删掉备份目录,别急着删,留几天观察。
4. 初始化安全配置与远程访问打通
4.1 跑一遍安全初始化脚本
MySQL 自带一个mysql_secure_installation脚本,交互式地帮你处理几件安全相关的事:删匿名用户、禁止 root 远程登录、删测试库、重载权限表。装完数据库第一件事就该跑它:
sudo mysql_secure_installation按提示走,几个关键选择我说明一下。是否删除匿名用户,选是,匿名用户是历史遗留的安全隐患;是否禁止 root 远程登录,选是,root 只允许本机登录,远程管理走普通账号加权限控制;是否删除测试库,选是,test库谁都能访问,留着没意义。
这个脚本跑完,基础安全面就干净了不少。但要注意,它不能替代后续的账号权限规划,只是把明显的口子堵上。
4.2 创建业务账号与最小权限分配
生产环境绝不该用 root 跑业务。正确做法是按业务建独立账号,只给必要的库和权限。比如给一个应用建账号,只允许它操作某个库:
CREATE USER 'app_user'@'%' IDENTIFIED BY '强密码'; GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'%'; FLUSH PRIVILEGES;这里的'%'表示允许从任意主机连接,配合安全组限制来源 IP 使用。如果应用和数据库在同一内网,更严谨的写法是限定内网网段,比如'app_user'@'10.0.1.%'。权限上遵循最小够用原则:只读业务给SELECT,不要图省事给ALL PRIVILEGES。我见过太多因为账号权限过大,一个注入漏洞就把整库拖走的情况。
账号建好后,用SHOW GRANTS确认权限范围符合预期:
SHOW GRANTS FOR 'app_user'@'%';4.3 远程连接的三道关卡
从外部连不上 MySQL,九成是卡在三个地方,按顺序排查效率最高。
第一道是安全组。云控制台里确认实例绑定的安全组入方向规则放行了 3306,来源限定为你的 IP 或内网网段。这一步在云平台侧,和系统内部无关,但最容易被忘。
第二道是MySQL 监听地址。默认配置可能只监听127.0.0.1,外部自然连不上。检查:
SHOW VARIABLES LIKE 'bind_address';如果是127.0.0.1,改配置文件里的bind_address为0.0.0.0(或指定内网 IP),然后重启服务。改成0.0.0.0意味着监听所有网卡,安全性靠安全组和账号来源限制来兜底。
第三道是账号的 host 范围。前面建账号时如果写的是'app_user'@'localhost',那只能本机连。远程连接需要'%'或具体网段。用下面命令看现有账号的 host:
SELECT user, host FROM mysql.user;三道关卡逐一确认,基本没有连不上的情况。如果还不行,用telnet或nc测端口通不通,先分清是网络层还是数据库层的问题:
nc -zv 你的实例IP 3306端口不通就是网络或安全组问题,端口通但连不上就是账号权限或监听配置问题,方向一下就清晰了。
5. 参数调优与日常运维要点
5.1 几个真正影响性能的核心参数
MySQL 的参数几百个,但真正对性能影响大的就那么几个。我按重要性排一下,并给出计算思路。
innodb_buffer_pool_size是头号参数,决定 InnoDB 缓存数据和索引的内存大小。经验值是物理内存的 50% 到 70%,专用数据库机器可以取高值。比如 8GB 内存的实例,可以设 5GB 左右。设太小,缓存命中率低,频繁读盘;设太大,挤占系统内存导致 swap,反而更慢。修改后需要重启生效。
innodb_log_file_size影响写性能,日志文件大一些能减少检查点频率,但恢复时间会变长。一般设 256MB 到 1GB 之间,具体看写入量。这个参数在 8.0 之后调整方式有变化,改之前查一下对应版本的官方说明。
max_connections是最大连接数,默认 151。设太小,高并发时应用报"too many connections";设太大,每个连接都占内存,可能把内存吃光。粗略估算:每个连接约占几 MB 内存,结合实例内存和业务并发来定。我一般会配合连接池使用,应用侧控制连接数,数据库侧留一定余量。
innodb_flush_log_at_trx_commit关系到数据安全和性能的权衡。值为 1 时每次提交都刷盘,最安全但最慢;值为 2 时每秒刷一次,性能好一些但极端情况下可能丢一秒数据;值为 0 性能最好但最不安全。金融类业务必须为 1,普通业务可以评估后选 2。
改参数的方式有两种:临时改(SET GLOBAL,重启失效)和永久改(写配置文件)。生产环境一律写配置文件,改完重启验证:
[mysqld] innodb_buffer_pool_size=5G max_connections=500 innodb_flush_log_at_trx_commit=1提示:改参数一次只改一个,改完观察一段时间再改下一个。同时改多个参数,出问题根本不知道是哪个引起的。
5.2 备份策略:别等出事才想起它
自建数据库最怕的就是"数据没了"。云盘有快照,但快照是整盘级别的,恢复粒度粗,而且不能替代逻辑备份。我的建议是物理快照加逻辑备份双保险。
逻辑备份用mysqldump,适合中小数据量,备份出来是 SQL 文件,跨版本迁移也方便:
mysqldump -u root -p --single-transaction --routines --triggers --events app_db > /data/backup/app_db_$(date +%F).sql--single-transaction这个参数很关键,它让备份在一致性快照下进行,不锁表,对在线业务友好。但要注意它只对 InnoDB 有效,如果库里有 MyISAM 表,还是会有锁。--routines、--triggers、--events把存储过程、触发器、事件一起备出来,不然恢复后发现这些对象全丢了。
数据量大到mysqldump跑不动时(比如几十 GB 以上),考虑物理备份工具,备份和恢复速度都快得多,但版本兼容性要求更严。
备份做完一定要验证能恢复。我见过备份文件天天生成,真出事时发现文件是空的或者损坏的。定期拿备份到测试环境恢复一遍,这个习惯能救命。备份文件也别只放本机,传到对象存储或者另一台机器上,本机磁盘挂了备份跟着一起没的例子不少。
5.3 日常巡检看什么
数据库跑起来不是终点,日常得盯着几个关键指标。我列一个巡检清单,按优先级排:
| 检查项 | 命令/方法 | 关注点 |
|---|---|---|
| 连接数使用率 | SHOW STATUS LIKE 'Threads_connected' | 接近 max_connections 要警惕 |
| 慢查询 | 慢查询日志 | 定期分析,找索引缺失 |
| 磁盘空间 | df -h | 数据盘剩余低于 20% 要处理 |
| 主从延迟(如有) | SHOW SLAVE STATUS | 延迟持续增大要排查 |
| 错误日志 | /var/log/mysqld.log | 有无反复出现的报错 |
慢查询日志建议默认开着,设一个合理的阈值(比如 2 秒),定期用mysqldumpslow或类似工具分析。很多性能问题不是突然出现的,而是慢查询日积月累。磁盘空间也要盯紧,binlog 和慢日志都可能悄悄把盘吃满,盘满了数据库直接写不进去,业务瞬间挂掉。
6. 常见问题排查与避坑实录
6.1 服务起不来的排查顺序
MySQL 启动失败是最常见的问题,排查有固定顺序,按这个走能少走弯路。
先看错误日志,这是最直接的线索,位置一般在/var/log/mysqld.log或数据目录下。日志里通常写得很清楚:权限问题、端口占用、配置语法错误、数据目录不存在等。
如果是权限问题,检查数据目录的属主是不是 mysql 用户:
ls -ld /data/mysql属主不对就用chown -R mysql:mysql /data/mysql修正。迁移数据目录后这个问题特别常见。
如果是端口占用,看谁占了 3306:
sudo ss -lntp | grep 3306有可能是之前没卸载干净的旧实例还在跑,或者别的服务占用了端口。
如果是配置语法错误,日志里会指出哪一行有问题。改配置文件时手滑多打个字符、少个等号,都会导致启动失败。改完配置养成用mysqld --validate-config之类的方式先校验的习惯(具体命令看版本支持)。
6.2 连接慢与连接被拒的区分
"连不上"和"连得慢"是两回事,排查方向完全不同。
连接被拒(Connection refused)通常是服务没起、端口没监听、安全组没放行,属于"根本没通到数据库"。连接超时(Connection timed out)多半是网络层或安全组丢包,包发出去了没回应。而"能连上但很慢",往往是 DNS 反解、权限表过大、或者网络延迟的问题。
MySQL 有个经典坑:客户端连接时默认会做反向 DNS 解析,如果 DNS 配置不当,每次连接都要等解析超时,表现为连接极慢。解决办法是在配置里加skip-name-resolve,跳过反解,代价是账号的 host 只能用 IP 不能用域名。内网环境我一般直接开这个参数,连接速度立竿见影。
6.3 磁盘写满的应急处理
数据盘写满,数据库会直接拒绝写入,业务报错。应急处理分两步:先腾空间,再查原因。
腾空间优先删可再生的文件:binlog(如果确认不需要用于恢复或主从)、慢查询日志、旧的备份文件。binlog 清理用 SQL 命令而不是直接删文件,直接删会导致索引文件不一致:
-- 查看当前 binlog 列表 SHOW BINARY LOGS; -- 清理指定时间之前的 PURGE BINARY LOGS BEFORE '2024-01-01 00:00:00';腾出空间后查根因:是数据增长过快、binlog 没设过期、还是日志没轮转。binlog 可以设自动过期天数(binlog_expire_logs_seconds),避免无限增长。慢日志也要配轮转,不然一个文件能长到几十 GB。
6.4 常见问题速查表
| 现象 | 可能原因 | 处理方向 |
|---|---|---|
| 服务启动失败 | 数据目录权限、配置语法、端口占用 | 看错误日志逐项排除 |
| 远程连不上 | 安全组、bind_address、账号 host | 三道关卡依次确认 |
| 连接很慢 | DNS 反解、权限表过大 | 开 skip-name-resolve |
| 写入报错 | 磁盘满、表空间满 | 清 binlog 和日志,查增长原因 |
| 查询突然变慢 | 索引失效、统计信息过期、锁等待 | 看慢日志和执行计划 |
| 内存持续上涨 | 连接泄漏、缓冲池配置过大 | 查连接数,评估参数 |
7. 我个人的几条实操心得
装数据库这件事,命令本身不难,难的是把每个环节的"为什么"想清楚。我这些年踩过的坑里,印象最深的有这么几个。
一是别在系统盘上放数据。早期图省事,数据目录没迁,结果系统盘扩容折腾了半天,还差点因为重装系统把数据弄丢。后来所有实例一律单独挂数据盘,这个习惯救过我好几次。
二是安全组别图方便全开。有次测试环境图省事把 3306 对全网开放,第二天日志里全是暴力破解尝试,虽然没被攻破,但那种"门没锁"的感觉很不好。现在我的原则是:临时开的规则用完立刻删,能限定来源就绝不放宽。
三是备份一定要验证恢复。备份文件天天生成不代表备份有效,我见过备份脚本因为权限问题一直生成空文件的案例,直到真需要恢复时才发现。现在我的做法是每月至少拿一份备份到测试环境完整恢复一次,跑几条校验查询确认数据完整。
四是参数别一次改太多。调优是个渐进过程,一次改一个参数,观察至少一天再动下一个。同时改五个参数,性能上去了不知道谁的功劳,出问题了也不知道该回滚哪个。
最后说一个容易被忽略的点:记录你的操作。每次改配置、迁目录、调参数,都在一个文档里记一笔,写清楚改了什么、为什么改、怎么回滚。时间一长,这些记录就是你的排障地图,比任何记忆都可靠。数据库运维这件事,稳比快重要,可回滚比可优化重要。