MySQL——SQL优化

该文章已生成可运行项目,

前言

SQL优化是提升数据库性能、减少系统资源消耗的关键。本文将从多个方面介绍SQL优化的策略、最佳实践和实际案例,帮助您编写更高效的SQL语句。

一、SQL语句优化技巧

1.避免使用SELECT *

问题:SELECT * 会查询出表中的所有数据,即使有些列在业务中不需要,这样会导致数据库资源浪费(CPU、IO、内存、网络带宽),并可能导致无法使用覆盖索引,增加回表操作。

SELECT * 会消耗更多的 CPU。

SELECT * ⽆⽤字段增加⽹络带宽资源消耗,增加数据传输时间,尤其是

SELECT * ⼤字段(如 varchar、blob、text)。

SELECT * SELECT * ⽆法使⽤ MySQL 优化器覆盖索引的优化(基于 MySQL 优化 器的“覆盖索引”策略⼜是速度极快,效率极⾼,业界极为推荐的查询优化⽅ 式)

SELECT * <字段列表> 可减少表结构变更带来的影响。

回表是什么:MySQL回表是什么?哪些情况下会回表-CSDN博客

优化:明确指定需要查询的列,例如:

SELECT name,age FROM user WHERE id=1;

这样有助与减少数据传输量,并提高使用覆盖索引的可能性。

2.使用UNION ALL 代替 NUION

问题:NUINO 操作会进行去重,这涉及到遍历、排序和比较,回消耗更多的CPU资源和时间。

UNION ALL 不会对结果集进行去重操作,获取到的数据包含重复项。

优化:如果明确知道两个结果集没有重复数据,或者业务允许重复数据,应使用UNION ALL。

例如:

