【MySQL】SQL优化

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


1.insert优化

(1)批量插入

  • 批量插入
sql 复制代码
INSERT INTO tb_test VALUES (1,'Tom'), (2,'Cat'), (3,'Jerry');
  • 手动提交事务
sql 复制代码
START TRANSACTION;
INSERT INTO tb_test VALUES (1,'Tom'), (2,'Cat'), (3,'Jerry');
INSERT INTO tb_test VALUES (4,'Tom'), (5,'Cat'), (6,'Jerry');
INSERT INTO tb_test VALUES (7,'Tom'), (8,'Cat'), (9,'Jerry');
COMMIT;
  • 主键顺序插入:顺序插入可以减少页分裂,提高插入效率。

(2)大批量插入数据

 如果一次性需要插入大批量数据,使用 INSERT 语句性能较低,此时可以使用 MySQL 提供的 LOAD 指令进行插入。操作步骤如下:

sql 复制代码
-- 客户端连接服务端时,加上参数 --local-infile
mysql --local-infile -u root -p

-- 设置全局参数 local_infile 为 1,开启从本地加载文件导入数据的开关
SET GLOBAL local_infile = 1;

-- 执行 load 指令将准备好的数据加载到表结构中
LOAD DATA LOCAL INFILE '/root/sql1.log' INTO TABLE tb_user 
FIELDS TERMINATED BY ',' 
LINES TERMINATED BY '\n';

2.主键优化

(1)数据组织方式

 在 InnoDB 存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(Index Organized Table, IOT)。

 存储结构层次:表空间 → 段 → 区 → 页 → 行

(2)页分裂

 如果是乱序插入,例如叶子节点已有序,此时插入数据 50,但 1 号数据页空间不足,就会产生页分裂。

(3)页合并

 当删除一行记录时,实际上记录并没有被物理删除,只是被标记(flagged)为删除,其空间允许被其他记录使用。

 当页中删除的记录达到 MERGE_THRESHOLD(默认为页大小的 50%)时,InnoDB 会开始寻找最靠近的页(前或后),尝试将两个页合并以优化空间使用。

参数 MERGE_THRESHOLD 表示合并页的阈值,可以自行设置,在创建表或创建索引时指定。

(4)主键设计原则

  • 满足业务需求的情况下,尽量降低主键的长度(因为二级索引中会存储主键值)。
  • 插入数据时,尽量选择顺序插入,推荐使用 AUTO_INCREMENT 自增主键(顺序插入可防止页分裂)。
  • 尽量不要使用 UUID 做主键或其他自然主键(如身份证号),因为它们是乱序的。
  • 业务操作中,避免对主键的修改。

3.orderd by的优化

MySQL 中排序的两种方式如下,

  • Using filesort : 通过表的索引或全表扫描读取满足条件的数据行,然后在排序缓冲区(sort buffer)中完成排序。所有不是通过索引直接返回排序结果的排序都称为 filesort。
  • Using index : 通过有序索引顺序扫描直接返回有序数据,不需要额外排序,效率高。

默认创建索引时,索引列按升序(ASC)排列。

sql 复制代码
-- 未创建索引时,根据 age, phone 排序(Using filesort)
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age, phone;

-- 创建索引(age, phone 均升序)
CREATE INDEX idx_user_age_phone_aa ON tb_user(age, phone);

-- 此时排序使用索引(Using index)
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age, phone;

-- 改变排序顺序(phone, age),Using index + Using filesort
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY phone, age;

-- 全部降序,使用 Backward index scan + Using index
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age DESC, phone DESC;

-- 一升一降,Using index + Using filesort
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age ASC, phone DESC;

-- 创建 age 升序、phone 降序的索引
CREATE INDEX idx_user_age_pho_ad ON tb_user(age ASC, phone DESC);

-- 此时排序使用索引(Using index)
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age ASC, phone DESC;

优化总结

  • 根据排序字段建立合适的索引,多字段排序时也遵循最左前缀法则。
  • 尽量使用覆盖索引。
  • 多字段排序时,如果一个升序一个降序,需要注意联合索引在创建时的规则(指定 ASC / DESC)。
  • 如果不可避免出现 filesort,在大数据量排序时可以适当增大排序缓冲区大小 sort_buffer_size(默认 256 KB)。

4.group by的优化

  • 在分组操作时,可以通过索引来提高效率。
  • 分组操作时,索引的使用也是满足最左前缀法则的。
