当前位置:   article > 正文

Sql优化篇-干货总结大全_如何优化sql

如何优化sql

 前言

        我们经常会听到Sql优化的一个概念,但其实sql优化不一定就是说sql语句写的有问题,它可能是因为cpu资源爆满,或者内存空间不足,它也会导致sql执行卡顿;或者说表设计层面,过滤条件没有加索引之类的  等等~~  接下来从各个层面分析一下:

一、硬件层面:

        在硬件层面,CPU和内存的性能对SQL执行速度有直接影响。如果CPU或内存资源不足,可能导致SQL执行卡顿或延迟。在这种情况下,可以考虑升级硬件设备,增加CPU核数或扩展内存容量,以提高系统性能。

优化方案:

  • 升级CPU:选择高性能的CPU,特别是多核处理器,以提高并发处理能力。较高的CPU频率和更多的核心数可以加快SQL查询和计算速度。
  • 增加内存容量:增加内存可以减少频繁的磁盘访问,提高缓存命中率,从而加快数据读取和写入速度。
  • 选择高速存储设备:使用固态硬盘(SSD)替代传统机械硬盘可以大幅提升IO性能,加快数据的读写速度。
  • 提高网络带宽:优化数据库服务器与客户端之间的网络连接,确保网络带宽充足,减少网络延迟对数据库操作的影响。
  • 调整操作系统参数:针对数据库工作特性,调整操作系统的参数如文件描述符、最大进程数等,以提高数据库性能。

二、表设计层面:

        合理的表设计对SQL性能至关重要。首先,确保表结构符合规范化原则,避免冗余数据和重复字段。其次,选择合适的数据类型和字段长度,避免浪费存储空间。另外,根据业务需求和查询频率,添加适当的索引可以加快查询速度。

优化方案:

  • 尽量遵守数据库三大范式:遵守数据库三大范式,避免数据冗余和重复字段。但是对于经常查询的字段可以冗余一份,比如name,减少联表查询;

  • 选择合适的数据类型和字段长度: 使用较小的数据类型可以减少存储空间的占用,并提高查询效率。例如,如果某个字段只需要存储整数值,可以选择INT类型而不是BIGINT类型。

  • 添加适当的索引:通过索引,数据库可以更快地定位到需要查询的数据。但是,过多或不必要的索引会增加写入操作的开销,因此需要权衡索引的数量和查询的性能。

  • 设计主从结构:对于大量读写请求的时候,我们可以使用主从复制,读写分离的思想。主节点用来处理增删改,从节点用来处理查询,这样可以有效地分担数据库的读写压力,提高整体的性能和可用性。主从结构的工作原理:主节点将写操作记录到二进制日志(binlog)中,然后从节点通过读取主节点的binlog,并将这些写操作在从节点上执行,从而保持主从节点的数据一致性。在MySQL中,从节点会通过Relay Log来保存主节点的binlog,以便实现数据同步。

三、Java代码层面:

        编写高效的Java代码也能提升SQL性能。避免频繁的数据库连接和关闭操作,可以使用数据库连接池来管理连接。使用预编译语句(Prepared Statement)替代动态拼接SQL语句,可以提高效率并防止SQL注入攻击。此外,合理利用缓存机制,减少对数据库的访问也是一种优化策略。

优化方案:

1、使用数据库连接池:避免频繁地创建和关闭数据库连接,这会增加系统开销。使用数据库连接池可以管理连接,提高连接的复用性和性能;

2、使用预编译语句:预编译语句(Prepared Statement)可以将SQL语句预编译成一个模板,并将参数插入到模板中,避免了每次执行SQL都要重新解析和编译SQL语句的开销。此外,预编译语句也能够有效防止SQL注入攻击;

3、合理利用缓存机制:使用缓存机制可以减少对数据库的频繁访问,提高系统的响应速度和性能。可以使用内存缓存或者分布式缓存来存储经常访问的数据,减轻数据库的压力。如redis、Memcached 或者本地缓存Caffeine之类的;