(SELECT * FROM user WHERE id=1)UNION ALL ((SELECT * FROM user WHERE id=2); 

3.小表驱动大表

问题:在多表关联查询时,如果驱动选择不当,可能导致性能下降。

优化:

IN适用于左边大表,右边小表:

SELECT * FROM order WHERE user_id IN (SELECT id FROM user WHERE status=1);

EXISTS适用于左边小表,右边大表:

SELECT * FROM order WHERE EXISTS (SELECT 1 FROM user WHERE order.user_id = user.id AND status=1);

4.批量操作

问题:循环条件插入、更新或删除数据会导致多次数据请求,增加网络IO和数据负载。

优化: 采用批量操作,将多条SQL语句合并为一条。

例如批量插入:

INSERT INTO order(id,code,user_id) VALUES(123,'001',100),(124,'002',100),(125,'003',101);

建议每次批量操作的数据量控制在500以内

循环插入数据:

批量插入数据:

5.善用LIMIT

问题:查询只需要少量数据时,不加LIMIT会返回所有匹配数据,浪费资源。

优化: 当只需要查询部分数据时,使用LIMIT限制返回的记录数。

例如:

SELECT id, create_date FROM order WHERE user_id=123 ORDER BY create_date ASC LIMIT 1;

6.避免IN中值过多

问题:IN子句中包含大量值时,可能会导致查询效率低下甚至超时。

优化:

•在SQL中对数据使用LIMIT限制。

•在业务代码中限制IN子句中的值数量,例如限制在500条以内。

•如果数据量过大,可以考虑分批次、多线程查询。

7.,增量查询

问题:频繁全量同步数据会导致大量不必要的IO和网络传输

优化:针对需要同步的数据,使用增量查询,只同步发生变化的数据。例如,通过时间戳或自增ID来标识新增或修改的数据。

8.避免使用子查询(针对MySQL 5.5及更早版本)

问题:在MySQL 5.5及更早版本中,子查询的执行计划可能效率低下,先查外表再匹配内表。

优化:尽量将子查询改写为JOIN关联查询。

例如:

SELECT t1.* FROM t1 JOIN t2 ON t1.id = t2.id

虽然MySQL 5.6及更高版本对SELECT子查询进行了优化,但对UPDATE/DELETE子查询仍然无效,因此建议避免使用。

9.用IN代替OR

问题:在WHERE子句中使用多个OR连接条件,可能导致索引失效。

优化L:对于等值判断,使用IN代替OR,

例如:

SELECT t1.* FROM t1 JOIN t2 ON t1.id = t2.id

于连续的数值,优先使用BETWEEN。

10.禁止不必要的ORDER BY排序

问题:不必要的排序会消耗额外的CPU资源。

优化如果对结果没有排序要求,尽量避免使用ORDER BY ,在ORDER BY 查询中,如果不需要排序,可以添加ORDER BY NULL 来禁止默认排序:

SELECT goods_id,COUNT(*) FROM t GROUP BY goods_id ORDER BY NULL;

11.避免随机取记录

问题: 使用ORDER BY RAND()会导致全表扫描,无法利用索引,性能极差。

优化:避免使用ORDER BY RAND()。如果需要随机记录,可以考虑其他方法,例如先获取总数,然后生成随机数,再结合LIMIT和OFFSET查询。

12.优化GROUP BY语句

问题:GROUP BY操作可能导致使用临时表和文件排序,影响性能。

优化:

•如果对GROUP BY结果没有排序要求,添加ORDER BY NULL。

•尽量让GROUP BY过程利用表的索引,通过EXPLAIN查看结果中是否包含Using temporary和Using filesort。

•适当调大tmp_table_size参数,尽量使用内存临时表。

•如果数据量实在太大,可以使用SQL_BIG_RESULT提示,让优化器直接使用排序算法(磁盘临时表)。

13.区分IN 和 EXISTS

问题:IN和EXISTS在某些场景下性能差异较大,选择不当可能影响效率。

优化:

•IN适合于外表大而内表小的情况。

•EXISTS适合于外表小而内表大的情况。

14.减少使用通配符字符

问题:在WHERE子句中使用%开头的模糊查询(例如LIKE '%keyword%')会导致索引失效,进行全表扫描。

优化: 尽量避免使用%开头的模糊查询。如果必须使用,可以考虑使用全文索引或搜索引擎。

15.避免在WHERE子句中使用!=或<>操作符

问题::!=或<>操作符会导致引擎放弃使用索引而进行全表扫描。

优化:尽量使用=、>、<、>=、<=、BETWEEN等操作符

16.避免在WHWERE子句中对列进行函数操作或者表达式计算

问题:对列进行函数操作或表达式计算会导致索引失效。

优化:将函数操作或表达式计算放到=的右侧。

例如:

//将
WHERE DATE(create_time) = '2023-01-01'
//改为
WHERE create_time >= '2023-01-01 00:00:00' AND create_time < '2023-01-02 00:00:00'

17.使用JOIN代替子查询(通用原则)

问题:子查询在某些数据库中可能效率不高,尤其是在处理大量数据时。

优化:尽可能使用JOIN来连接表,因为JOIN通常会比子查询更高效,数据库优化器能更好地处理JOIN操作。

注:尽量避免多表做JOIN,阿⾥巴巴《Java 开发⼿册》中说到:超过三个表禁⽌ join。需要 join 的字段,数据类型保持绝对⼀致;多表 关联查询时,保证被关联 的字段需要有索引。

join 的效率⽐较低,主要原因是因为其使⽤嵌套循环(Nested Loop)来实现关

联查询,三种不同的实现效率都不是很⾼:

Simple Nested-Loop Join :没有进过优化,直接使⽤笛卡尔积实现 join, 逐⾏遍历/全表扫描,效率最低。

Block Nested-Loop Join :利⽤ JOIN BUFFER 进⾏优化,性能受到 JOIN BUFFER ⼤⼩的影响,相⽐于 Simple Nested-Loop Join 性能有所提 升。不过,如果两个表的数据过⼤的话,⽆论如何优化,Block NestedLoop Join 对性能的提升都⾮常有限。

Index Nested-Loop Join :在必要的字段上增加索引,使 join 的过程中可以使⽤到这个索引,这样可以让 Block Nested-Loop Join 转换为 Index Nested-Loop Join,性能得到进⼀步提升。

实际业务场景避免多表 join 常⻅的做法有两种:

1. 单表查询后在内存中⾃⼰做关联 :对数据库做单表查询,再根据查询结果进 ⾏⼆次查询,以此类推,最后再进⾏关联。

2. 数据冗余,把⼀些重要的数据在表中做冗余,尽可能地避免关联查询。很笨 的⼀张做法,表结构⽐较稳定的情况下才会考虑这种做法。进⾏冗余设计之 前,思考⼀下⾃⼰的表结构设计的是否有问题。更加推荐第⼀种,这种在实际项⽬中的使⽤率⽐较⾼,除了性能不错之外,还有

如下优势:

1. 拆分后的单表查询代码可复⽤性更⾼ :join 联表 SQL 基本不太可能被复 ⽤。

2. 单表查询更利于后续的维护 :不论是后续修改表结构还是进⾏分库分表,单 表查询维护起来都更容易。 不过,如果系统要求的并发量不⼤的话,我觉得多表 join 也是没问题的。很多公 司内部复杂的系统,要求的并发量不⾼,很多数据必须 join 5 张以上的表才能查出来。

可以去知乎上面找找相关话题。

二、索引优化

1.合理创建索引

重要性:索引时提高查询性能最有效的方法之一。

原则:

•在WHERE子句、JOIN条件和ORDER BY子句中经常使用的列上创建索引。

•避免在选择性低的列(如性别)上创建索引。

•考虑联合索引,覆盖查询的多个列。

•索引不是越多越好,过多的索引会增加写操作的开销。

2.避免索引失效

常见情况:

•在索引列上使用函数或表达式。

•使用OR连接条件(除非所有条件都使用索引)。

•LIKE '%keyword%'。

•数据类型不匹配。

•使用!=或<>。

3.删除长期未使用的索引

删除⻓期未使⽤的索引,不⽤的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使⽤

三、数据库结构优化

1.数据类型选择

原则:选择合适的数据类型,尽量使用占用空间小的数据类型。例如,能用TINYINT就不用INT,能用VARCHAR就不用TEXT

2.范式和反范式

原则:

•范式: 减少数据冗余,保持数据一致性,但可能导致多表联查,增加查询复杂度。

•反范式: 适当增加数据冗余,减少JOIN操作,提高查询性能,但可能导致数据一致性问题。 根据业务需求权衡范式和反范式。

3.表分区

作用:将大表分割成更小、更可管理的部分,提高查询效率,尤其是在处理历史数据时。

四、查询执行计划分析

工具: 使用EXPLAIN(MySQL)、EXPLAIN PLAN(Oracle)等工具分析SQL语句的执行计划。

关注点:

•type: 访问类型,从好到坏依次是system > const > eq_ref > ref > range > index > ALL。

•rows: 扫描的行数,越少越好。

•Extra: 额外信息,关注Using filesort(文件排序)和Using temporary(使用临时表),这些都表示需要优化。

五、数据库配置优化

1. 调整缓冲区大小

作用: 增加数据库缓冲区(如MySQL的innodb_buffer_pool_size)可以减少磁盘IO,提高性能。

2. 优化连接池

作用: 合理配置数据库连接池大小,避免频繁创建和销毁连接,减少资源消耗。

六.分页优化

普通的分⻚在数据量⼩的时候耗费时间还是⽐较短的

SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC LIMIT

如果数据量变⼤,达到百万甚⾄是千万级别,普通的分⻚耗费的时间就⾮常⻓了。

SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC LIMIT
SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC LIMIT

优化:

SELECT `score`,`name` FROM `cus_order` WHERE id >= (SELECT id

我们先查询出 limit 第⼀个参数对应的主键值,再根据这个主键值再去过滤并

limit,这样效率会更快。

阿⾥巴巴《Java 开发⼿册》中也有对应的描述:

七、其他优化建议

1. 避免大事务

问题: 大事务会长时间锁定资源,影响并发性能。

优化: 将大事务拆分为小事务,减少事务的持续时间。

2. 使用存储过程和函数

作用: 存储过程和函数可以减少网络传输,提高执行效率,并增强代码的复用性。

3. 读写分离与分库分表

作用:

•读写分离: 将读操作和写操作分发到不同的数据库服务器,提高并发能力。

•分库分表: 当单表数据量过大时,将数据分散到多个数据库或表中,提高查询和写入性能。

4.Show Profile 分析 SQL 执⾏性能

为了更精准定位⼀条 SQL 语句的性能问题,需要清楚地知道这条 SQL 语句运⾏

时消耗了多少系统资源。 SHOW PROFILE 和 SHOW PROFILES 展示 SQL 语

句的资源使⽤情况,展示的消息包括 CPU 的使⽤,CPU 上下⽂切换,IO 等待,

内存使⽤等。

MySQL 在 5.0.37 版本之后才⽀持 Profiling, select @@have_profiling 命

令返回 YES 表示该功能可以使⽤。

注意 : SHOW PROFILE 和 SHOW PROFILES 已经被弃⽤,未来的 MySQL 版本中可能会被删除,取⽽代之的是使⽤ Performance Schema

想要使⽤ Profiling,请确保你的 profiling 是开启(on)的状态。 你可以通过 SHOW VARIABLES 命令查看其状态:

也可以通过 SELECT @@profiling 命令进⾏查看:

默认情况下, Profiling 是关闭(off)的状态,你直接通过 SET @@profil

ing=1 命令即可开启。

开启成功之后,我们执⾏⼏条 SQL 语句。执⾏完成之后,使⽤ SHOW PROFILE

S 可以展示当前 Session 下所有 SQL 语句的简要的信息包括 Query_ID(SQL

语句的 ID 编号) 和 Duration(耗时)。

具体能收集多少个 SQL,由参数 profiling_history_size 决定,默认值

为 15,最⼤值为 100。如果设置为 0,等同于关闭 Profiling。

总结

SQL优化是一个持续的过程,需要结合具体的业务场景和数据库类型进行。通过遵循上述优化策略和最佳实践,并结合执行计划分析,可以显著提升SQL查询性能和数据库整体效率。

参考资料:

MySQL 8.2 Optimizing SQL Statements:

https链接://dev.mysql.com/doc/refman/8.0/en/statementoptimization.html

为什么阿⾥巴巴禁⽌数据库中做多表 join - Hollis:

https://mp.weixin.qq.com/s/GSGVFkDLz1hZ1OjGndUjZg

MySQL 的 COUNT 语句,竟然都能被⾯试官虐的这么惨 - Hollis:

https://mp.weixin.qq.com/s/IOHvtel2KLNi-Ol4UBivbQ

MySQL 性能优化神器 Explain 使⽤分析:

https://segmentfault.com/a/1190000008131735

如何使⽤ MySQL 慢查询⽇志进⾏性能优化 :

https://kalacloud.com/blog/how-to-use-mysql-slow-query-logprofiling-mysqldumpslow/

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值