
做运维这几年在能在 Ubuntu 22.04 上装 MySQL 的机会没有一百也有八十次了。我真正把这套流程吃透是在连续三次帮别人收拾装完就跑不起来的服务器之后——那几台机器都是系统刚装好MySQL 安装过程毫无波澜结果要么 root 密码怎么试都进不去要么远程连不上要么建好的表插进去的中文全是问号。问题从来不在安装命令本身而在安装之后那一堆需要懂原理才能做对的配置。这篇就写我在 ubuntu22.04 上安装和配置 mysql 的完整记录从 apt 安装、root 认证、字符集、时区、远程访问到几个我真实踩过的坑整理成一份可以直接照着做的实操教程。如果你手头有一台刚装好的 Ubuntu 22.04 服务器或者想在本地虚拟机里搭一套 MySQL 环境做开发测试照着这篇文章走完基本不会出大问题。1. 动手之前先摸清底细版本、路线和系统状态1.1 Ubuntu 22.04 官源里的 MySQL 8.0 到底能不能用Ubuntu 22.04 的官方软件源默认提供 MySQL 8.0.x注意不是 5.7也不是 MariaDB。我见过不少人在这一步就犹豫觉得官方源版本不够新非要去装 8.2、8.3 之类的版本。我的建议是除非有明确需求否则直接用发行版仓库里的版本。原因是 Ubuntu 打包的 MySQL 会跟着系统一起做测试和 systemd 服务脚本、AppArmor 配置、日志轮转策略都是配套好的。装完以后服务怎么启动、日志写到哪、权限策略长什么样全部是开箱即用的状态。从官方 APT 仓库装的版本可能确实更新但你需要自己维护源地址和 GPG 密钥还要承受升级时配置差异带来的风险。确认版本信息很简单安装之前先执行sudo apt update apt-cache policy mysql-server装完以后看实际版本mysql --version我手头一台服务器装完以后输出的是mysql Ver 8.0.35-0ubuntu0.22.04.1作为长期运行的数据库服务器这个版本完全够用。1.2 三种安装方式怎么选apt、官方 APT 仓库、源码编译把三种方式摆在一起对比会更直观安装方式优点缺点适合场景apt发行版源最省事与系统集成好升级随系统大版本走小版本更新滞后绝大多数生产与开发环境官方 APT 仓库版本新补丁及时安全修复快需要自己配源、管密钥升级时可能有配置差异对版本有强需求或合规要求高的环境源码编译定制化强可以自定义编译参数编译慢、依赖多、后期维护成本极高极少见通常是被迫选择普通项目用第一种就够了。即使你真的需要新版本我也建议走官方 APT 仓库而不是源码编译。源码编译 MySQL 的坑实在太多依赖库版本冲突、Boost 版本要求、编译参数错误导致性能下降而且后续每次更新都要重新编译一遍没有任何运维收益。1.3 准备阶段必做的三件小事安装之前我习惯顺手做三件事sudo apt update df -h sudo ss -tlnp | grep 3306第一件保证软件源索引是新的避免 apt 拉到过时的元数据第二件确认磁盘还有充足空间MySQL 8.0 安装后数据目录加上日志至少预留几个 GB磁盘写满会导致初始化失败第三件确认 3306 端口还没被占用如果机器上已经跑了别的服务占用这个端口后续启动会直接冲突。这些检查听起来很基础但我每次装都做节省了不少装完莫名其妙起不来的排查时间。尤其是ss那条曾经救过我一次——那台机器上有个残留的容器端口映射占着 3306安装一切正常服务就是起不来提前确认就能一眼定位。2. 从零到跑起来apt 装完后要做的几件事2.1 一条命令装完但装完要看的不只是安装成功实际安装只需要一条命令sudo apt install -y mysql-server执行过程中 apt 会拉取 mysql-server、mysql-client 以及一系列运行时依赖。如果系统里之前残留过别的 MySQL 相关包apt 可能会提示你是否替换这时建议看清楚再回车不要一路 Y。装完以后第一件事不是急着建库而是看一眼服务状态sudo systemctl status mysql正常状态是active (running)。如果这里就报错直接跳到文末第 6 节看排查路线不要继续往下走后面所有操作都依赖一个能正常启动的 MySQL 实例。2.2 让 MySQL 开机自启并确认 3306Ubuntu 的 mysql-server 包在安装时会自动把 MySQL 注册为 systemd 服务。这里建议主动执行一次sudo systemctl enable mysql sudo systemctl start mysql然后确认监听端口和开机自启状态sudo ss -tlnp | grep 3306 sudo systemctl is-enabled mysql两条输出分别说明 3306 端口正在被 mysqld 监听、MySQL 服务已加入开机自启列表。这里有个重点默认情况下 MySQL 只监听127.0.0.1即只允许本机连接。这个特性会在第 5 节详细展开你现在只需要记住装完默认状态是本机可用、外部不可达。2.3 初次进入 MySQL 后建议做的事现在用无密码方式登录sudo mysql -u root看到mysql提示符就代表一切正常。建议此时至少执行两条命令SHOW DATABASES; SELECT user, host, plugin FROM mysql.user;第一条会列出默认数据库information_schema、mysql、performance_schema、sys。第二条用来查看用户列表重点关注rootlocalhost那一行对应的plugin值后面第 3 节会用到。这里顺便多说一句如果之后你用某个开发框架初始化数据库时报错无法创建数据库多半就是账号权限不够。你需要回到这里用 root 执行GRANT给业务账号授权而不是去修改 MySQL 的底层权限表——直接改 mysql.user 表是反模式很容易把系统搞坏。3. root 账号的认证机制为什么默认只能用 sudo mysql 进3.1 auth_socket 的工作方式和设计初衷如果你刚装完就执行mysql -u root -p会被告知密码错误。这不代表你漏设了密码而是 rootlocalhost 用的认证插件是auth_socket。auth_socket不校验密码它只校验两件事连接方式是不是本地 Unix socket以及发起连接的进程是不是以系统 root 身份运行。换句话说只要你能拿到系统的 root 权限在本地执行sudo mysql就能直接进 MySQL root密码形同虚设。这个设计有它的道理Ubuntu 的思路是MySQL 的 root 应该由系统的 root 来管你要用 MySQL 做业务就去建自己的业务账号。对不少开发者来说这有点绕把 root 改成密码登录更顺手所以接下来要做的就是改认证方式。3.2 改成密码登录的正确姿势用前面说的sudo mysql -u root方式进入后执行ALTER USER rootlocalhost IDENTIFIED WITH caching_sha2_password BY 你的强密码;或者更简洁一点ALTER USER rootlocalhost IDENTIFIED BY 你的强密码;默认认证插件是caching_sha2_password这是 MySQL 8.0 的默认项安全性比老式的mysql_native_password更高。不过要注意如果以后有一个非常老的客户端连过来不认这个插件可以暂时给对应账号指定mysql_native_password但这个插件在 8.0.34 开始已经被标记为废弃只能作为过渡手段。改完之后有一件事必须记住以后 root 也需要密码才能进入包括本机sudo mysql也无法直接绕过。我见过好几个同事改完密码随手一放过两天忘了最后只能走skip-grant-tables的恢复流程非常折腾。所以改之前先确认密码已经记在安全的地方。3.3 mysql_secure_installation 加固流程逐项说明改完密码后建议立刻跑一遍官方加固脚本sudo mysql_secure_installation脚本会依次询问几个问题我按推荐答案逐项说是否配置密码强度校验组件VALIDATE PASSWORD COMPONENT——开发环境可以选 N正式环境建议选 Y。选 Y 后还会让你选强度等级 0/1/2一般选 1 级就够。是否为 root 设置密码——如果你在前面已经改过这里会要求输入当前密码。是否移除匿名用户——选 Y。残留的匿名账号会带来权限混乱具体见第 6 节。是否禁止 root 远程登录——选 Y。远程连接要单独建用户不要开 root 远程。是否删除 test 数据库——选 Y。默认 test 库没有任何权限限制留着没有好处。是否立即刷新权限表——选 Y。这一套流程跑完一个相对干净的基础环境就出来了。很多人装完 MySQL 直接开始建库跳过加固脚本后面遇到的很多诡异问题其实都能在这个脚本里避免。4. 字符集、排序规则和时区让数据库说中文4.1 为什么是 utf8mb4 而不是 utf8MySQL 里的utf8其实不是大家理解的 UTF-8它最多只支持 3 字节字符官方文档叫它utf8mb3。表情符号、部分生僻汉字、扩展区字符都需要 4 字节编码存进 utf8 字段要么报错要么变成问号。utf8mb4才是完整的 UTF-8 编码。MySQL 8.0 默认字符集已经改成 utf8mb4但如果你是从旧版本迁移过来的环境服务器变量很可能还停留在旧值。装完以后用这条 SQL 看一眼SHOW VARIABLES LIKE character_set_server;如果看到的不是 utf8mb4就要在配置层面统一改掉。排序规则方面8.0 默认是utf8mb4_0900_ai_ci我自己的习惯是新环境直接用默认值但如果你要从 5.7 迁移库可以用utf8mb4_unicode_ci它在旧版本里就存在兼容性更好。4.2 修改配置文件让全局默认生效Ubuntu 上 MySQL 的主配置是/etc/mysql/my.cnf它通过 include 加载/etc/mysql/conf.d/和/etc/mysql/mysql.conf.d/下的文件。服务端配置真正存放的位置是/etc/mysql/mysql.conf.d/mysqld.cnf。编辑这个文件在[mysqld]段下加入[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci然后重启sudo systemctl restart mysql重启后再次确认SHOW VARIABLES LIKE character_set%; SHOW VARIABLES LIKE collation%;这里有个细节非常容易踩服务端字符集改了但客户端连接时的character_set_client可能还是旧的。为了让 mysql 命令行客户端也统一可以新建一个/etc/mysql/conf.d/charset.cnf[client] default-character-set utf8mb4服务端、数据库、客户端三层字符集全部统一后中文乱码问题基本就根治了。我之前帮人排查过一个乱码问题服务端和库都改成 utf8mb4 了乱码依旧最后发现就是客户端连接参数没带上字符集设置——服务端改得再对客户端以 latin1 连进来照样是问号。4.3 已经建好的老库老表怎么补救如果你在改配置之前就已经建库建表光改全局配置不会影响已有对象库和表要单独改ALTER DATABASE 你的库名 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE 你的表名 CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意这里的区别CONVERT TO会重建表并转换已有列的数据大表可能需要很长时间还会产生锁竞争尽量放在维护窗口执行。如果只想改表的默认字符集、不动已有列的数据用ALTER TABLE ... DEFAULT CHARACTER SET utf8mb4就行。{% hint styleinfo %} 判断一个表当前到底是什么字符集用SHOW CREATE TABLE 表名;输出的DEFAULT CHARSET位置就是答案。 {% endhint %}4.4 时区设置比你想的更影响业务代码默认情况下 MySQL 8.0 的时区跟随系统的时区设置。如果服务器是 UTC 时区业务在北京时间上午 9 点写入数据SELECT NOW()出来的时间会比预期晚 8 小时这个坑非常隐蔽——应用日志看起来时间正常数据库里查出来却对不上。处理办法是在[mysqld]段加入default-time-zone 08:00重启后用SELECT NOW();验证。如果想用带名字的时区比如Asia/Shanghai需要先把系统时区表导入 MySQLmysql_tzinfo_to_sql /usr/share/zoneinfo | sudo mysql mysql这条命令在生产环境要谨慎执行因为它会把大量时区数据写进 mysql 系统库。多数场景下直接写固定偏移量就足够了简单直接还没有额外开销。5. 远程访问的完整链路bind-address、防火墙和专用账号5.1 默认只听 127.0.0.1到底为什么回到前面说的默认只监听本地。打开/etc/mysql/mysql.conf.d/mysqld.cnf能看到这一行bind-address 127.0.0.1bind-address决定 mysqld 监听哪个网络接口。127.0.0.1表示只允许本机回环接口连接安全性最好但其他机器完全无法访问。需要远程连接时有两种改法改成0.0.0.0监听所有网卡或者改成内网 IP比如10.0.0.8只监听特定网卡。我自己的习惯是尽量用具体 IP避免把服务暴露到不必要的网段。改完同样要重启然后用ss验证sudo ss -tlnp | grep 33065.2 修改监听并放行防火墙监听地址改完后还要过防火墙这一关。Ubuntu 自带 UFW执行sudo ufw allow 3306/tcp sudo ufw status如果部署在云服务器上记得同时检查安全组的入方向规则。我见过很多案例本地防火墙开了、云安全组没放行导致从公网怎么都连不上。两地都要检查缺一个就通不了。5.3 创建业务专用账号别把 root 暴露出去远程连接我强烈不建议直接用 root哪怕只在公司内网使用。正确做法是建一个只拥有所需权限的业务账号CREATE USER app_user192.168.1.% IDENTIFIED BY 另一个强密码; GRANT SELECT, INSERT, UPDATE, DELETE ON 你的库.* TO app_user192.168.1.%; FLUSH PRIVILEGES;账号名后面的主机部分是重点。192.168.1.%表示只允许这个网段连接10.0.0.5精确匹配单个 IP%表示任意主机能用但也最危险。其实在 MySQL 8.0 里CREATE USER和GRANT执行后权限会自动生效不需要FLUSH PRIVILEGES——这个命令只有在你直接修改了 mysql.user 表时才需要。写了当然无害但也别指望它能解决权限不生效的问题。还有一个隐藏坑如果开启了skip_name_resolveMySQL 不会反查客户端域名所有按主机名匹配的账号都会失效。所以生产环境统一用 IP 段做主机匹配别写域名。最后说一个和 MySQL 8.0 有关的小细节默认还开启了 33060 端口供 MySQL Shell 的 X Protocol 使用。如果你用 MySQL Shell 远程管理要记得在防火墙放行这个端口否则会出现3306 能连但 Shell 连不上的诡异现象。6. 部署后踩过的坑四个高频问题的排查实录6.1 AppArmor 拦住了自定义数据目录有人不想把数据放在/var/lib/mysql想挪到/data/mysql。改了datadir之后启动服务会直接失败错误日志里写着权限不足。这不是文件权限问题而是 AppArmor 在拦截。Ubuntu 自带一套针对 mysqld 的 AppArmor 策略只允许 mysqld 访问/var/lib/mysql等预设路径自定义目录不在白名单里。解决思路有两条。第一条是修改 AppArmor 配置文件编辑/etc/apparmor.d/usr.sbin.mysqld加上一行alias /var/lib/mysql/ - /data/mysql/,保存后重载sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld第二条是我现在更推荐的做法不改 datadir而是在/var/lib/mysql路径上挂载一块新磁盘或分区。这样既不会触发 AppArmor也完全不需要改 MySQL 配置后续磁盘扩容也更灵活。数据目录迁移这件事能不碰配置就别碰配置。6.2 老业务连不上 8.0caching_sha2_password 兼容性处理MySQL 8.0 默认认证插件改成caching_sha2_password之后客户端驱动跟不上就会出现这类报错Authentication plugin caching_sha2_password cannot be loaded。常见于比较老的 PHP mysqlnd、旧版 JDBC 驱动、某些低版本的 Go 驱动。最推荐的解法是升级客户端驱动库这比在数据库层面做兼容要彻底得多。如果业务暂时不能动可以给这个老客户端单独建一个兼容账号过渡CREATE USER legacy_app% IDENTIFIED WITH mysql_native_password BY 密码; GRANT 需要的权限 ON 你的库.* TO legacy_app%;注意mysql_native_password在 8.0.34 开始被标记为废弃只适合作为过渡方案别把它当成长期配置。如果你发现新装的 MySQL 里caching_sha2_password连不上的问题反复出现优先从客户端版本入手而不是把服务端认证方式降级。6.3 anonymous user 导致 root 密码白设有个很隐蔽的问题root 密码明明改对了从本机用密码登录仍然被拒绝。查了一圈发现问题出在残留的匿名账号上。MySQL 匹配用户时有一套优先级规则匿名用户在某些连接场景下会先被匹配到连接就被导向了一个你不知道的账号自然提示 access denied。检查方法SELECT user, host, plugin FROM mysql.user;看到 user 为空字符串的行就是匿名用户。直接删掉DROP USER localhost;这也解释了为什么mysql_secure_installation里那个是否移除匿名用户的问题值得选 Y。跳过这一步的人早晚要在这上面吃一次亏而且排查成本远高于按一次 Y 的成本。6.4 服务起不来的通用排查路线最后列一个通用的排查流程。MySQL 服务起不来时第一步永远不是改配置而是看日志systemctl status mysql journalctl -u mysql --no-pager -n 50 tail -n 100 /var/log/mysql/error.logUbuntu 上 MySQL 的错误日志默认在/var/log/mysql/error.log但具体路径要看log_error参数指向哪里。日志位置不对可以用SHOW VARIABLES LIKE log_error;确认。常见原因和处理动作见下表报错/现象最常见的原因优先处理动作Job for mysql.service failed配置语法错误、datadir 权限不对看 error.log检查/var/lib/mysql属主是否为 mysql:mysqlCant connect to local MySQL server through socket服务没启动或 socket 目录不可写重启服务确认/var/run/mysqld存在且属主正确The table ... is full磁盘满df -h查看清理或扩容AppArmor ... denied自定义路径未加入策略按 6.1 的方式处理error.log 里通常已经把真正的原因写清楚了很多时候只是一条配置不对或者目录权限问题。把日志完整看完再动手比瞎试一通高效太多。说一句题外话我现在每装一台新机器的 MySQL都会在改完配置后先把/etc/mysql下所有 .cnf 文件备份一份文件名带上日期。这套动作花不了半分钟但后期排查配置漂移问题时能省掉不少冤枉时间。如果你也要在生产环境部署建议把这篇里涉及的关键 SQL 命令整理成一份初始化脚本装完直接跑一遍比手工一条条敲可靠得多。