4、批量操作:避免在for循环中多次查询sql语句,尽量在一个sql语句完成,可以借助mybatis的forEach之类的;因为每次执行sql语句java层面都会与数据库进行通信,在一个sql语句完成批量操作,只需要进行一次通信,提高效率。

假如在for循环重复调用sql,就会一直重复以下操作:

1、连接器

        验证用户名、密码、权限

2、缓存

        因为是更新,将会清空缓存

3、解析器

        验证关键字的合法性

4、执行器

        1)prepare阶段(预处理器)

                验证表名和字段是否存在、将*扩展成表中所有字段;

        2)optimize阶段 (优化器)

                为查询语句制定一个执行计划,选择成本最小的执行计划;

        3)execute阶段(执行器)

                首先检查Buffer Pool中是否已经缓存了所需的数据页,如果没有,则会从存储

        引擎中读取数据到Buffer Pool中进行处理,然后再进行两阶段提交,先写undolog,再            把bufferPool数据进行修改,然后redolog状态修改为prepare,再写binlog,写完以后把

        redolog状态修改为commit,并在合适的时机将redolog日志重做至磁盘中。

execute阶段步骤如图:

5、优化查询语句:选择合适的字段、合理使用索引、避免全表扫描等,可以提高查询效率。

  • 按需查询:在查询时只查需要的字段,尽量避免使用select *,并使用limit获取需要的数据量;

  • 合理使用索引:当条件中有索引时,尽量避免索引失效的场景,具体场景就不举例了;

  • 优化连接查询:当需要进行连接查询时,尽量选择性能更好的连接方式,如使用 INNER JOIN 替代 OUTER JOIN,避免笛卡尔积等情况的发生。同时确保连接字段上有适当的索引。

6、异步处理:对于一些耗时的数据库操作,可以使用异步处理的方式,将数据库操作放到后台线程中执行,提高系统的并发能力和性能。如CompletableFuture配合线程池使用:

  1. ExecutorService sharedThreadPool = Executors.newCachedThreadPool();
  2. CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
  3. //执行查询语句
  4. return result;
  5. }, sharedThreadPool);

四、存储层面:

选择合适的存储引擎(如InnoDB、MyISAM等),根据实际需求调整相关参数,如缓冲池大小、日志设置等。对于大量重复的静态数据,可以考虑使用缓存技术,减轻数据库的负担。

1、选择合适的存储引擎:InnoDB适合于高并发和事务处理,而MyISAM适合于读密集型操作。根据应用的实际需求选择合适的存储引擎可以提升数据库的性能和稳定性。

补充一下它们的区别:

1、事务,innodb支持事务,myisam不支持事务。

2、多版本并发控制,innodb有MVCC,可以保证读写并发下的一致性。

3、日志,innodb有undolog、redolog日志,支持回滚和事务崩溃恢复。

4、锁,innodb支持行级锁,表级锁,记录锁,间隙锁,临键锁,而myisam只支持表级锁。

5、myisam具有高性能读取的表现,适合数据库做大量的情况下

6、innodb支持聚簇索引,有两个文件,.ibd和.frm,.ibd储存数据和索引。

myisam不支持聚簇索引,有三个文件,分别是.myd .myi .frm,

其中.myd存储数据,.myi存储索引,.frm存储了表的结构定义

