这篇博文来源于一次实打实的 mysql 部署:一台刚开好的 Ubuntu 22.04 云服务器,从零开始安装和配置 MySQL 8.0,中间踩了不少老教程的坑。Ubuntu 22.04 默认软件源里带的 MySQL 已经是 8.0 系列,网上大量“mysql 5.7 ubuntu 安装”的教程照搬过来,不是报错就是权限混乱。所以我把完整过程、参数含义、排错思路都整理出来,适合刚接手 Linux 服务器的新手,也适合从 MySQL 5.7 迁移过来的老手做个对照。
需要说明的是,这篇文章里我默认你已经能用 SSH 登录服务器,并有 sudo 权限。如果是在本地虚拟机操作,逻辑完全一样,只是不需要考虑防火墙安全组的部分。
1. 安装前的认知准备:Ubuntu 22.04 与 MySQL 8.0 的关键特性
在敲apt install之前,我建议先把版本和源的问题讲清楚。因为在这里选错,后面每一步都会跟着错。
1.1 为什么推荐 Ubuntu 22.04 LTS + MySQL 8.0
Ubuntu 22.04 LTS 是长期支持版本,官方维护周期到 2027 年,这对生产服务器非常重要。它的默认 apt 软件源里打包的 MySQL 就是 8.0 系列,而不是 5.7,也不是 8.1 或更新版本。也就是说,用系统自带源进行 mysql 部署,你得到的是一套经过发行版充分测试、与系统服务兼容性最好的 MySQL 8.0。
MySQL 8.0 对比 5.7 有几个绕不开的变化:
- 默认字符集变成了
utf8mb4,建表时哪怕不写字符集也不会再出现中文乱码问题。 - 默认身份认证插件从
mysql_native_password换成了caching_sha2_password,这个变化直接导致很多 5.7 时代的连接字符串、老版本客户端全部失效。 - 查询缓存
query_cache被彻底移除,老教程里的query_cache_type=1配置写了等于白写,甚至会让实例拒绝启动。 - 数据字典变成了统一的系统表存储,DDL 操作是原子的,
INFORMATION_SCHEMA的很多查询行为和 5.7 不同。 - 新增了窗口函数、公用表表达式(CTE)等现代 SQL 功能。
对于部署者来说,最需要记住的就是认证插件和 query cache 这两个差异。因为网上 80% 的 MySQL 安装教程都是 5.7 时代写的,你拿着 8.0 去执行,失败点基本都集中在这两个地方。
1.2 用 Ubuntu 官方源还是 MySQL 官方源
安装方式上,最常见的是两种:直接用 Ubuntu 官方源安装,或者手动添加 MySQL 官方 APT 源安装。
Ubuntu 官方源的好处是简单、稳定,一条apt install mysql-server搞定,补丁跟随系统安全更新一起发布,和系统的 systemd 服务、AppArmor 策略、日志轮转都配合得很好。缺点是版本相对保守,比如你装的时候源里可能是 8.0.34、8.0.35,不会第一时间追上 MySQL 官方发布的最新小版本。
MySQL 官方 APT 源可以装到最新版,甚至可以选 8.4 LTS 这类不同版本线,但你需要手动添加 GPG 密钥、配置源文件。而且官方源的包对系统的集成不如发行版源那么“原生”,AppArmor、日志路径有时候要自己调。我个人的建议是:如果你的需求没有特殊到必须用最新小版本,直接用 Ubuntu 官方源最省心。等到真要升级到 8.4,再手动迁也不迟。
1.3 安装前的环境检查清单
不要一上来就apt install,先花两分钟检查环境。以下是我每次部署前都会执行的一组命令:
# 确认系统版本 lsb_release -a # 检查是否已经存在 mysql 或 mariadb 残留 dpkg -l | grep -iE 'mysql|mariadb' # 检查 3306 端口是否被占用 ss -lntp | grep 3306 # 检查磁盘空间,数据目录所在分区尤其要注意 df -h /var/lib/mysql # 检查内存 free -h内存方面,如果只有 1GB 以内,MySQL 8.0 可以跑,但很容易在压力下触发 swap。建议至少 2GB,4GB 以上做生产更舒服。磁盘方面,安装本身需要约 1GB 空间,但数据目录会持续增长,我给/var/lib/mysql所在分区至少留出 3GB 的起步余量。另外检查一下/var/lib/mysql目录是否存在,如果之前有过一次失败的初始化,残留的文件会影响后续安装。
2. 实际操作:一步步完成 MySQL 的安装与初始化
环境检查没问题,就可以正式安装了。这一部分的操作顺序很重要,尤其是首次登录账号的理解。
2.1 通过 apt 安装 mysql-server
先更新软件源索引,保持最新状态:
sudo apt update如果系统刚开出来,可以考虑顺带升级已有软件包,但注意apt upgrade可能升级内核并要求重启。如果不想折腾,直接跳过升级,只执行安装也没问题。安装:
sudo apt install -y mysql-server装完后会自动启动服务,不需要额外手动 start。检查一下:
systemctl status mysql --no-pager正常情况下输出里会出现active (running)。再确认版本:
mysql --version我这次装到的版本是 8.0.35,不同时间安装会有差异,但一定是以 8.0 开头。
安装包会自动创建一份目录结构:配置文件放在/etc/mysql/,其中/etc/mysql/mysql.conf.d/mysqld.cnf是主要参数文件;数据目录在/var/lib/mysql/;错误日志默认在/var/log/mysql/error.log。这几个路径后面排错经常用到。
还要检查一下服务是否会随系统自启:
systemctl is-enabled mysql输出enabled即可。如果因为某些原因没开启,执行sudo systemctl enable mysql。
2.2 初始化与 root 账号登录机制
这一步是新手最容易懵的地方。Ubuntu 官方源安装的 MySQL 8.0,root@localhost默认使用的是auth_socket认证插件。也就是说,你直接执行mysql -u root -p输了密码也进不去,压根就没有密码;但用系统 root 权限执行sudo mysql却能直接免密进入管理界面。
很多老教程在这一步会让新用户怀疑自己装错了版本。其实不是,这是 Ubuntu 打包策略和 MySQL 默认行为结合的结果。auth_socket 插件做的事简单说就是:只允许本地通过 socket 文件连接,并且校验操作系统的用户身份。你用的是 unix 账号 root,才允许以 MySQL root 身份进入。这比密码登录更安全,但很多人不习惯。
我第一次装完也是愣住了,测试几个密码全提示ACCESS DENIED。遇到这种情况,正确操作是用系统 sudo 身份先进去,然后给 root 设置密码并切换成密码认证:
sudo mysql进入 mysql 控制台后执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'YourStrong_Password123'; FLUSH PRIVILEGES;解释一下这条语句:IDENTIFIED WITH caching_sha2_password明确指定了 8.0 默认的认证插件,如果你想恢复成 5.7 风格的mysql_native_password,把这里替换成IDENTIFIED WITH mysql_native_password BY ...再执行一遍就行。不过我不推荐为了省事去改回旧插件,除非客户端确实不支持新插件,这个下面会展开讲。
设置完成后,退出再重新登录:
exit mysql -u root -p输入刚才设置的密码,能进入就说明初始化成功了。
2.3 验证服务状态与基础连接
服务正常、root 可登录后,做一轮基础验证:
SELECT VERSION(); SELECT CURRENT_USER(); SHOW DATABASES;输出里应该能看到mysql、sys、performance_schema、information_schema四个默认库。如果SHOW DATABASES里出现多余的测试库或者匿名用户能访问的系统信息,说明安全加固还没做。
如果此时遇到最常见的ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2),表示服务没起来或者 socket 文件路径不对。优先看状态和日志:
systemctl status mysql --no-pager sudo tail -n 50 /var/log/mysql/error.log大部分启动失败问题都能在 error.log 里找到直接原因。比如日志目录权限不对、磁盘空间满、之前的残留 datadir 导致初始化失败等。
3. 安全配置:从默认账号到生产可用的加固过程
安装好后默认状态还太“裸”,接下来这一步是很多教程一笔带过但生产环境必须做扎实的环节。
3.1 mysql_secure_installation 脚本逐个解释
MySQL 自带的安全加固脚本能自动完成大半重复劳动:
sudo mysql_secure_installation脚本启动后会依次问几个问题,我逐个说下最合理的回答:
- 是否安装密码校验组件
VALIDATE PASSWORD COMPONENT?测试环境可以选 No,生产环境建议选 Yes 并设置 1/2/3 的强度等级。选 2 是严格模式,对长度、大小写、数字、特殊字符都有要求,可能会对业务方现有密码造成冲击,酌情使用。 - 是否修改 root 密码?如果你已经通过 2.2 节设置过,可以选 N 跳过;如果还没设置,选 Y。
- 是否移除匿名用户?必须选 Y。匿名用户意味着任何本地用户都能以空密码试探连接。
- 是否禁止 root 远程登录?必须选 Y。root 只在 localhost 使用,这是生产铁律。
- 是否移除 test 数据库并回收匿名用户对它的访问权限?建议选 Y。test 库在很多安装版本里连匿名用户都能随意访问。
- 是否现在重新加载权限表?选 Y,让前面的修改立即生效。
脚本跑完后,再用sudo mysql -e "SELECT user,host,plugin FROM mysql.user;"看一眼用户表,正常情况下应该只有root@localhost和mysql.infoschema、mysql.session、mysql.sys这几个系统内置账号。
3.2 创建独立业务账号并遵循最小权限
很多人部署完就继续用 root 给业务系统连接数据库,这是非常危险的做法。业务连接应该使用专用账号,并且权限只给业务需要的库和操作。
我通常的做法是,为每个业务单独建库、单独建用户。比如业务库叫app_db,账号app_user:
CREATE DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; CREATE USER 'app_user'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'App_User_Password_123'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP ON app_db.* TO 'app_user'@'localhost'; FLUSH PRIVILEGES;注意几点:
@'localhost'表示只允许从本机连接。如果应用和数据库在同一台机器,就这么写。- 授权时不要给
ALL PRIVILEGES ON *.*,应该精确到app_db.*,甚至在不需要DROP/ALTER的情况下把它们去掉。 - 如果以后需要远程连接,单独创建
@'%'或@'192.168.1.%'的账号,不要直接改app_user@localhost。
我见过不少团队图省事用 root 连库,一旦被注入或者密码泄露,整个实例任人操作。这个习惯应该从一开始就纠正。
3.3 认证插件选择:caching_sha2_password 与 mysql_native_password
前面多次提到认证插件,这里集中讲明白。MySQL 8.0 默认caching_sha2_password基于 SHA-256 的挑战-响应机制,安全性更高,但它不是所有客户端都原生支持。常见不兼容的有:
- PHP 7.4 之前版本的
pdo_mysql和mysqli扩展。 - 一些老版本 JDBC 驱动,需要额外加
allowPublicKeyRetrieval=true和useSSL=true。 - 老版本的图形化管理工具,比如很多绿色版 Navicat。
如果应用侧确实无法升级,只能把用户改成mysql_native_password:
ALTER USER 'app_user'@'%' IDENTIFIED WITH mysql_native_password BY 'App_User_Password_123';但我的建议是优先升级客户端,而不是降级认证。因为 MySQL 官方已经在后续版本中把mysql_native_password标记为废弃,迟早会被移除。你为了兼容一个老客户端让整个账密体系退回到旧标准,后面还得返工。
还有一个细节:使用caching_sha2_password做远程连接时,首次认证为了获取服务端公钥可能要求 SSL 或 RSA 公钥交换。如果 SSL 没配好,客户端在首次连接时会慢或者失败。MySQL 8.0 要求比较严格,不像 5.7 那样宽松。遇到Authentication plugin 'caching_sha2_password' cannot be loaded或Public Key Retrieval is not allowed这种报错,优先去客户端配置里找 enable 公钥获取的开关,不要一上来就改数据库插件。
4. 远程连接与网络访问的常见坑
部署完成后,业务方往往需要从别的机器连接数据库。远程连接配置是踩坑重灾区,经常三四处配置互相打架,本地能连、远程连不上。
4.1 bind-address、用户 host、防火墙三处配合
远程连接能不能通,取决于三处设置同时满足:
- MySQL 是否监听外部地址。
- 用户 host 是否匹配客户端来源 IP。
- 系统防火墙和云安全组是否放行 3306 端口。
先看绑定的监听地址。默认bind-address = 127.0.0.1,只允许本机访问。修改/etc/mysql/mysql.conf.d/mysqld.cnf:
bind-address = 0.0.0.0然后重启:
sudo systemctl restart mysql再看用户 host。在 MySQL 里执行:
SELECT user, host FROM mysql.user;如果业务用户是app_user@'%',表示任何地址都能匹配;如果是app_user@'192.168.1.%',只有该网段能匹配。注意'%'的通配含义是不包含 localhost 以外的所有主机,通常和bind-address配合没问题,但如果你本机也需要用这个用户连接,本地连接走 socket 反而匹配不到'%',还得单独建一个'localhost'用户。这个细节很多人会忽略。
最后是防火墙:
sudo ufw allow from 192.168.1.0/24 to any port 3306 proto tcp sudo ufw reload如果用的是云服务器,还要到控制台的安全组确认 TCP 3306 放行。这里的建议是不要把端口裸奔暴露到公网,来源 IP 能缩小到业务机器的网段就缩小。
4.2 连接测试与客户端支持问题
从远程机器测试连接:
mysql -h 数据库服务器IP -u app_user -p我整理了一份按错误码排查的表,基本覆盖远程连接失败的常见原因:
| 错误信息 | 可能原因 | 处理方向 |
|---|---|---|
ERROR 2003 (HY000): Can't connect to MySQL server on 'x.x.x.x' | 3306 不通或被防火墙拦截 | 检查 bind-address、ufw、云安全组 |
ERROR 1045 (28000): Access denied for user 'app_user'@'x.x.x.x' | 用户名密码错误,或 host 不匹配 | 核对账号密码,检查 mysql.user 里的 host |
ERROR 1130 (HY000): Host 'x.x.x.x' is not allowed to connect | 用户 host 限制严格 | 调整成'%'或包含该来源 IP 的网段 |
ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded | 客户端不支持新认证插件 | 升级客户端,或临时切换 mysql_native_password |
| 连接后执行 SQL 非常慢,几秒才返回 | DNS 反向解析导致 | 考虑开启 skip-name-resolve 并用 IP 作为 host |
4.3 DNS 反向解析和 skip-name-resolve
这个坑比较隐蔽。默认情况下,MySQL 接受到客户端连接后,会对来源 IP 做反向 DNS 解析。如果 DNS 服务器响应慢,连接就会卡住。日志里经常出现Aborted connection ... (Got an error reading communication packets)。
解决方法是在mysqld.cnf的[mysqld]段加入:
skip-name-resolve但开了这个参数后,mysql.user表里的 host 就不能再写域名,只能用 IP 或'%'。例如原本'webapp'@'app.internal.example.com'会失效。所以在纯内网 IP 环境我建议开,能明显提升连接速度和稳定性;如果 host 里有很多域名形式的配置,那就先清理再开。
5. 数据目录迁移、备份恢复与运维基础
前面的配置解决的是“能用”,这一节解决的是“数据放哪里、出事了怎么救”。这两件事在生产里比安装本身重要得多。
5.1 把 datadir 迁移到独立磁盘
很多云服务器默认把数据写在系统盘,而系统盘空间小、IO 可能也不够。生产环境通常会挂一块独立数据盘,把 MySQL 的数据目录迁过去。这个操作并不复杂,坑主要在 AppArmor 上。
步骤如下:
# 1. 停服务 sudo systemctl stop mysql # 2. 迁移数据目录,假设数据盘已挂载到 /data sudo mkdir -p /data/mysql sudo rsync -av /var/lib/mysql/ /data/mysql/ sudo chown -R mysql:mysql /data/mysql sudo chmod 750 /data/mysql # 3. 修改 datadir sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf # 在 [mysqld] 段下写入: # datadir = /data/mysql # 4. 验证配置 sudo mysqld --validate-configmysqld --validate-config这一行不能省,它会提前发现参数文件里的语法错误和路径问题,比直接 restart 后拿日志排错高效得多。
然后要处理 AppArmor。Ubuntu 22.04 默认对 mysqld 启用了 AppArmor 策略,只允许访问/var/lib/mysql/下的路径。迁移后不改策略,启动会直接失败,日志里出现类似AppArmor parser error或Permission denied。
常见做法是给 mysqld 增加一个别名映射。编辑/etc/apparmor.d/tunables/alias,加入:
alias /var/lib/mysql/ -> /data/mysql/,然后 reload:
sudo systemctl reload apparmor sudo systemctl start mysql如果都顺利,用systemctl status mysql和df -h确认服务在跑、目录真的在使用。很多人启动失败就以为是 MySQL 配置错了,其实只差 AppArmor 这一层。
5.2 备份方案设计:mysqldump 与 binlog 增量
备份不能只停留在“偶尔导出一次”的层面。MySQL 8.0 默认开启了 binlog,这个设计天然支持增量恢复,只要你有全量备份 + 后续的 binlog,就能回放到任意时间点。
全量备份我建议用mysqldump,关键参数要写全:
mysqldump \ --single-transaction \ --routines \ --events \ --triggers \ --hex-blob \ --databases app_db \ | gzip > app_db_$(date +%F).sql.gz参数含义依次是:--single-transaction用 InnoDB 的一致性快照,不锁业务表;--routines导出存储过程和函数;--events导出事件调度器;--triggers导出触发器;--hex-blob保证二进制数据导出后再导入不损坏;--databases指定导出哪些库。
binlog 的过期时间设置,MySQL 8.0 里不要再用expire_logs_days,那个参数在 8.0 已废弃,改用:
binlog_expire_logs_seconds = 604800表示保留 7 天。配合每天一次全量备份,最多只需要回放 1 天的 binlog。
自动化部分我用 crontab:
0 3 * * * /usr/local/bin/backup_mysql.sh >> /var/log/mysql_backup.log 2>&1脚本里做三件事:执行 mysqldump、检查备份文件是否非空、删除 15 天前的旧备份文件。别小看“检查备份文件非空”这一步,我遇到过磁盘满导致备份文件只有 0 字节,但 crontab 日志全绿的情况。
5.3 恢复演练与从 5.7 迁移的注意
备份的目的是恢复,所以恢复演练必须做过才算数。至少每个季度做一次“删掉一个业务库,再从备份恢复”的演练。
恢复命令本身很简单:
gunzip < app_db_2025-XX-XX.sql.gz | mysql -u root -p或者进入 mysql 后source /path/backup.sql。如果要在恢复时先清掉现有库,执行DROP DATABASE再CREATE DATABASE。
从 MySQL 5.7 导出的备份往 8.0 恢复时,容易遇到两个问题,提前说明:
- 5.7 导出的
mysql_native_password用户虽然在 8.0 里还能认,但官方已经在后续版本标记废弃,迟早要转。 - 如果 5.7 实例开了 GTID,备份文件里可能包含
SET @@GLOBAL.GTID_PURGED,导入 8.0 时可能会报错。常见处理是在导出时加--set-gtid-purged=OFF,简单省事。
另外 5.7 的utf8字符集原样导过来还是utf8,8.0 里并不会自动升级成utf8mb4。想彻底切换到utf8mb4,要额外处理:改库的默认字符集、改表的字符集、改列的字符集,顺序不能反。
6. 核心参数调优与排错实录
最后一部分聊聊部署后一定要做的参数调整,以及几个容易让运维人员“啊”一声的坑。
6.1 内存与缓冲池配置
MySQL 8.0 默认配置很保守,装完直接扛业务大概率性能不行。第一个要动的是 InnoDB 缓冲池,它决定数据和索引页在内存里的缓存大小,是读写性能最关键的一个参数。
合理取值通常为物理内存的 50%~70%,但不能不考虑系统上是否还有其他服务。2GB 内存的机器,我给 1G;4GB 内存,给 2G。修改/etc/mysql/mysql.conf.d/mysqld.cnf:
[mysqld] innodb_buffer_pool_size = 2G修改后重启,验证:
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';还有一个容易被忽略的设置是innodb_buffer_pool_instances。缓冲池大于 1G 时建议拆成多个实例,减少并发访问竞争:
innodb_buffer_pool_instances = 4注意这个参数只有在innodb_buffer_pool_size大于 1G 时生效,并且实例数一般是缓冲池大小往 1G 取整,比如 2G 对应 2~4 个实例比较合理。
6.2 连接数、慢查询日志与超时
默认的max_connections是 151,对大多数中小业务够用,但如果接入的微服务多、连接池配置不当,会出现Too many connections。
合理调法:
max_connections = 500我还喜欢顺手开慢查询日志,排查应用 SQL 问题的时候非常有用:
slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow-query.log long_query_time = 2long_query_time单位是秒,超过 2 秒的 SQL 会记录。随后用自带工具分析:
mysqldumpslow /var/log/mysql/slow-query.log注意long_query_time设成 2 秒在生产可以,开发环境可能刷屏,先设 10 再逐步收窄。
另外把空闲超时和交互超时调成合理值,防止无意义的连接占用:
wait_timeout = 600 interactive_timeout = 600如果max_connections调太高,内存压力会很大。每个连接大致占用几 MB 到几十 MB 内存,取决于排序缓冲、连接缓冲等参数。所以这组参数要一起看,别只盯一个。
6.3 我踩过的三个部署大坑
最后把我实际踩过的几个坑放出来,每个都值得花时间理解原理。
第一个是lower_case_table_names。有次部署完直接导入了一个在 Windows 上开发的业务库,表名是大写字母开头的。导入后应用查询时表名变成了小写,MySQL 直接报Table doesn't exist。这个问题的根源是lower_case_table_names参数默认在 Linux 上是 0,也就是大小写敏感,而 Windows 上默认是 1,大小写不敏感。
但这个参数在 MySQL 8.0 里极其特殊,它必须在数据目录初始化之前指定,初始化后再改会直接导致服务无法启动。也就是说,部署时就要决定好未来是否需要大小写不敏感:
[mysqld] lower_case_table_names = 1如果装完后再发现业务库大小写混乱,最稳妥的办法是备份后重建实例,把参数写在初始化前。这是我吃过的一次大亏,没有捷径。
第二个坑是 root 改用密码认证后,时间一长忘了密码。这个在很多机器上都会发生,尤其是那些一年半载才维护一次的服务器。解决方法是先停服务,使用跳过授权表的模式临时启动:
sudo systemctl stop mysql sudo mysqld_safe --skip-grant-tables --skip-networking &然后用sudo mysql进去改密码,改完后重启服务。注意给这个临时启动的 mysqld 加上--skip-networking,避免暴露给外部网络。
第三个坑是修改 my.cnf 参数后服务起不来,最后发现是权限模式问题。有一次我把自定义配置文件放到了/etc/mysql/conf.d/custom.cnf,顺手用了sudo vim保存,文件权限变成 664,而 MySQL 对配置文件权限有严格要求,world-writable 的配置文件会被拒绝。把权限改成 644、属主改成 root 后正常启动:
sudo chown root:root /etc/mysql/conf.d/custom.cnf sudo chmod 644 /etc/mysql/conf.d/custom.cnf这类问题在 error.log 里并不会直接提示“文件权限不对”,只会笼统地显示World-writable config file '/etc/mysql/conf.d/custom.cnf' is ignored,所以排查时务必看完整错误日志,而不是只看最后几行。
说到底,MySQL 部署这套流程本身并不难,真正决定线上稳定性的,是安装前的版本认知、安装后的安全加固,以及备份恢复这套兜底机制。上面这些内容覆盖了我从零部署一台 Ubuntu 22.04 服务器时遇到的绝大多数问题,照着做一遍,至少能保证实例起得来、连得上、出事了有救。后续如果再把慢查询分析和数据安全这块做扎实,基本就达到生产可用的门槛了。