SQL优化是每个开发人员在日常工作中都要面对的任务。一个性能优良的SQL语句可以非常有效的提高数据库的响应速度,减少系统资源消耗。本篇文章将分享一些SQL优化的小技巧,帮助你轻松应对各种SQL性能问题。
1.避免不必要的列
查询时尽量避免查询冗余字段,不要使用select * ,只查询要用到的列,减少数据传输量
- 减少I/O开销:数据库在查询时,每读取一个数据块(通常包含多个行),都需要从磁盘上读取数据。当你使用
SELECT *
时,数据库会读取整行的数据,即使你的应用只需要其中的一部分字段。这增加了不必要的I/O开销。 - 减少网络传输:在分布式系统中,数据库和应用服务器可能位于不同的物理位置。当你查询大量数据时,减少返回的数据量可以显著减少网络传输的开销。
- 明确性:只选择需要的列可以使查询的意图更加明确,有利于其他开发人员理解查询的目的。
- 兼容性:如果数据库结构在未来发生变化(例如,某个字段被删除或重命名),使用
SELECT *
的查询可能会失败,而明确指定字段的查询则不会。
2.分页优化
当数据量很大的时候,limit和offset可能会导致性能问题,因为数据库查询时需要扫描offset+limit数量的行,可以考虑延迟关联(Late Row Lookups)和(Seek MEthod)这两种方法优化分页查询
延迟关联(Late Row Lookups):
- 原理:首先,通过索引快速定位到需要的数据行的位置,然后再去获取这些行的完整数据。这可以避免使用
LIMIT
和OFFSET
时导致的全表扫描或大范围扫描。 - 实现:通常,你可以通过一个子查询来定位到需要的数据行的主键,然后再根据这些主键去获取完整的数据。
Seek Method(或Keyset Pagination):
- 原理:与传统的基于偏移量的分页不同,Seek Method 是基于上一页最后一行的某个字段(通常是主键或具有唯一性的字段)的值来进行分页的。这种方法可以避免在数据量很大时
OFFSET
导致的性能问题。 - 实现:在你的应用中,保存上一页最后一行的某个字段的值。在下一页的查询中,使用这个值作为起始点来获取数据。例如,如果你使用主键作为起始点,那么查询可能类似于
WHERE id > :last_id LIMIT :page_size
。 - 优点:随着翻页的进行,查询的效率不会降低,因为数据库总是从上次查询的结束点开始查找。
- 缺点:不能随意地跳转到任意一页,只能一页一页地往后翻。同时,如果删除或修改了数据,可能会导致“跳过”某些行或重复显示某些行的问题。为了解决这个问题,你可能需要定期重新计算或更新你的起始点值。
3.索引优化
正确使用索引可以非常有效的减少sql查询时间,通常我们可以从索引覆盖,尽量避免用!=或者<>操作符、适当地去使用前缀索引、避免使用列上函数运算、正确使用联合索引等方面进行优化。
(1).使用索引覆盖
在使用非主键索引查询数据的时候如果查询的列有未在索引上的列,则需要回表查询,但是如果索引覆盖了,那么就不需要回表查询了
(2).避免使用!=或者<>操作符,尽量修改成使用=、>、<、BETWEEN等操作符
如果使用!=或者<>操作符会导致索引失效,从而引发全表扫描,比如说可以把class<>2,修改成class>2 or class< 2,就可以使用索引了
(3).适当使用前缀索引
(4).避免在列上使用函数
在where子句中直接对列使用函数会导致索引失效,比如说date_format,因为数据库需要对每行的列应用函数后再进行比较,无法直接利用索引
(5).正确使用联合索引
正确的使用联合索引可以大大提高查询效率,联合索引的创建应该符合最左前缀原则,就是索引的顺序应该根据列在查询中的使用频率和重要性来安排
(6)一张表中索引不要建立太多,一般5个以内
- 索引不是越多越好,虽然提高查询效率,但是会降低插入和更新的效率,插入和更新的时候有可能会导致重建索引,如果数据量巨大,重建将会对记录进行重新排序
- 创建索引时,数据库会为每个索引分配磁盘空间。索引过多会占用大量的磁盘空间,可能导致磁盘空间不足,影响数据库的正常运行。
- 虽然索引能够提高查询效率,但索引过多也可能导致查询效率下降。因为查询时需要扫描所有的索引,而索引过多会导致扫描的时间变长,从而降低查询效率。
4.不要使用join关联太多表,最多不要超过3张
<<阿里巴巴Java开发手册>>上提出join关联最多不要超过3张表,因为join太多表会进行笛卡尔积生成大量数据降低查询速度,可以考虑将复杂的JOIN查询分解为多个简单查询,然后再内存中去组合查询结果。
5.对排序进行优化
MySQL生成有序结果的方式有两种,一种是对结果集进行排序操作,另外一种是根据索引顺序得出的自然有序结果,所以在设计索引的时候需要充分考虑到排序的需求。
6.对UNION进行优化
UNION在SQL中用于合并两个或多个SELECT语句的结果集,但合并的结果集不包含重复行。UNION ALL则包括所有行,包括重复行。在使用UNION时,需要注意以下几点以进行优化:
-
减少结果集的大小:由于UNION会去除重复行,因此在合并结果集之前,每个SELECT语句都应该尽可能减少返回的行数。这可以通过在WHERE子句中增加限制条件或使用LIMIT子句来实现。
-
确保索引有效:每个SELECT语句都应该尽可能使用索引来加快查询速度。当使用UNION时,每个SELECT语句都会单独执行,并且每个查询都可以使用其自己的索引。
-
排序和LIMIT:如果需要对UNION的结果进行排序,最好是在最后一个SELECT语句中进行,而不是在UNION操作后对整个结果集进行排序。同样,如果需要限制返回的行数,也应该在最后一个SELECT语句中使用LIMIT子句。
-
使用UNION ALL代替UNION:如果确定合并的结果集中不会有重复行,或者不介意结果集中的重复行,那么应该使用UNION ALL代替UNION。因为UNION ALL不需要去除重复行,所以它的执行速度通常比UNION快。
-
避免在UNION中使用ORDER BY:在UNION的每个子查询中使用ORDER BY通常是没有意义的,因为UNION会打乱这些顺序。如果需要对最终的结果集进行排序,应该在UNION操作后对整个结果集使用ORDER BY。
-
优化子查询:如果UNION中的每个SELECT语句都是一个复杂的子查询,那么应该考虑优化这些子查询以提高性能。这包括使用索引、减少数据转换、避免在WHERE子句中使用OR等。
-
考虑使用临时表:如果UNION操作涉及大量数据或复杂的逻辑,并且需要频繁执行,那么可以考虑将结果集存储在一个临时表中,然后对该临时表进行查询。这可以避免重复执行UNION操作,从而提高性能。
7.Where子句优化
谨慎在where子句使用or来连接条件
当WHERE
子句中使用OR
操作符时,可能会导致索引失效,当WHERE
子句中的OR
条件导致索引使用不高效时,可以考虑将查询拆分为多个部分,每个部分使用单独的索引,并使用UNION
或UNION ALL
来合并结果,如下所示:
SELECT * FROM employees WHERE salary > 50000 OR department = 'IT';
这个查询可能会因为OR
而导致索引使用不高效,如果employees
表有一个复合索引(例如,在salary
和department
上),并且查询优化器能够高效地使用它,那么原始的OR
查询可能仍然是一个好的选择。作为替代方案,我们可以使用UNION
(去除重复行)或UNION ALL
(包括所有行,包括重复行)来重写查询:
(SELECT * FROM employees WHERE salary > 50000)
UNION
(SELECT * FROM employees WHERE department = 'IT');
以下是为什么OR
可能导致索引失效的几个原因:
索引选择复杂性:
当WHERE
子句中包含OR
时,数据库需要评估多个条件,并确定如何最有效地检索数据。如果每个OR
条件都对应于一个单独的索引,那么数据库可能需要决定是使用哪个索引,还是根本不使用索引而进行全表扫描。由于这种选择的复杂性,数据库可能会选择不使用任何索引,从而导致索引失效。
索引不匹配:
如果OR
条件中的某个字段没有索引,或者该索引不适用于当前查询,那么即使其他条件有合适的索引,整个WHERE
子句也可能不会使用索引。这是因为数据库需要同时考虑所有条件,而不仅仅是那些有索引的条件。(比如说上述例子中如果department字段没有索引,那么尽管salary走了索引,而到department查询条件时,还是会进行全表扫描。也就是说整个过程需要三步走,索引扫描+全表扫描+合并)
8.尽量避免进行类型转换
错误的写法(可能导致索引失效):
SELECT * FROM students WHERE name = 123; -- 这里123是数字,而不是字符串
在这个例子中,MySQL会尝试将name
列的值转换为数字类型来与123进行比较,这通常会导致全表扫描,因为索引(如果存在)是基于字符串类型构建的。
正确的写法(利用索引):
SELECT * FROM students WHERE name = 'John'; -- 这里'John'是字符串
在这个例子中,因为两边都是字符串类型,所以MySQL可以直接使用基于name
列的索引(如果存在的话)来快速定位到相应的行。
9.提高group by 的效率
先对数据进行过滤再进行分组,把不必要的数据进行过滤