MySQL 技术体系这个东西,我最早是真没当回事,以为“装个库、写几条SQL、不行就加个索引”完事了。后来线上一条排序慢查询把连接池打满,业务整整停了十分钟,我才意识到,MySQL 的部署安装、存储引擎、索引结构、锁与事务、日志与备份、主从复制,每一块都是大坑,而性能调优的本质,就是把这一整套体系里最薄弱的那一环找出来补上。这篇指南不打算做文档翻译,而是把我实际踩过的坑、验证过的方法、能直接抄作业的参数组合整理出来,尽量把 MySQL 技术体系和实战调优讲透,适合正在学 MySQL、准备接手数据库运维、或者面试前想系统过一遍的朋友。
1. MySQL 技术体系全景:这不是一个黑盒
1.1 一条SQL语句的完整旅程
把 MySQL 拆开看,它不只是一个存数据的文件,而是一套分层明显的服务端程序。我习惯用下面这张示意图去跟新人讲整体结构,虽然简单,但比啃官方文档高效得多:
客户端 | v +-------------------------------+ | 连接管理 / 认证 / 权限 | +-------------------------------+ | 查询解析器(词法、语法分析) | | 查询优化器(生成执行计划) | | 执行器(调用引擎接口) | +-------------------------------+ | 存储引擎层(InnoDB / MyISAM) | +-------------------------------+ | 物理文件:ibd / redo / undo | | / binlog / 系统表空间 |一次普通的 SELECT 查询,先要经过客户端和服务器之间的连接器,完成用户名密码校验、权限检查,然后再进入服务层。服务层里解析器会检查SQL语法,把一条语句拆成可识别的结构;优化器负责选择索引、决定连接顺序、生成执行计划;执行器拿到计划后,再一层层调用存储引擎接口。你会发现,真正读磁盘、读内存、加锁、记录日志的其实是存储引擎,而 SQL 的解析、优化、权限控制都在上面的 Server 层完成。
这套分层设计的最大好处是解耦。你可以在不改动上层逻辑的情况下,把 MyISAM 换成 InnoDB,也可以针对不同业务表选择不同引擎。很多问题排查也依赖这种分层思想:SQL 慢,先看是不是 optimizer 选错了索引;连接失败,先看连接器和权限;磁盘读写异常,再看引擎层的物理文件状态。
1.2 InnoDB 为何能成为默认存储引擎
早期 MySQL 的默认引擎是 MyISAM,那时候很多生产库连事务都没有,一台机器崩了,恢复基本靠备份。后来 InnoDB 成为绝对主力,默认引擎也换成了它。为什么?因为 InnoDB 提供了一整套让数据更可靠、并发更高的能力。
简单对比一下:
| 对比项 | InnoDB | MyISAM |
|---|---|---|
| 事务支持 | 支持 ACID | 不支持 |
| 锁粒度 | 行锁 + 表锁 | 仅表锁 |
| MVCC | 支持 | 不支持 |
| 数据存储 | 聚簇索引,数据与主键索引绑定 | 索引与数据分离 |
| 崩溃恢复 | redo log + doublewrite | 依赖修复工具 |
| 缓存 | 数据页缓存 + 索引缓存 | 仅索引缓存 |
| 外键 | 支持 | 不支持 |
我最看重的是 InnoDB 的崩溃恢复能力。它把每一次数据页的修改通过 redo log 先落盘,就算数据库突然断电,重启后也可以根据 redo log 把数据恢复到“崩溃前一刻”的状态。而 MyISAM 一旦索引文件或数据文件损坏,可能连表都打不开,得用 myisamchk 慢慢修。选错引擎的教训我见过太多:有人拿 MyISAM 存订单,业务量一上来整表锁死,接口全部卡住。现在的原则其实很简单:业务数据表一律 InnoDB,除非你明确知道自己在做什么。
1.3 三种日志与崩溃恢复
日志是 MySQL 技术体系里最容易被忽略、又最影响可靠性和主从复制的部分。很多新手分不清 redo log、undo log、binlog,我按“谁产生的、记录什么、用来干嘛”做了张对照表:
| 日志 | 所属层级 | 记录内容 | 核心作用 |
|---|---|---|---|
| redo log | InnoDB | 物理页变更 | 崩溃恢复,保证事务持久性 |
| undo log | InnoDB | 行记录变更前的版本 | 事务回滚、MVCC 快照读 |
| binlog | Server 层 | 逻辑 SQL 或行事件 | 主从复制、时间点恢复 |
redo log 是 InnoDB 自己维护的,记录的是“数据页第几页第几条记录改成什么”,采用循环写的方式,不需要无限增长。每次事务提交时,默认会把这个事务产生的 redo 刷到磁盘,这就是参数 innodb_flush_log_at_trx_commit=1 的语义。binlog 则是 Server 层的逻辑日志,记录的是“哪条语句把哪一行改成了什么”,主从复制和基于 binlog 的恢复全靠它。
很多复制延迟、数据不一致问题,本质上是三种日志配合出了问题。比如主库 binlog 格式用了 STATEMENT,一条带 limit 的 update 落到从库执行就可能产生不同结果;再比如没有开启 binlog 的生产库,一旦误删数据基本只能找快照,时间点恢复无从谈起。所以我的建议是:从搭建第一天就开 binlog,且格式用 ROW。
2. 部署与安装:从裸机到生产环境
2.1 Linux RPM 方式部署 MySQL 5.7/8.0
生产环境最常用的安装方式之一就是 RPM。以 CentOS/Rocky 这类系统为例,千万别一上来就 yum install mysql-server,那装的很可能是系统自带兼容包或者 MariaDB,版本和路径都不对。正确流程大致是这样:先确认系统里没有自带数据库,有 MariaDB 就卸掉,然后下载官方 MySQL 社区版 RPM 包,或者配置官方 Yum 源。
# 检查是否已有 mysql/mariadb rpm -qa | grep -i mysql rpm -qa | grep -i mariadb # 下载官方 rpm 源包(以 8.0 为例) wget https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm rpm -ivh mysql80-community-release-el7-7.noarch.rpm # 安装服务端和客户端 yum install -y mysql-community-server mysql-community-client # 初始化并查看临时密码 mysqld --initialize --user=mysql grep 'temporary password' /var/log/mysqld.log # 启动并登录 systemctl start mysqld mysql -uroot -p注意 8.0 初始化之后 root 账号默认带临时密码,且密码策略默认是中等的,第一次改密码要满足大小写、数字、特殊字符要求。如果只想本地测试,可以顺手把密码策略调低:set global validate_password.policy=LOW;但生产环境不建议。
关于 MySQL 5.7,社区版最后的版本其实是 5.7.44,之后官方不再对 5.7 系列做维护更新。所以网上搜索时看到 5.7.43、5.7.44 不必困惑,5.7.44 就是收官版。如果是从 5.7 升级 8.0,RPM 包目录和默认参数差异很大,升级前一定要备份。
2.2 Docker 部署 MySQL 与镜像拉取失败排查
Docker 部署 MySQL 最大的优势是环境隔离、数据目录挂在宿主机,换机器方便。常规命令:
docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /data/mysql:/var/lib/mysql \ -v /etc/mysql/conf.d:/etc/mysql/conf.d \ mysql:8.0挂在宿主机的数据目录非常重要,否则容器一删数据全没。第一次启动时容器会自动执行初始化脚本,创建 root 用户和默认数据库,之后重启不会再初始化。
但 Docker 拉取镜像和启动容器时经常翻车。比如网上很多人遇到的failed to decode referrers index: invalid,这多半是 Docker Desktop 或 Docker Engine 版本太旧,对镜像仓库新接口协议支持不完整。解决办法很简单:升级 Docker Desktop 到最新版,然后重新 pull 一次。如果一直失败,可以先docker pull mysql:8.0.36指定具体版本,避免 latest 标签解析问题。
容器启动失败更常见的是端口占用、数据卷权限不对、或者宿主机的 selinux 拦截。遇到这种问题先别急着删容器,执行docker logs mysql8看最后几行。如果是Can't create/write to file '/var/lib/mysql/',多半是挂载目录权限不够,执行chown -R 999:999 /data/mysql再重启。这也是为什么我不建议一上来用--privileged粗暴绕过的原因。
2.3 离线环境与 ARM 架构部署
有些内网环境不能访问外网,没法用 Yum 源,这时候需要在一台能联网的机器上把 RPM 包和依赖一起导出,然后拷进内网。离线安装的坑在于依赖关系:MySQL 的 RPM 包依赖于libaio、perl等系统包,只拷 mysql 开头的几个 rpm 不够,启动时会报error while loading shared libraries: libaio.so.1。建议用yumdownloader --resolve mysql-community-server把依赖全部拉到同一个目录,再到目标机上rpm -Uvh *.rpm。
ARM 架构的服务器近几年越来越多。MySQL 官方镜像本身提供 ARM64 版本,直接用 MySQL 8.0 官方 Docker 镜像一般没问题。但如果是基于 Oracle Linux 的镜像,旧版本可能在部分 ARM 环境上兼容性差,可以改用 MariaDB 或者用 MySQL 官方提供的 ARM 二进制包。再一个容易忽略的点是客户端工具链:Windows 上连接 ARM 服务器时,ODBC 驱动必须选对应架构。MySQL Connector/ODBC 8.0 装不上,经常是因为系统缺少 Microsoft Visual C++ 2015-2022 Redistributable x64,先装运行库再装驱动,顺序反了一堆莫名其妙的问题。
2.4 安装后的初始化与安全基线
数据库装好能连上,只算完成了 30%。我接手过的系统里,安全问题和参数问题都集中在这个环节。推荐安装后立刻执行几件事:第一,运行安全初始化脚本,删掉匿名账号和空密码账号;第二,root 账号只在本地使用,业务账号权限最小化;第三,确认字符集和排序规则。
-- 创建业务专用账号,避免 root 上应用 CREATE USER 'app_user'@'192.168.%.%' IDENTIFIED BY 'StrongPass_123'; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'192.168.%.%'; -- 修改字段默认值为 0 ALTER TABLE t MODIFY status INT NOT NULL DEFAULT 0;热搜里有人问“mysql 设置默认值为 0”,这个在项目里常有:用户状态字段新建时默认 0。要注意如果表里已有大量数据,直接加 DEFAULT 0 不会反填历史数据,只影响新插入的行;真想把存量值刷成 0,还得单独执行 UPDATE。安全方面,MySQL 8.0 默认的认证插件是 caching_sha2_password,老客户端连接会报 SSL/认证相关错误,我在后面故障排查一节里单独讲。
3. 性能调优实战:先定位,后动刀
3.1 打开慢查询日志,用 EXPLAIN 定位真凶
性能调优最忌讳一上来就改参数。我见过有人把 max_connections 调得很大,结果数据库连接更多,慢查询更慢,最终直接把实例打挂。正确做法是先给问题“定位”,Step 1 永远是打开慢查询日志。
-- 临时开启慢查询日志 SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; SET GLOBAL log_output = 'TABLE'; -- 查询最近慢查询 SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;日志开了之后,把执行时间超过 1 秒的 SQL 揪出来,对每条慢 SQL 执行 EXPLAIN。EXPLAIN 的结果里,我最关注四列:type、key、rows、Extra。type 从全表扫描 ALL 到索引点查 const,基本能判断这条 SQL 缺不缺索引;rows 是估算扫描行数,数值越大通常越慢;Extra 出现 Using filesort 或 Using temporary 基本意味着 sort buffer 和临时表参与进来了,这种 SQL 值得重构。
举个真实例子:一张 500 万行的订单表,按下单时间排序分页:
SELECT * FROM orders WHERE status = 1 ORDER BY create_time DESC LIMIT 20;刚开始 type 是 ALL,Extra 是 Using filesort,页面打开要 3 秒。加了一个(status, create_time)联合索引之后,type 变成 range,扫描行数从 500 万降到几千,查询 30ms 返回。调优不是靠玄学,EXPLAIN 就是数据库在告诉你问题在哪。
3.2 内存与并发核心参数调优
定位到问题后,才是参数调整环节。MySQL 参数很多,但真正影响绝大部分业务的关键参数没几个。
首先是 InnoDB 缓冲池:innodb_buffer_pool_size。这个参数决定了 InnoDB 把多少热数据页缓存在内存里,是数据库最大的内存消费者。我的经验值:纯 MySQL 实例按物理内存的 50%~70% 设置,如果机器上还跑 Redis、Nginx,就降到 40%~50%。在线调整示例:
-- 查看当前值 SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; -- 8.0 支持在线调整 SET GLOBAL innodb_buffer_pool_size = 8589934592;其次是日志相关参数。innodb_flush_log_at_trx_commit 有三个值:1 表示每次事务提交都刷 redo 到磁盘,安全性最高、性能最慢;0 每秒刷一次,最快但可能丢 1 秒数据;2 每次提交写操作系统缓存但每秒刷盘。金融类业务必须 1,日志系统和允许少量丢失的业务可考虑 2,既要性能又不想丢太多数据时,2 是折中选择。
连接数和并发参数也很容易踩坑。max_connections 不是越大越好,连接数一旦超过数据库能同时处理的线程数,请求会全部堆积。常用检查思路:跑show status like 'Threads_connected';,如果接近 max_connections 且 CPU 已经很高,不是调大连接数,而是该查慢 SQL 和连接池配置。
3.3 索引设计与 SQL 写法优化
索引是 MySQL 性能调优的核心中的核心。只要查询条件带 where、需要排序、需要去重,都应该先想想有没有合适的索引可用。但索引也不是越多越好,每个索引都占用写入和存储成本。
联合索引遵循最左前缀原则:索引(a, b, c)能用到 a、a+b、a+b+c,但直接查 b 或 c 用不上这个索引。所以写 SQL 时,where 条件里等式列的顺序、以及 order by 的列,最好都能和联合索引建立方向一致。我调优时经常干一件事:把慢 SQL 里的 where 列和 order by 列提出来,设计一个多列索引,让 where 筛选和排序都能走索引。
SQL 写法方面,热搜里“mysql 的 or 能去重吗”这个问题很有代表性。答案是:or 本身不会去重,它只是连接多个条件,如果两个条件查出同一行,结果集里还是会重复。去重用DISTINCT或GROUP BY:
SELECT DISTINCT user_id FROM orders WHERE status = 0 OR pay_type = 1;注意 or 有时会让优化器放弃索引,尤其是不同列 or 条件。遇到这类情况,可以改写为 union all + 去重,或者用 in 替代等值 or,效果往往更好。排序相关的另一个坑是字符集排序规则,同一张表字段用 utf8mb4_general_ci 和 utf8mb4_unicode_ci,排序结果可能不一样。如果业务要求严格的大小写敏感排序,干脆把字段 collation 设成 *_bin。
3.4 存储过程在实际业务中的合理用法
存储过程现在确实不像十年前那么流行,很多公司甚至禁用,因为业务逻辑放在数据库里不好调试、不好扩展。但在批处理、数据迁移、报表统计等固定流程里,存储过程仍然很香。比如批量初始化几百万行记录的状态:
DELIMITER // CREATE PROCEDURE batch_update_status() BEGIN DECLARE v_i INT DEFAULT 0; WHILE v_i < 100 DO UPDATE orders SET status = 1 WHERE id BETWEEN v_i * 10000 + 1 AND (v_i + 1) * 10000; SET v_i = v_i + 1; END WHILE; END // DELIMITER ; CALL batch_update_status();这里有个关键经验:大批量 update 别一次性更新全表,不然会锁大量行、撑爆 undo log,甚至会阻塞其他事务。我习惯按主键分段处理,每段几万行,每段之间稍微停顿,这样对主库的影响可控很多。存储过程里也尽量别拼动态 SQL,因为一个错误不容易定位,还会带来注入风险。如果业务逻辑频繁变化,还是建议挪到应用层,存储过程只留给固定的后台任务。
4. 事务、锁与并发控制:高性能的第一道门槛
4.1 MySQL 锁体系速查
并发性能出问题,十有八九是锁没玩明白。InnoDB 的锁类型不少,我把实际工作中最重要的整理成一张速查表:
| 锁类型 | 锁粒度 | 作用场景 | 常见问题 |
|---|---|---|---|
| 全局锁 | 整个实例 | 全库备份 | 阻塞所有写操作 |
| 表锁 | 整张表 | DDL、MyISAM | 写并发直接挂起 |
| 元数据锁 | 表结构 | DDL/DML 并发 | 长事务阻塞 DDL |
| 意向锁 | 表级 | 行锁和表锁协调 | 通常无感知 |
| 行锁 | 单行 | 普通 DML | 行竞争 |
| 间隙锁 | 区间 | RR 隔离级别防止幻读 | 锁范围扩大 |
| 临键锁 | 索引区间 | RR 默认 | 容易死锁 |
| 自增锁 | 表级 | 自增列插入 | 批量插入性能下降 |
我的经验是,大多数“锁表”问题不是真的 LOCK TABLES 锁,而是行锁没释放:某个事务 update 了一批行却一直不提交,其他事务要改同一行就只能一直等待。排查时别光看表,要看事务。
4.2 隔离级别与 MVCC
InnoDB 是通过 MVCC 和锁共同实现事务隔离的。四个隔离级别分别解决不同的一致性问题:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 否 | 可能 | 可能 |
| REPEATABLE READ | 否 | 否 | 可能(但 InnoDB 间隙锁解决) |
| SERIALIZABLE | 否 | 否 | 否 |
MySQL 默认是 REPEATABLE READ,但 InnoDB 用间隙锁把幻读挡掉了大半,所以实际使用和整体一致性体验接近快照读。
MVCC 的原理可以理解为:每行记录在 undo log 里保存历史版本链,事务读数据时通过版本链看到“自己启动那一刻的快照”,这样读操作不会被写操作阻塞。这也是为什么我在前面反复强调 undo log 不能暴涨。有一次我处理一个跑了 5 小时没提交的事务,undo 把磁盘塞满,所有 delete/update 全部卡死,就是 MVCC 快照版本被长事务一直拽住无法清理。这种场景下的解决方案很明确:先查 longest transaction,然后和应用确认能否提交或 kill 掉。
4.3 锁表、锁等待与死锁排查实录
排查锁问题有现成的表和命令。MySQL 8.0 的 performance_schema 和 sys 库能直接告诉你是谁在等、谁在锁:
-- 当前事务列表 SELECT * FROM information_schema.innodb_trx\G -- 锁等待关系 SELECT * FROM sys.innodb_lock_waits\G -- 主从库上的锁等待数量 SHOW STATUS LIKE 'Innodb_row_lock_waits';从 innodb_trx 里能看到 trx_started、trx_state、trx_query,如果某个事务开始时间很早、状态是 RUNNING,且 SQL 迟迟没返回,基本就是它没提交导致后续事务排队。确定之后联系业务确认,必要时KILL <thread_id>。
死锁和锁等待不一样。锁等待是“一个等一个”,死锁是“你等我、我等你”,InnoDB 默认开启死锁检测,检测到会自动回滚代价小的事务。真遇到报错Deadlock found when trying to get lock,不要一上来就调参,先去 MySQL 错误日志里看死锁详情,看涉及哪两张表、哪些行、什么 SQL。我处理过一个高频死锁:两个事务分别按不同顺序更新 a、b 两张表,互相持锁等待。修复方法很简单:所有事务统一按 id 从小到大顺序更新,死锁自然消失。
5. 故障排查实录:这些坑,我替你们踩过了
5.1 mysql SSL 连接错误怎么解决
MySQL 8.0 一个高频报错是客户端连不上,像SSL connection error: unknown error,或者Access denied for user ... using password: YES。导致这个问题的原因通常不是密码错,而是 8.0 默认认证插件 caching_sha2_password 在非 SSL 连接下需要额外的 RSA 公钥交换,老客户端或某些驱动不支持。
临时排查手段是显式跳过 SSL:
mysql -uroot -p -h127.0.0.1 --ssl-mode=DISABLED如果能连上,说明问题确实在 SSL/认证流程。正式解法有三条:一是升级客户端驱动到支持 caching_sha2_password 和 SSL 的版本;二是连接串上配置useSSL=true&verifyServerCertificate=false;三是把账号改回 mysql_native_password。第三种方式在 MySQL 8.0 里还可用但不推荐,因为 mysql_native_password 也逐步被官方标记为废弃。我自己的习惯是:能升级驱动就升级驱动,生产环境保持默认认证插件不动,不要为了省事关掉 SSL,那等于把数据库密码裸奔在内网上。
5.2 Windows 下 net start mysql 服务无法启动
Windows 上安装 MySQL 后net start mysql报“服务无法启动”很常见。这个报错信息本身没有任何价值,真正的问题在 error log。默认日志文件一般在 MySQL 数据目录下,名为hostname.err,打开后往往能看到以下几类:
| 日志关键字 | 真正原因 | 处理方式 |
|---|---|---|
| Can't create/write to file | 数据目录无权限 | 以管理员运行,或重新设置目录权限 |
| unknown variable | my.ini 配置项写错 | 修正配置,删掉不认识的参数 |
| Cannot find file | 服务关联的 mysqld.exe 路径不对 | 重新配置服务,指定完整路径 |
| InnoDB: Unable to lock | 数据目录被占用 | 确认没有残留 mysqld 进程 |
这类问题的通用解法:先重新初始化一份干净的 data 目录,命令是mysqld --initialize-insecure(空密码 root),再用mysqld --install MySQL --defaults-file="C:\...\my.ini"重建服务。最坑的是手写 my.ini 时把datadir路径写到了不存在的位置,MySQL 不会告诉你“路径不存在”,只会在启动时默默失败,看错误日志才能发现。
5.3 升级报错 Invalid MySQL server upgrade
升级 MySQL 8.0 时,日志里出现[ERROR] [MY-014060] [Server] Invalid MySQL server upgrade:这类提示,说明数据目录版本和当前二进制版本不匹配,或者升级流程没走完。我遇到过的典型场景:用 8.0.35 的 mysqld 启动了一个之前由 8.0.20 初始化过的数据目录,MySQL 检测到需要升级,但升级没有自动跑成功,进程直接拒绝启动。
处理思路是这样:备份数据目录后,确认二进制版本是 8.0 且高于旧版本,然后显式触发升级:
# 停库后执行 mysqld --upgrade=FORCE --user=mysqlMySQL 8.0.16 之后,官方已经不建议单独执行 mysql_upgrade 命令,而是用上面的 mysqld 启动参数。升级完成后注意检查系统表中是否有版本记录,比如SELECT * FROM mysql.user能否正常查询。这类错误最怕硬着头皮反复启动,可能导致系统表进一步损坏,风险很大,生产环境升级一定先做全量备份。
5.4 Xtrabackup 备份与 GTID 主从同步
真正生产级的高可用方案里,Percona Xtrabackup 是备份 MySQL 最常用的工具,尤其面对大实例时,物理备份比 mysqldump 快好几个量级。备份和恢复的套路如下:
# 全量备份 xtrabackup --backup --target-dir=/backup/full \ --user=backup_user --password=xxx --host=127.0.0.1 # prepare,使备份可恢复 xtrabackup --prepare --target-dir=/backup/full # 恢复到新实例 xtrabackup --copy-back --target-dir=/backup/full主从同步现在基本都用 GTID,比传统 file+pos 方式更省心,因为同步位点由事务 ID 自动管理,不怕找错 binlog 文件名。启用 GTID 需要在主从的 my.cnf 里都配置:
server-id = 1001 log-bin = mysql-bin gtid_mode = ON enforce_gtid_consistency = ON从库恢复好备份后,直接 change master 指向主库:
CHANGE MASTER TO MASTER_HOST='192.168.1.20', MASTER_USER='repl_user', MASTER_PASSWORD='xxx', MASTER_AUTO_POSITION=1; START SLAVE; SHOW SLAVE STATUS\G看Replica_IO_Running和Replica_SQL_Running都是 Yes,这两个线程才表示同步正常。GTID 方案最常用的排错技巧就是对比主从的gtid_executed集合,如果从库多了一段主库没有的 GTID,基本就是手工在从库执行过写入,这种不一致会越积越深,必须尽早解决。
5.5 高频问题速查表
最后把我在社区和群聊里经常看到的问题整理成一张速查表,按场景定位很快:
| 场景 | 报错现象 | 常见原因 | 处理命令/方案 |
|---|---|---|---|
| Sqoop 抽数 | 连不上 MySQL | JDBC 驱动未放入 lib、host 不一致 | 下载 mysql-connector-java 并置于 sqoop/lib |
| 命令行执行 SQL | 一直等待无返回 | 连接超时参数过小、有锁等待 | 调大 connect_timeout / net_read_timeout,查 innodb_trx |
| MySQL 排序 | 结果和 App 预期不一致 | collation 不是预期规则 | 改字段 collation 为 utf8mb4_bin |
| 修改表结构 | 执行卡住 | 元数据锁被长事务阻塞 | 查 innodb_trx,kill 长事务 |
| UPDATE 误操作 | 数据被大批量改错 | 忘写 where | 利用备份+binlog 做时间点恢复 |
| Docker 映射端口 | 容器起不来 | 3306 被占用 | netstat -ano 查占用进程,换端口 |
| Yum 安装 | 版本不对 | 装了系统自带 mariadb | 先卸载,再装官方 repo |
这张表里每条都是真实生产环境的反馈,不是理论推导。我尤其想强调最后一行:很多人贪方便选 Yum 源,可能装了 MariaDB 或旧版本,后面字符集、认证方式全对不上,排查成本比安装成本高得多。
6. 写在最后的个人体会
这些内容写到最后,我自己最大的体会是:MySQL 的性能问题很少是单个参数造成的,绝大多数是表结构、索引、SQL 和执行计划共同作用的结果。调优的第一步永远是先看慢查询日志,第二步是 EXPLAIN,第三步才谈参数。如果只能留一条建议,我大概会先说:把 innodb_buffer_pool_size 设到物理内存的 50%~70%,然后老老实实学会看执行计划,剩下的都是在这个基础上查漏补缺。
另外还想分享一个细节:不要觉得生产环境“加一台从库”就能解决所有慢查询。从库也要跑同样的 SQL,索引没建好一样会卡;主从延迟还会引出一堆强一致性问题。真正稳妥的做法是把技术体系里的每个环节都过一遍,从存储引擎到日志、从锁到索引、从备份到同步,每块都知道它是怎么工作的、会在哪里坏。遇到问题的时候,这些东西串起来,答案往往自己就浮出来了。