一、Insert 插入优化
1. 批量多行插入(优先推荐)
不要多次单条 insert,改成一条 SQL 批量写入,减少网络 IO 开销
sql
sql
INSERT INTO tb_test VALUES(1, 'Tom'),(2, 'Cat'),(3, 'Jerry');
2. 优化小技巧
- 手动提交事务:多条插入包裹在事务内,减少事务提交次数
- 主键顺序插入:自增主键顺序写入性能远高于乱序主键
3. 超大批量数据:load data local infile
几十万、百万级数据,普通 insert 效率很低,使用本地文件高速导入
sql
sql
-- 1.客户端启动时开启本地文件权限
mysql --local-infile -u root -p
-- 2.开启全局本地文件导入开关
SET GLOBAL local_infile = 1;
-- 3.查看参数是否开启成功
SELECT @@local_infile;
-- 4.加载本地文件导入数据表
LOAD DATA LOCAL INFILE '/root/sql1.log'
INTO TABLE tb_user
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n';
导入文件内数据主键有序可以规避页分裂,导入速度更佳。
二、主键优化(InnoDB 索引组织表 IOT)
InnoDB 属于索引组织表 (IOT),数据表物理行按照主键顺序存放。 因此主键设计直接影响页分裂、页合并,决定写入性能。
1. 页分裂
- InnoDB 每页存储 2~N 行记录;行太大触发行溢出
- 乱序主键插入 :随机 UUID、无序业务主键,新主键值需要插入到已有页中间,空间不足就会开辟新页,产生页分裂,消耗大量 IO。
2. 页合并
- 删除数据不会立刻物理删除,仅打上删除标记,空间可复用
- 参数
MERGE_THRESHOLD默认阈值 50% - 当页面空闲空间达到阈值,InnoDB 会尝试和相邻页面合并,优化存储空间。
3. 主键设计黄金原则
- 在业务允许前提下,主键字段长度尽量短
- 使用
AUTO_INCREMENT自增主键,保证顺序插入,杜绝页分裂 - 禁止 UUID、身份证号等无序自然主键
- 业务代码尽量不要修改主键值
三、order by 排序优化
explain Extra 字段两种排序模式:
- Using filesort
没有有序索引;查询出符合条件的数据,放到 sort_buffer 排序缓冲区完成排序,性能较差。
- Using index(覆盖索引排序,最优)
依靠有序联合索引直接扫描返回有序结果,不需要额外排序。
实战演示
sql
sql
-- 未建索引,Extra为 Using filesort
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age;
-- 创建联合索引
CREATE INDEX idx_user_age_phone ON tb_user(age, phone);
-- 再次执行,命中 Using index
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age;
多字段排序坑点:最左前缀法则
sql
sql
-- 索引(age,phone),先按phone排序,违背最左前缀 → filesort
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY phone, age;
升降序混合排序
- 索引字段排序方向要和 SQL order by 升降序匹配
sql
sql
CREATE INDEX idx_user_age_pho_ad ON tb_user(age ASC, phone DESC);
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age ASC, phone DESC;
order‑by 优化总结
- 排序字段建立联合索引,遵守最左前缀法则
- 优先使用覆盖索引,避免回表
- 多字段升降序不一致时,索引 ASC/DESC 需要对应排序规则
- 无法避免 filesort:调大排序缓冲区
sort_buffer_size
四、group by 分组优化
group by 本质也需要排序分组,同样依赖索引优化。
sql
sql
-- 单字段分组
EXPLAIN SELECT profession, COUNT(*) FROM tb_user GROUP BY profession;
-- 多字段分组,索引顺序必须遵守最左前缀
EXPLAIN SELECT profession, age, COUNT(*) FROM tb_user GROUP BY profession, age;
优化要点:分组字段建立联合索引,多字段分组顺序匹配索引顺序,跳过额外排序操作。
五、limit 深分页优化
问题根源
limit offset,size,offset 数值越大,MySQL 需要扫描前面大量废弃数据,速度越慢
sql
sql
-- 深度分页,性能极差
SELECT * FROM tb_sku LIMIT 9000000,10;
优化方案:覆盖索引 + 子查询关联
- 先通过索引快速查出目标页主键 id(覆盖索引无需回表,速度很快)
- 再根据主键关联查询整行数据
sql
sql
SELECT s.*
FROM tb_sku s
INNER JOIN (
SELECT id FROM tb_sku ORDER BY id LIMIT 9000000,10
) a ON s.id = a.id;
六、count 计数优化
四种 count 底层原理(InnoDB)
- count (主键 id):遍历整张表读取每一行主键,返回到服务层累加
- count (普通字段)
- 字段无
not null:读取字段、判断 null 之后计数 - 字段有
not null:直接读取字段值累加
- 字段无
- count(1):引擎遍历表,不需要读取字段,服务层填充 1 进行计数
- count(*):MySQL 专门做底层优化,不读取任何行字段,直接按行计数,性能天花板
性能排序
count(字段) < count(主键id) < count(1) < count(*)
开发规范:统计总行数固定使用
COUNT(*)超大数据量表可以业务层维护计数器,实时缓存总数,避免频繁 count 扫描全表。
七、update 更新优化(行锁 & 表锁大坑)
InnoDB 的行锁是加在索引上,不是加在记录上;索引失效,行锁直接升级成全表表锁!
示例
sql
sql
-- id为主键索引,命中索引 → 行锁,只锁住该行
UPDATE course SET name = 'javaEE' WHERE id = 1;
-- name无索引,索引失效 → 升级表锁,锁住整张表阻塞其他写入
UPDATE course SET name = 'SpringBoot' WHERE name = 'PHP';
-- 为更新条件字段建立索引
CREATE INDEX idx_course_name ON course(name);
-- 索引生效,走行锁
UPDATE course SET name = 'Spring' WHERE name = 'SpringBoot';
update 优化总结
- where 后面的更新条件字段一定要建立索引
- 避免索引失效:函数、隐式类型转换、模糊前缀通配符都会废掉索引,触发表锁
八、SQL 优化整体总结
- Insert :批量插入、事务手动提交、主键顺序写入;海量数据优先
load data local infile - 主键优化:短主键、自增顺序主键,减少页分裂和页合并开销,禁用无序 UUID 主键
- order by 依靠有序索引实现 Using index,规避 Using filesort,遵守最左前缀、覆盖索引
- group‑by:分组字段建立联合索引
- limit 深分页:覆盖索引子查询先查主键再关联整表
- count :优先
count(*) - update:更新条件带上有效索引,防止行锁升级表锁
- MySQL 绝大多数 SQL 优化本质 = 索引优化