☰
Ubuntu 22.04从零部署MySQL 8.0:安装、安全加固与运维避坑指南
2026/10/16 4:42:40 网站建设 项目流程

这篇博文来源于一次实打实的 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

脚本启动后会依次问几个问题,我逐个说下最合理的回答:

  1. 是否安装密码校验组件VALIDATE PASSWORD COMPONENT?测试环境可以选 No,生产环境建议选 Yes 并设置 1/2/3 的强度等级。选 2 是严格模式,对长度、大小写、数字、特殊字符都有要求,可能会对业务方现有密码造成冲击,酌情使用。
  2. 是否修改 root 密码?如果你已经通过 2.2 节设置过,可以选 N 跳过;如果还没设置,选 Y。
  3. 是否移除匿名用户?必须选 Y。匿名用户意味着任何本地用户都能以空密码试探连接。
  4. 是否禁止 root 远程登录?必须选 Y。root 只在 localhost 使用,这是生产铁律。
  5. 是否移除 test 数据库并回收匿名用户对它的访问权限?建议选 Y。test 库在很多安装版本里连匿名用户都能随意访问。
  6. 是否现在重新加载权限表?选 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、防火墙三处配合

远程连接能不能通,取决于三处设置同时满足:

  1. MySQL 是否监听外部地址。
  2. 用户 host 是否匹配客户端来源 IP。
  3. 系统防火墙和云安全组是否放行 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-config

mysqld --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 = 2

long_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 服务器时遇到的绝大多数问题,照着做一遍,至少能保证实例起得来、连得上、出事了有救。后续如果再把慢查询分析和数据安全这块做扎实,基本就达到生产可用的门槛了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询