2、调整相关参数:数据库系统提供了许多参数用于调整其性能和行为。例如,可以调整缓冲池大小、日志设置、连接数等参数来优化数据库的性能。根据数据库的负载情况和硬件资源,适时地调整这些参数可以提升数据库的吞吐量和响应速度。具体如下:

  1. 通过innodb_buffer_pool_size 来调整缓冲池大小:建议根据系统的内存资源,将缓冲池大小设置为物理内存的 50%-70%。过小的缓冲池会导致频繁的磁盘读写操作,影响性能;而过大的缓冲池可能会占用过多的内存资源,影响系统的稳定性。

  2. 日志设置:数据库的日志设置涉及到事务日志、错误日志、慢查询日志等。对于事务日志,可以调整参数 innodb_log_file_size 和 innodb_log_buffer_size 来设置事务日志文件的大小和缓冲区的大小。对于错误日志和慢查询日志,可以调整参数 log_error 和 slow_query_log 来设置日志文件的位置和是否开启慢查询日志等。当然,具体的还是根据实际业务需求和系统负载情况,适时地调整这些日志设置。

  3. 通过参数 max_connections 来调整数据库的最大连接数:根据系统的并发请求量和硬件资源,合理地设置最大连接数。过小的连接数可能会导致请求被阻塞,影响系统的并发能力;而过大的连接数可能会占用过多的系统资源,导致资源浪费。

  4. 禁用查询缓存(MySQL8.0已废弃):查询缓存可以缓存查询的结果,提高查询的性能。但是在高并发的场景下,查询缓存可能会成为性能瓶颈,因此在一些情况下可能需要禁用查询缓存。可以通过参数 query_cache_type 和 query_cache_size 来控制查询缓存的开启和大小。建议根据实际情况评估是否开启查询缓存,以及设置合适的缓存大小。

  5. 其他参数:还有许多其它的数据库参数可以调整,如排序缓冲区大小、临时表大小、并发控制参数等。根据实际情况,逐步调整这些参数以优化数据库的性能和稳定性。

五、索引层面:

        合理的索引设计对SQL查询性能至关重要。根据实际查询需求和频率,选择合适的字段作为索引,并避免创建过多的索引。注意,索引的更新操作会对写入性能产生一定的影响,因此需要权衡读写操作的平衡。

  1. 选择合适的字段作为索引:根据实际查询需求和频率,选择合适的字段作为索引。通常情况下,经常用于查询条件、连接条件或排序的字段是较好的索引候选者。例如,对于经常出现在 WHERE 子句中的字段或者经常用于连接表的字段,可以考虑创建索引。

  2. 避免创建过多的索引:虽然索引可以提高查询性能,但是过多的索引也会增加数据库的维护成本,并且可能会影响到写入操作的性能。因此,需要权衡索引的数量和对性能的影响。避免创建不必要的索引,只保留那些真正有助于查询性能提升的索引。

  3. 使用联合索引:联合索引包含多个字段的索引,可以用于支持多个字段的查询。通过合理设计联合索引,可以减少索引的数量,同时提高查询的效率。需要根据查询条件和数据访问模式来设计合适的联合索引,避免创建过于复杂或冗余的索引。

另外,MySQL对联合索引做了很多优化,如索引下推、索引覆盖、索引跳跃等,大大加快了查询的一个效率,具体请参考:Mysql【索引覆盖、索引下推、索引合并、索引跳跃】介绍-CSDN博客

六、慢SQL优化:

        分析和优化慢查询是提高SQL性能的重要任务。可以通过数据库监控工具或慢查询日志来定位慢查询的具体原因。一般可以通过调整查询语句、优化索引、重新设计表结构等方式来提升慢查询的性能。

我们可以打开慢日志进行分析,具体步骤如下:

1、启用慢查询日志: 打开MySQL配置文件(my.cnf或my.ini),找到[mysqld]部分,在其中添加或修改以下行:

slow_query_log = 1

slow_query_log_file = /path/to/mysqldslow.log

long_query_time = 2

        改完配置后重启MySQL服务,这将会启用慢查询日志,将慢查询记录到指定的日志文件中。long_query_time表示执行时间超过多少秒的查询会被记录,默认为10秒。

2、执行慢查询: 把感觉较慢的查询执行一遍。这些查询将被记录到慢查询日志文件中。

3、分析慢查询日志: 通过mysqlddumpslow命令将慢SQL导出,以下是常用的四种方式:

  1. mysqldumpslow ­s c ­t 10 /var/run/mysqld/mysqld­slow.log # 取出使用最多的10条慢查询
  2. mysqldumpslow ­s t ­t 3 /var/run/mysqld/mysqld­slow.log # 取出查询时间最慢的3条慢查询
  3. mysqldumpslow ­s t ­t 10 ­g “left join” /database/mysql/slow­log #得到按照时间排序的前10条里面含有左连接的查询语句
  4. mysqldumpslow ­s r ­t 10 ­g 'left join' /var/run/mysqld/mysqldslow.log # 按照扫描行数最多的

