先别急着拿 Windows 里的习惯来操作 Ubuntu 上的 MySQL。我身边好几个朋友第一次在 Ubuntu 上用 apt 装完 mysql-server,紧接着就被 mysql -uroot -p 的登录方式搞懵了:密码明明输对了,界面却无情地给出 Access denied。更离谱的是,有人不小心敲了 sudo mysql,竟然不需要密码直接进去了。这不是灵异事件,而是 Ubuntu 发行版把 root 的认证方式默认成了 auth_socket 插件。今天这篇就围绕 Ubuntu 下的 MySQL,把安装选型、初始化配置、日常运维、故障排查和备份恢复这些事一条龙讲清楚,全部是基于我在真实环境里反复踩过、也验证过的实操路径。
如果你是后端开发、运维工程师,或者正在自学 Linux 加数据库组合的学生,这篇可以直接当一份避坑手册来用。我会尽量把每个操作背后的“为什么”也说清楚,不光是给命令。
1. 动手之前先弄懂三件事:auth_socket、版本选择和部署路径
1.1 反直觉的 auth_socket:为什么 root“没有密码”
Ubuntu 从 16.04 之后,apt 源里的 MySQL 做了一个和其他平台很不一样的设计:root 用户默认启用的认证插件是 auth_socket,而不是 mysql_native_password 或 caching_sha2_password。
auth_socket 的工作机制很简单:它不校验密码,而是看当前发起连接的 Linux 系统用户是谁。如果你当前是系统的 root 用户,或者通过 sudo 提升了权限,那么 MySQL 的 root 用户就可以直接放行,连密码都不用输入。打开终端输入:
sudo mysql你会发现自己直接进到了 MySQL 的命令行里,没有任何密码提示。而换成:
mysql -uroot -p无论你怎么输入密码,大概率都会收到 Access denied。因为 auth_socket 根本不读密码字段,它认的是操作系统用户身份。
可以用下面这个 SQL 查看 root 当前的认证插件:
SELECT user, host, plugin FROM mysql.user WHERE user = 'root';执行结果里插件栏如果写着 auth_socket,那就说明你正处在这个“免密但不让你用密码登录”的特殊状态。
这个设计本身是出于安全考虑的:Ubuntu 希望本地 root 权限和数据库 root 权限绑定,避免出现那种设置了弱密码、结果被局域网扫描爆破的情况。但对于需要从远程客户端连接数据库的开发场景,这个默认配置就非常不友好了。所以后面我会专门讲怎么切换认证方式。
1.2 同是 Ubuntu,不同版本默认的 MySQL 版本不同
这是一个特别容易被忽略的细节。很多人搜索“Ubuntu 安装 MySQL”,搜到的教程可能是三四年前写的,里面说的是安装 MySQL 5.7,但在当前的 Ubuntu 上执行 apt install mysql-server,装出来的是 8.0 版本。
不同的 Ubuntu 发行版,软件源里的 mysql-server 版本大概是这样:
| Ubuntu 版本 | 默认 MySQL 版本 |
|---|---|
| Ubuntu 16.04 | MySQL 5.7 |
| Ubuntu 18.04 | MySQL 5.7 |
| Ubuntu 20.04 | MySQL 8.0.x |
| Ubuntu 22.04 | MySQL 8.0.x |
| Ubuntu 24.04 | MySQL 8.0.x(更新补丁版本) |
在安装之前,建议先看一眼当前源里的候选版本:
apt-cache policy mysql-server输出里的 Candidate 字段就是 apt 会安装的版本。如果你需要 MySQL 5.7,注意 Ubuntu 20.04 及之后的官方源里已经没有 5.7 了,只能通过通用二进制包自行安装,这个我在下面会详细演示。
1.3 版本选型:5.7 还是 8.0
MySQL 5.7 已经在 2023 年底结束了官方维护,5.7.44 是这一系列的最终版本。除非你的业务系统有非常硬性的兼容要求,否则新部署的话我强烈建议直接用 8.0。
8.0 相比 5.7 有几个关键变化:
- 默认字符集是 utf8mb4,不再需要手动指定;
- 默认认证插件是 caching_sha2_password,安全性更高,但老客户端需要适配;
- 查询缓存被彻底移除;
- 窗口函数、公共表表达式这些 SQL 特性在 8.0 里才比较完善。
如果因为历史项目原因必须用 5.7,比如某些老框架的 ORM 对 8.0 的认证方式支持不好,那就别死磕 apt 了,直接上通用二进制包,自己掌控一切。
2. 实测三种安装方式:apt、通用二进制包和 Docker
2.1 apt 方式:最快,但要注意初始化环节
apt 方式是最省事的,适合绝大多数日常开发场景:
sudo apt update sudo apt install mysql-server装完之后检查一下服务状态:
sudo systemctl status mysql看到 active (running) 基本就成了。这一阶段 Ubuntu 的默认配置里,数据目录在 /var/lib/mysql,配置文件在 /etc/mysql/mysql.conf.d/mysqld.cnf,socket 文件在 /var/run/mysqld/mysqld.sock,服务由 systemd 管理。
接下来很多人会执行 mysql_secure_installation 做安全初始化,交互过程中会问是否设置 root 密码、是否删除匿名用户等。这里有个真实的坑:在 auth_socket 为默认认证方式的条件下,即使你在这一步设置了 root 密码,MySQL 仍然会继续使用 auth_socket 插件,密码并没有真正生效。我当时就是在这里误以为密码已设置成功,后面用密码登录被拒了半小时。
所以正确的理解是:mysql_secure_installation 主要做的是清理匿名账户、移除测试库、限制远程 root 访问这些事,root 密码要真正生效,需要手动切换认证插件。具体方法在第 3 节。
2.2 二进制包方式:想要 5.7 就用 tar.gz 自己装
如果你需要 MySQL 5.7.44,那么最可控的方式是下载官方通用二进制包。官网的 MySQL Archives 页面里可以找到 mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz。
安装步骤大致如下:
cd /usr/local sudo tar zxvf mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz sudo mv mysql-5.7.44-linux-glibc2.12-x86_64 mysql然后创建 mysql 系统用户,并准备好数据目录:
sudo groupadd mysql sudo useradd -r -g mysql -s /bin/false mysql sudo mkdir -p /usr/local/mysql/data /usr/local/mysql/tmp sudo chown -R mysql:mysql /usr/local/mysql初始化数据目录。5.7 时代我习惯用 --initialize-insecure,这样 root 初始密码为空,进入后可以自己设置:
sudo /usr/local/mysql/bin/mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data接着写一份最简配置文件 /etc/my.cnf:
[mysqld] basedir=/usr/local/mysql datadir=/usr/local/mysql/data socket=/tmp/mysql.sock port=3306 user=mysql为了让 systemd 能管理它,创建一个服务文件 /etc/systemd/system/mysql-custom.service:
[Unit] Description=MySQL Community Server 5.7.44 After=network.target [Service] Type=simple User=mysql Group=mysql ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf LimitNOFILE=65535 [Install] WantedBy=multi-user.target然后重新加载并启动:
sudo systemctl daemon-reload sudo systemctl start mysql-custom sudo systemctl enable mysql-custom这里额外提醒一句:5.7.44 虽然还在下载页面上,但它已经是功能冻结状态,不会再有任何安全补丁。把它当成“遗留系统的兼容兜底”可以,别当成“长期稳定方案”,迁移到 8.0 的规划要先做起来。
2.3 Docker 方式:隔离度高,但最容易因细节失败
Docker 跑 MySQL 最大优势是环境完全不污染宿主机,也能方便地跑多个版本。一条最基本的命令是这样的:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourPass@123 \ -e MYSQL_DATABASE=appdb \ -v /opt/mysql/data:/var/lib/mysql \ mysql:8.0这条命令本身不复杂,但在实际环境里失败率不低,常见原因我列一下:
- 宿主机 3306 端口已经被本机 MySQL 占用,容器启动直接报端口冲突。这种情况把宿主机端口换个高位映射就行,比如 -p 33066:3306。
- 挂载目录权限问题。容器里的 mysql 用户 uid 是 999,如果宿主机 /opt/mysql/data 的属主不是 uid 999,容器会因为无法写入而退出。建议提前执行 chown -R 999:999 /opt/mysql/data。
- 数据目录已经有旧数据。如果 /var/lib/mysql 挂载目录里有之前初始化过的文件,MYSQL_ROOT_PASSWORD 这类环境变量不会再生效,此时要用旧密码登录再改密码。
- 客户端认证方式不兼容。8.0 镜像默认创建的 root 用的是 caching_sha2_password,较老的客户端连不上。
虽然说 Docker 方式对数据生命周期管理和备份策略的要求更高,但对于做本地联调、试用不同版本、搭建临时测试环境来说,确实是最干净的方案。
3. 装完之后的安全基线:把 root 认证、密码强度和字符集一次配好
3.1 root 认证方式切换:从 auth_socket 到 caching_sha2_password
无论你用的是 apt 还是二进制包,最终面向应用连接的数据库账号都不能依赖 auth_socket。切换 root 或业务账号的认证方式,SQL 写法如下:
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '你的强密码'; FLUSH PRIVILEGES;执行完再退出,用 mysql -uroot -p 登录测试,这时候密码就真正生效了。如果客户端驱动太老、还不支持 caching_sha2_password,可以暂时退回 mysql_native_password,但我不建议把这种方式作为长期默认,升级客户端驱动才是正经出路。
另外强调一句:业务代码里不要用 root 连接数据库。新建一个最小权限账号,只授予业务库的增删改查权限,这是我在生产环境里反复确认过的安全底线。
3.2 密码策略组件:validate_password
MySQL 8.0 里,密码强度校验使用的是 validate_password 组件。安装和查看方式:
INSTALL COMPONENT 'file://component_validate_password'; SHOW VARIABLES LIKE 'validate_password%';默认情况下,MySQL 8.0 的密码策略是 MEDIUM:密码长度至少 8 位,必须包含数字、大小写字母和符号。如果你觉得这个策略太严格,可以调整:
SET GLOBAL validate_password.policy = 'LOW'; SET GLOBAL validate_password.length = 6;但需要注意,这些是运行时设置,重启后失效。想让策略永久生效,需要写进配置文件里。对于生产环境,我建议保持 MEDIUM 以上策略,没必要为了图省事降低密码门槛。
3.3 字符集和时区:utf8mb4 应该成为默认
在 8.0 里,默认字符集已经是 utf8mb4,不需要额外配置。但如果是 5.7 或者从旧版本迁移过来的库,最好检查一下:
SHOW VARIABLES LIKE 'character_set%';如果字符集还是 latin1,就需要在配置文件里修改:
[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_0900_ai_ci记住一个核心逻辑:utf8mb4 是 utf8 的超集,emoji 表情和一些生僻字必须用 utf8mb4 才能存,MySQL 里的 utf8 其实只能存最多 3 个字节的字符。
时区配置也是一个容易踩的坑。如果服务器时区不是 UTC,但数据库默认跟系统时区走,应用层记录的 timestamp 容易出现偏差。我习惯在配置文件里显式指定:
[mysqld] default-time-zone='+08:00'写成固定偏移量,而不是 SYSTEM,这样即使服务器改时区,数据库行为依然稳定。
4. 日常操作实战:排序优化、锁排查与存储过程
4.1 ORDER BY 背后的排序陷阱
MySQL 里排序相关的热搜词一直不少,很多人以为 ORDER BY 就是简单加个字段,实际上数据量一大,排序会变成性能瓶颈。
最常见的性能信号是 EXPLAIN 结果里的 Extra 列出现 Using filesort。这就意味着 MySQL 没有利用索引顺序,而是把结果集拉出来在内存或磁盘上额外排了一遍。
比如有一张订单表:
CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, created_at DATETIME NOT NULL, KEY idx_created_amount (created_at DESC, amount ASC) );8.0 支持降序索引,可以把 ORDER BY created_at DESC, amount ASC 这个查询在索引层面直接满足,避免 filesort。而在 5.7 及更早版本里,降序排序通常只能靠反向扫描实现,覆盖不了所有场景。
所以优化排序的核心思路不是“堆索引”,而是让查询的 WHERE、JOIN、ORDER BY 全部落到同一个索引的字段顺序里。覆盖索引解决排序问题,是性价比最高的手段。
4.2 锁的分类与死锁定位
InnoDB 的锁体系是事务并发的核心,也是面试和实际问题排查的高频区。我习惯从三个维度去理解它:
| 分类维度 | 锁类型 | 说明 |
|---|---|---|
| 模式 | 共享锁 S | 允许其他事务读,但阻止写 |
| 模式 | 排他锁 X | 允许持有者读写,阻止其他事务任何操作 |
| 算法 | 记录锁 Record Lock | 锁定单条记录 |
| 算法 | 间隙锁 Gap Lock | 锁定记录之间的区间,防止幻读 |
| 算法 | 临键锁 Next-Key Lock | 记录锁加间隙锁组合,InnoDB 在可重复读下的默认方案 |
| 粒度 | 表锁 / 行锁 / 元数据锁 | 分别对应 DDL、DML、结构变更场景 |
实际开发中最常见的是死锁问题。举一个典型场景:事务 A 先更新 id=1 的行,再更新 id=2 的行;事务 B 正好相反,先更新 id=2,再更新 id=1。两个事务互相等对方释放锁,就死锁了。
排查时用到这几张表:
SELECT * FROM performance_schema.data_locks\G; SELECT * FROM performance_schema.data_lock_waits\G; SELECT * FROM sys.innodb_lock_waits\G;sys.innodb_lock_waits 是最直观的,它会告诉你当前哪个事务在等待哪个事务的锁。定位到之后,处理方式无非就是:干掉阻塞事务、优化业务里多行更新的顺序、让所有事务都按同一个顺序拿锁。
4.3 一个“压箱底”的存储过程例子
存储过程这种功能,现在很多团队用得少了,但某些场景下依然很顺手。比如按月统计订单数据,我写过一个很简单但实用的存储过程:
DELIMITER // CREATE PROCEDURE sp_monthly_report(IN y INT, IN m INT) BEGIN SELECT DATE_FORMAT(created_at, '%Y-%m') AS month, COUNT(*) AS order_count, SUM(amount) AS total_amount FROM orders WHERE created_at >= DATE(CONCAT(y, '-', m, '-01')) AND created_at < DATE(CONCAT(y, '-', m, '-01')) + INTERVAL 1 MONTH GROUP BY month; END // DELIMITER ;调用方式:
CALL sp_monthly_report(2024, 6);这种把固定统计逻辑固化在数据库里的做法,适合报表口径稳定、不想在多个语言环境里重复实现的场景。但反过来,如果统计逻辑经常调整、或者涉及复杂的业务权限过滤,那就应该放应用层而不是数据库层。存储过程不是不能用,而是要克制地用。
4.4 慢查询日志与 Explain 解析
排查性能问题第一步永远是开慢查询日志:
SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 2; SHOW VARIABLES LIKE 'slow_query_log_file';线上环境我会把 long_query_time 设置成 1 秒,配合 pt-query-digest 这类工具定期分析慢日志,找出真正需要优化的 SQL,而不是凭感觉去优化。
拿到一条慢 SQL 后,EXPLAIN 是必须做的动作:
EXPLAIN SELECT user_id, SUM(amount) FROM orders WHERE created_at >= '2024-01-01' GROUP BY user_id\G;重点看几个字段:
- type:从 system、const、eq_ref、ref、range 到 index、ALL,越往右性能越差。出现 ALL 说明在扫全表。
- key:实际用到的索引。如果为 NULL,说明没有可用索引。
- rows:预估扫描行数,这个数字和实际性能高度相关。
- Extra:出现 Using filesort 或 Using temporary,就要考虑索引设计问题。
5. Ubuntu 下 MySQL 高频问题排查实录
5.1 socket 连接被拒与服务启动失败
这个报错太经典了:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)遇到先不要慌,按下面顺序排查:
sudo systemctl status mysql sudo journalctl -u mysql -n 50如果服务没启动,journalctl 会给出具体原因。常见的是数据目录权限不对、磁盘满了、配置文件语法错误。检查 socket 路径是否被改过,可以执行:
sudo mysqld --verbose --help | grep socket还有一点容易被忽略:/var/run/mysqld 目录必须存在且属主是 mysql 用户。如果缺了这个目录,MySQL 启动时会无法创建 socket 文件,导致连接被拒。
5.2 root 密码明明对了却登录失败
这个场景我在开头已经描述了。如果你确认密码没错,但 mysql -uroot -p 就是进不去,而 sudo mysql 能进,那 100% 是 auth_socket 在起作用。
解决办法很简单:
sudo mysql进入后执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '新密码'; FLUSH PRIVILEGES;然后退出,重新用密码登录。这条命令执行完之后,root 的密码认证才真正接管。
5.3 客户端 SSL 连接报错的两种场景
关于 mysql ssl 连接错误,我在实际环境里遇到过两种典型情况。
第一种是客户端版本太老,报错为:
Authentication plugin 'caching_sha2_password' cannot be loaded这其实和 SSL 本身无关,是认证插件不匹配。老客户端只认 mysql_native_password,而 8.0 默认创建的用户是 caching_sha2_password。解决办法要么升级客户端驱动,要么对特定账号做认证方式兼容:
ALTER USER 'user'@'%' IDENTIFIED WITH mysql_native_password BY '密码';第二种才是真正的 SSL 问题。MySQL 8.0 默认启用 SSL,如果配置文件里指向的证书私钥权限不对,服务起来后客户端连接会报 SSL 相关错误。检查私钥文件权限,确保 mysql 用户可读。临时的绕过方式是在客户端命令里加:
mysql -uroot -p --ssl-mode=DISABLED但这不是长期方案,该配好的证书权限还是要配好。
5.4 Docker MySQL 启动失败的快速判断
Docker 方式跑 MySQL,启动失败的判断路径和本机安装完全不同。先用:
docker logs mysql8看容器日志。如果日志里有类似:
[ERROR] Can't read dir of '/etc/mysql/conf.d/'说明挂载配置目录的方式有问题。先别急着加各种自定义配置,用最小参数跑通之后再逐步加环境变量和卷映射,排查效率会高很多。
如果日志里反复出现权限错误,基本可以断定是挂载目录的属主不对。执行:
sudo chown -R 999:999 /opt/mysql/data再次启动即可。
6. 备份与恢复:mysqldump 和 binlog 双保险
6.1 mysqldump 的正确打开方式
日常备份我最常用的还是 mysqldump。一个比较稳妥的全库备份命令是:
mysqldump -uroot -p \ --single-transaction \ --set-gtid-purged=OFF \ --all-databases > backup_$(date +%F).sql这里有两个参数一定要理解:
- --single-transaction 用于 InnoDB 表,它通过快照读取保证备份期间数据一致性,不锁表,避免影响线上写入。
- --set-gtid-purged=OFF 是为了让备份文件在非 GTID 环境或要导入到其他实例时,不带上原实例的 GTID 信息。很多人备份文件导不回去,就是这个参数没处理好。
单库备份更简单:
mysqldump -uroot -p --single-transaction dbname > dbname.sql6.2 binlog 开启与增量恢复
全量备份只能覆盖到备份时间点以前的数据。要让数据库恢复到精确的时间点,必须依赖 binlog。
在配置文件里加上:
[mysqld] server-id=1 log-bin=mysql-bin binlog_format=ROW8.0 默认 binlog_format 就是 ROW,5.7 则需要确认。expire_logs_days 这种清理参数在 8.0 里已经改成了 binlog_expire_logs_seconds,注意区分。
当需要恢复到某个时间点时:
mysqlbinlog \ --start-datetime="2024-01-01 00:00:00" \ --stop-datetime="2024-01-01 10:30:00" \ mysql-bin.000001 | mysql -uroot -p增量恢复的前提是 binlog 文件还在,所以备份脚本里一定要包含 binlog 文件的归档,并且配置好自动清理策略,避免磁盘被日志撑爆。
6.3 做一次真实的恢复演练
很多团队“有备份”不等于“能恢复”。建议大家在新环境里做一次完整的演练,流程是这样的:
- 全量备份当前库,得到 backup.sql;
- 继续写入一批测试数据;
- 删除或误操作部分表;
- 先用全量备份恢复,再用 binlog 把备份时间点到误操作之前的数据补回来;
- 校验表数量和关键行数,确认和预期一致。
演练的目的不是过程本身,而是要验证备份文件可用、binlog 权限正确、恢复步骤没有遗漏。实际事故发生时每一分钟都是钱,演练做熟了你才能冷静应对。
最后分享一点个人习惯:每次改 MySQL 配置文件之前,先备份一份原始文件;重启服务之前,先用 mysqld --validate-config 或者 mysqld --verbose --help 做一次配置校验。这个简单的动作,替我挡过至少三次因为配置写错导致 MySQL 起不来的事故。Ubuntu 下用 MySQL,最大的门槛不是命令记不住,而是对发行版默认行为和认证机制的“反直觉”没有提前心里有数。把这篇文章里的路径走通一遍,你应该就能很从容地在这套组合上开展实际业务了。