MySQL 极致性能优化指南——从 INSERT 到 UPDATE 的七大战场
MySQL 极致性能优化指南
——从 INSERT 到 UPDATE 的七大战场
适用版本:MySQL 8.0+
引擎:InnoDB(默认)
目标:同等硬件,并发翻倍,延迟减半
目录
- 插入数据优化
- 主键优化
- ORDER BY 优化
- GROUP BY 优化
- LIMIT 优化
- COUNT 优化
- UPDATE 优化(避免行锁升级)
1. 插入数据优化
- InnoDB 每次插入都隐式开启事务,自动提交 = 每条一次 log flush → 磁盘 IO 暴涨
- 顺序主键可减少页分裂
- 大批量场景
LOAD DATA
比INSERT
快 10~20 倍
代码示例
-- 1. 单条→批量(10 倍提升)
INSERT INTO tb_user VALUES (1,'Tom'),(2,'Cat'),(3,'Jerry'); -- 合 1 条 SQL-- 2. 手动事务包裹(减少 log flush 次数)
START TRANSACTION;
INSERT INTO tb_user VALUES (1,'Tom'),(2,'Cat');
INSERT INTO tb_user VALUES (3,'Jerry'),(4,'Mary');
COMMIT;-- 3. 百万级导入 LOAD DATA(17 s 导入 100 W)
mysql --local-infile -u root -p
SET GLOBAL local_infile = 1;
LOAD DATA LOCAL INFILE '/tmp/user_100w.csv'
INTO TABLE tb_user
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n';
案例
某日志表 500 W 行/天,从
Spring Batch 单条 insert
改为LOAD DATA
后,导入耗时由 45 min → 2 min 30 s。
2. 主键优化
笔记
- InnoDB 聚簇索引 → 数据行即叶子节点
- 乱序插入触发 页分裂(移动 50 % 数据)
- 过长主键使二级索引翻倍膨胀
代码示例
-- ✔ 推荐:短、自增、业务无关
CREATE TABLE good_pk (id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL UNIQUE
) ENGINE=InnoDB;-- ✘ 反面:UUID 字符串,长且乱序
CREATE TABLE bad_pk (id CHAR(36) PRIMARY KEY, -- 36 字节,二级索引也跟着 36 字节username VARCHAR(50)
) ENGINE=InnoDB;
案例
订单表主键从
UUID
改为雪花 ID(有序 Long)
,主键长度 36 → 8 字节,索引文件缩小 55 %,QPS 提升 30 %。
3. ORDER BY 优化
笔记
- 利用 索引有序性 消除额外排序(
Using filesort
) - 联合索引 升降序 必须与 SQL 一致
- 覆盖索引可彻底不回表
代码示例
-- 联合索引与排序方向一致
CREATE INDEX idx_age_phone ON tb_user(age ASC, phone DESC);-- 走索引,无 filesort
EXPLAIN SELECT age, phone
FROM tb_user
ORDER BY age ASC, phone DESC;
-- Extra: Using index
案例
分页查询
ORDER BY create_time DESC LIMIT 100000,10
原耗时 3.2 s;
增加(create_time, id)
联合索引后 0.03 s,Extra 由Using filesort
→Using index
。
4. GROUP BY 优化
笔记
- 语义:先排序后分组 → 索引有序可省排序
- 分组列 + 查询列 = 覆盖索引,可 索引一把梭
代码示例
-- 索引顺序与 GROUP BY 一致
CREATE INDEX idx_dept_sal ON emp(dept_id, salary);-- 无临时表、无 filesort
SELECT dept_id, COUNT(*) cnt, MAX(salary) max_sal
FROM emp
GROUP BY dept_id;
案例
报表 SQL 按
dept_id
分组,原 临时表 + filesort 2.1 s;
加联合索引后 0.05 s,Extra 变为Using index
。
5. LIMIT 优化
笔记
LIMIT offset, N
越往后越慢(全表扫描 offset 行)- 延迟关联 / 游标分页 把“跳行”改为“过滤主键”
代码示例
-- 传统深分页(慢)
SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;-- 延迟关联(快)
SELECT o.*
FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 20) t
ON o.id = t.id;
案例
管理后台翻页到 100 W 行,延迟关联 从 4.5 s → 0.08 s。
6. COUNT 优化
笔记
COUNT(*)
会 遍历最小二级索引(非聚簇)- MyISAM 维护行计数器,但不支持事务
- 业务大屏可用 缓存 / 近似值 / 汇总表
代码示例
-- 最小索引扫描
SELECT COUNT(*) FROM user; -- 自动选 idx_status 而非聚簇索引-- 汇总表(秒级)
CREATE TABLE user_count (cnt BIGINT UNSIGNED);
-- 定时刷新
REPLACE INTO user_count SELECT COUNT(*) FROM user;
-- 查询count
SELECT cnt FROM user_cnt_summary LIMIT 1; -- < 10 ms
案例
1.2 亿行用户表
COUNT(*)
12 s;
增加 每日汇总表 后接口 < 10 ms。
7. UPDATE 优化(避免行锁升级)
笔记
- 无索引列更新 → 全表扫描 + 表锁(行锁升级)
- 尽量 按主键/索引列 精确过滤
- 批量更新拆小事务,锁持有时间 < 200 ms
代码示例
-- 反面:无索引,锁全表
UPDATE orders SET status=1 WHERE create_time < '2025-01-01';-- 正面:先加索引,再按主键范围更新
ALTER TABLE orders ADD INDEX idx_ctime_id(create_time, id);-- 分批更新,防锁等待
UPDATE orders
SET status=1
WHERE id BETWEEN 1 AND 100000AND create_time < '2025-01-01';
-- 循环 + 睡眠 100 ms,直至结束
案例
凌晨批量更新 300 W 行,原 单条 SQL 锁表 90 s 导致业务超时;
改为 索引 + 每批 1 W 行 后,锁等待降为 0,耗时 3 min 平滑结束。
8. 一键检查清单(上线前对照)
场景 | 检查 SQL | 目标 |
---|---|---|
插入 | EXPLAIN INSERT ... | 无自动提交、主键顺序 |
主键 | SHOW INDEX / SELECT COUNT(DISTINCT pk) | 短、有序、非业务 |
排序 | EXPLAIN ... ORDER BY | Extra ≠ Using filesort |
分组 | EXPLAIN ... GROUP BY | Extra ≠ Using temporary |
分页 | EXPLAIN ... LIMIT offset, N | 走索引 + 延迟关联 |
计数 | SHOW TABLE STATUS / 汇总表 | < 100 ms |
更新 | EXPLAIN UPDATE ... WHERE | 用索引、分批 |
9. 结论
MySQL 优化是“索引 + 算法 + 习惯”的组合拳:
- 写:顺序主键、批量提交、LOAD DATA
- 读:覆盖索引、最左前缀、延迟关联
- 锁:精准过滤、小事务、分批
把本文 SQL 全部 EXPLAIN 一遍,让 Extra 里只剩 Using index,你就拥有了生产级的高可用 MySQL。