SQL优化

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)

  • 原理 :首先,通过索引快速定位到需要的数据行的位置,然后再去获取这些行的完整数据。这可以避免使用LIMITOFFSET时导致的全表扫描或大范围扫描。
  • 实现:通常,你可以通过一个子查询来定位到需要的数据行的主键,然后再根据这些主键去获取完整的数据。

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时,需要注意以下几点以进行优化:

  1. 减少结果集的大小:由于UNION会去除重复行,因此在合并结果集之前,每个SELECT语句都应该尽可能减少返回的行数。这可以通过在WHERE子句中增加限制条件或使用LIMIT子句来实现。

  2. 确保索引有效:每个SELECT语句都应该尽可能使用索引来加快查询速度。当使用UNION时,每个SELECT语句都会单独执行,并且每个查询都可以使用其自己的索引。

  3. 排序和LIMIT:如果需要对UNION的结果进行排序,最好是在最后一个SELECT语句中进行,而不是在UNION操作后对整个结果集进行排序。同样,如果需要限制返回的行数,也应该在最后一个SELECT语句中使用LIMIT子句。

  4. 使用UNION ALL代替UNION:如果确定合并的结果集中不会有重复行,或者不介意结果集中的重复行,那么应该使用UNION ALL代替UNION。因为UNION ALL不需要去除重复行,所以它的执行速度通常比UNION快。

  5. 避免在UNION中使用ORDER BY:在UNION的每个子查询中使用ORDER BY通常是没有意义的,因为UNION会打乱这些顺序。如果需要对最终的结果集进行排序,应该在UNION操作后对整个结果集使用ORDER BY。

  6. 优化子查询:如果UNION中的每个SELECT语句都是一个复杂的子查询,那么应该考虑优化这些子查询以提高性能。这包括使用索引、减少数据转换、避免在WHERE子句中使用OR等。

  7. 考虑使用临时表:如果UNION操作涉及大量数据或复杂的逻辑,并且需要频繁执行,那么可以考虑将结果集存储在一个临时表中,然后对该临时表进行查询。这可以避免重复执行UNION操作,从而提高性能。

7.Where子句优化

谨慎在where子句使用or来连接条件

WHERE子句中使用OR操作符时,可能会导致索引失效,当WHERE子句中的OR条件导致索引使用不高效时,**可以考虑将查询拆分为多个部分,每个部分使用单独的索引,并使用UNIONUNION ALL来合并结果,**如下所示:

复制代码
SELECT * FROM employees WHERE salary > 50000 OR department = 'IT';

这个查询可能会因为OR而导致索引使用不高效,如果employees表有一个复合索引(例如,在salarydepartment上),并且查询优化器能够高效地使用它,那么原始的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 的效率

先对数据进行过滤再进行分组,把不必要的数据进行过滤

相关推荐
IvorySQL5 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
·云扬·5 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
野生技术架构师5 小时前
SQL语句性能优化分析及解决方案
android·sql·性能优化
IT邦德5 小时前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
惊讶的猫5 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
不爱缺氧i6 小时前
完全卸载MariaDB
数据库·mariadb
纤纡.6 小时前
Linux中SQL 从基础到进阶:五大分类详解与表结构操作(ALTER/DROP)全攻略
linux·数据库·sql
jiunian_cn6 小时前
【Redis】渐进式遍历
数据库·redis·缓存
橙露6 小时前
Spring Boot 核心原理:自动配置机制与自定义 Starter 开发
java·数据库·spring boot