4、解释执行计划: 在慢查询日志中,我们可以看到执行时间,针对某个查询时间过长的语句,可以使用EXPLAIN关键字来获取该查询的执行计划,我们配置文件中修改的是2S以上为慢日志。

EXPLAIN SELECT * FROM your_table WHERE ...;

执行计划会出现以下字段:

  我们通常会关注type、key、Extra三个字段去具体分析。一般至少需要将type优化到range级别。最后优化完毕以后,请注意可以关闭慢日志,因为后期它可能会越来越大,占用我们的内存空间,并且每次写入慢日志中也要耗费一些性能。

当然只使用sql语句也是可以处理慢sql的:

  1. -- 修改慢查询时间,只能当前会话有效;
  2. set long_query_time=1; ­
  3. -- 启用慢查询 ,加上global,不然会报错的;
  4. set global slow_query_log='ON';­­
  5. -- 是否开启慢查询;
  6. show variables like "%slow%";­­
  7. -- 查询慢查询SQL状况;
  8. show status like "%slow%"; ­­
  9. -- 慢查询时间(默认情况下MySQL认位10秒以上才是慢查询)
  10. show variables like "long_query_time"; ­­
  11. -- 最后用EXPLAIN分析一波
  12. EXPLAIN SELECT * FROM your_table WHERE ...;

具体的sql语句各种优化策略,可以参考:Sql优化总结!详细!-CSDN博客

七、分表分库:

        当单表数据量过大时,可以考虑采用分表或分库的方式来分散数据负载,提高查询和写入性能。分表可以根据某个字段进行水平切分,而分库可以将不同表分布在不同的数据库中。此外,还可以使用分区表来进一步优化查询性能。

1、分表分库的作用?

  • 分库主要解决的是并发量大的问题。因为并发量一旦上来了,那么数据库就可能会成为瓶颈,因为数据库的连接数是有限的,虽然可以调整,但是也不是无限调整的。所以,当当你的数据库的读或者写的OPS过高,导致你的数据库连接数不足了的时候,就需要考虑分库了,通过增加数据库实例的方式来提供更多的可用数据库链接,从而提升系统的并发度。
  • 分表主要解决的是数据量大的问题。假如你的单表数据量非常大,因为并发不高,数据量连接可能还够,但是存储和查询的性能遇到了瓶颈了,你做了很多优化之后还是无法提升效率的时候,就需要考虑做分表了。
  • 那么,当你的数据库链接也不够了,并且单表数据量也很大导致査询比较慢的时候,就需要做既分库又分表了。

2、什么时候分表分库?

  • 一般我们认为,单表行数超过 500 万行或者单表容量超过 2GB之后,才需要考虑做分库分表了,小于这个数据量,遇到性能问题先建议大家通过其他优化来解决。
  • 那如果,既需要解决并发量大的问题,又需要解决数据量大的问题时候。通常情况下,高并发和数据量大的问题都是同时发生的,所以,我们会经常遇到分库分表需要同时进行的情况。
  • 所以,当你的数据库链接也不够了,并且单表数据量也很大导致查询比较慢的时候,就需要做既分库又分表了。
  • 阿里巴巴规范表示的是500万数据分表分库,是因为它们的并发量比较高,但是实际对于并发量不高的业务,2000万再分表分库也是可以的,因为b+树三层大概能容纳2500万条数据,大于三层效率就会大打折扣了。

3、应该优先考虑分区再考虑分表,那分区的原则?

  • 按照系统负载,将数据分到不同的区域中,
  • 按照应用程序查询模式,将数据库分为不同的分区,
  • 按照月份或者年份分区;
  • 通过实践哈希法可以将记录放置到不同的分区中,
  • 基于范围查询,使用分段来将记录放置到不同的分区中,以便提高查询效率。

什么是分区?

        在Innodb中,表存储主要依赖两个文件,分别是,frm文件和.ibd文件。.frm文件用于存储表结构定义信息,而.ibd文件则用于存储表数据。

        分区之后,表面看其实还是一张表,但是它数据存储的位置就分开来了。表的分区意味着将表的数据存储在不同的.ibd文件中,而不是像普通的表一样将所有数据存储在一个.ibd和一个.frm文件中。每个分区对应一个.ibd和一个.frm文件,这些文件可以分布在不同的磁盘上,从而实现数据的分散存储,提高了I/O并发性能。