sql 复制代码
-- 未创建索引时,根据 profession 分组(Using temporary)
EXPLAIN SELECT profession, COUNT(*) FROM tb_user GROUP BY profession;

-- 创建联合索引
CREATE INDEX idx_user_pro_age_sta ON tb_user(profession, age, status);

-- 根据 profession 分组,使用索引(Using index)
EXPLAIN SELECT profession, COUNT(*) FROM tb_user GROUP BY profession;

-- 根据 age 分组,不符合最左前缀,Using index + Using temporary
EXPLAIN SELECT profession, COUNT(*) FROM tb_user GROUP BY age;

-- 根据 profession, age 分组,使用索引(Using index)
EXPLAIN SELECT profession, COUNT(*) FROM tb_user GROUP BY profession, age;

5.limit分页查询

 对于LIMIT 2000000, 10操作来说,此时 MySQL 需要排序前 2000010 条记录,然后仅返回第 2000000 到 2000010 条,其他记录被丢弃,查询排序的代价非常大。

sql 复制代码
-- 原始分页查询(效率低)
SELECT * FROM tb_sku ORDER BY id LIMIT 9000000, 10;

-- 改进后:使用覆盖索引 + 子查询的方式
SELECT s.* 
FROM tb_sku s 
JOIN (SELECT id FROM tb_sku ORDER BY id LIMIT 9000000, 10) a 
ON s.id = a.id;

优化思路:先通过子查询获取主键(覆盖索引避免回表),再与原表关联获取完整数据。

6.count

sql 复制代码
explain select count(*) from tb_user ;
  • 对于上面的代码不同存储引擎之间存在差异,优化思路为自己手动计数(例如创建统计表)。
    • MyISAM:把一个表的总行数存储在磁盘上,执行 COUNT(*) 时直接返回,效率很高。
    • InnoDB:执行 COUNT(*) 时需要把数据一行一行地从引擎中读出来,然后累积计数。

count的几种用法

  • count(主键):InnoDB 引擎会遍历整张表,把每一行的 主键id 值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为null)。
  • count(字段):
    • 没有not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。
    • 有not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
  • count(1):InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字 "1" 进去,直接按行进行累加。
  • count(*):InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。

按照效率排序的话,count(字段)<count(主键 id) <count(1) ≈ count(),所以尽量使用 count()。

7.update 优化

 InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁。如果条件中的索引失效,行锁会升级为表锁。

  • 在 UPDATE 操作中,尽量使用带索引的列作为条件,避免索引失效导致表锁。
  • 如果无法避免,应尽量缩小锁范围或优化业务逻辑。

 现在有下面这张表结构

sql 复制代码
+----+--------+
| id | name   |
+----+--------+
| 1  | javaEE |
| 2  | PHP    |
| 3  | MySQL  |
| 4  | Kafka  |
+----+--------+
sql 复制代码
begin;
update course set name ='SpringBoot' where name ='PHP';
sql 复制代码
begin;
update course set name ='Kafka 2' where id = 4;

 对于上面的例子,由于name没有索引,因此此时加的是表锁,另一个事务会被阻塞,直到 COMMIT 后才能执行。(行锁升级为表锁的问题)

 添加name的索引后不会阻塞其他事务(此时加的是行锁)

相关推荐
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第89题】【Mysql篇】第19题:Hash 索引和 B+ 树索引的区别?它们在使用方面的区别?
java·数据库·mysql·面试·哈希算法
ImTryCatchException1 小时前
Android 性能优化实战手册:从理论到落地的完整方法论
android·性能优化
sun0077001 小时前
qnx网络相关模块,全链路,硬件网卡 → 用户态驱动 (.so) → io‑pkt/io‑sock(用户态 TCP/IP + 转发 + 控制)
android
元宝骑士2 小时前
MySQL 实战:跨表排序 + 指定类型置顶四种写法
后端·mysql
流星白龙2 小时前
【MySQL高阶】11.InnoDB存储引擎
数据库·mysql
赏金术士2 小时前
Android app 项目:模块打包 AAR 教程
android·热修复·tinker·aar打包
ImTryCatchException2 小时前
React Native 嵌入现有 Android 项目:踩坑记录与解决方案
android·react native·react.js
曼岛_2 小时前
[安卓逆向]在Android Studio中编写SO文件并测试调用 (四)
android·ide·android studio
xcLeigh3 小时前
KES数据库运维监控与故障排查实战
运维·数据库·sql·故障排查·运维监控·kes