4、分表字段如何选择?

  • 在分库分表的过程中,我们需要有一个字段用来进行分表,比如按照用户分表、按照时间分表、按照地区分表。这里面的用户、时间、地区就是所谓的分表字段。

  • 那么,在选择这个分表字段的时候,一定要注意,要根据实际的业务情况来做慎重的选择。

  • 比如说我们要对交易订单进行分表的时候,我们可以选择的信息有很多,比如买家Id、卖家Id、订单号、时间、地区等等,具体应该如何选择呢?

  • 通常,如果有特殊的诉求,比如按照月度汇总、地区汇总等以外,我们通常建议大家按照买家Id进行分表。因为这样可以避免一个关键的问题那就是--数据倾斜(热点数据)。因为卖家的数据非常集中,比如某个商家一天卖了1000万单,那分的这张表还是很大,但是买家基本不可能有这种现象。

5、横向拆分和纵向拆分

  • 谈及到分库分表,那就要涉及到该如何做拆分的问题,通常在做拆分的时候有两种分法,分别是横向拆分(水平拆分)和纵向拆分(垂直拆分)。
  • 假如我们有一张表,如果把这张表中某一条记录的多个字段,拆分到多张表中,这种就是纵向拆分。
  • 那如果把一张表中的不同的记录分别放到不同的表中,这种就是横向拆分。
  • 横向拆分的结果是数据库表中的数据会分散到多张分表中,使得每一个单表中的数据的条数都有所下降。
  • 纵向拆分的结果是数据库表中的数据的字段数会变少,使得每一个单表中的数据的存储有所下降。

6、分库分表后会带来哪些问题?

  • 首先,做了分库分表之后,所有的读和写操作,都需要带着分表字段,这样才能知道具体去哪个库、哪张表中去查询数据。如果不带的话,就得支持全表扫描。

  • 但是,单表的时候全表扫描比较容易,但是做了分库分表之后,就没办法做扫表的操作了,如果要扫表的话就要把所有的物理表都要扫一遍。

  • 还有一旦我们要从多个数据库中查询或者写入数据,就有很多事情都不能做了,比如跨库事务就是不支持的。

  • 所以,分库分表之后就会带来因为不支持事务而导致的数据一致性的问题。其次,做了分库分表之后,以前单表中很方便的分页查询、排序等等操作就都失效了。因为我们不能跨多表进行分页、排序。

  • 除此之外,还有一些其他的问题,比如二次分表的问题,一致性ID的问题等等。

7、分库分表的工具

        在选定了分表字段和分表算法之后,那么,如何把这些功能给实现出来,需要怎么做呢?我们如何可以做到像处理单表一样处理分库分表的数据呢? 这就需要用到一个分库分表的工具了。目前市面上比较不错的分库分表的开源框架主要有三个,分别是sharding-jdbc、TDDL和MyCat。

MycatSharding-JDBC

  • 现在叫ShardingSphere(Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar这3款相互独立的产品组成)。它定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

  • 开源地址: https://shardingsphere.apache.org

TDDL

  • TDDL 是淘宝开源的一个用于访问数据库的中间件,它集成了分库分表读写分离,权重调配,动态数据源配置等功能。封装 idbc 的 DataSource给用户提供统一的基于客户端的使用。

  • 开源地址: https://github.com/alibaba/tb_tddl

Mycat

  • Mycat是一款分布式关系型数据库中间件。它支持分布式SQL查询,兼容MySQL通信协议,以Java生态支持多种后端数据库,通过数据分片提高数据查询处理能力。

  • 开源地址: https://github.com/MyCATApache/Mycat2

ps:以下是我整理的java面试资料,感兴趣的可以看看。最后,创作不易,觉得写得不错的可以点点关注!

链接:https://www.yuque.com/u39298356/uu4hxh?# 《Java知识宝典》 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/674117
推荐阅读
相关标签
  

闽ICP备14008679号