赞
踩
我们经常会听到Sql优化的一个概念,但其实sql优化不一定就是说sql语句写的有问题,它可能是因为cpu资源爆满,或者内存空间不足,它也会导致sql执行卡顿;或者说表设计层面,过滤条件没有加索引之类的 等等~~ 接下来从各个层面分析一下:
在硬件层面,CPU和内存的性能对SQL执行速度有直接影响。如果CPU或内存资源不足,可能导致SQL执行卡顿或延迟。在这种情况下,可以考虑升级硬件设备,增加CPU核数或扩展内存容量,以提高系统性能。
优化方案:
合理的表设计对SQL性能至关重要。首先,确保表结构符合规范化原则,避免冗余数据和重复字段。其次,选择合适的数据类型和字段长度,避免浪费存储空间。另外,根据业务需求和查询频率,添加适当的索引可以加快查询速度。
优化方案:
尽量遵守数据库三大范式:遵守数据库三大范式,避免数据冗余和重复字段。但是对于经常查询的字段可以冗余一份,比如name,减少联表查询;
选择合适的数据类型和字段长度: 使用较小的数据类型可以减少存储空间的占用,并提高查询效率。例如,如果某个字段只需要存储整数值,可以选择INT类型而不是BIGINT类型。
添加适当的索引:通过索引,数据库可以更快地定位到需要查询的数据。但是,过多或不必要的索引会增加写入操作的开销,因此需要权衡索引的数量和查询的性能。
设计主从结构:对于大量读写请求的时候,我们可以使用主从复制,读写分离的思想。主节点用来处理增删改,从节点用来处理查询,这样可以有效地分担数据库的读写压力,提高整体的性能和可用性。主从结构的工作原理:主节点将写操作记录到二进制日志(binlog)中,然后从节点通过读取主节点的binlog,并将这些写操作在从节点上执行,从而保持主从节点的数据一致性。在MySQL中,从节点会通过Relay Log来保存主节点的binlog,以便实现数据同步。
编写高效的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配合线程池使用:
- ExecutorService sharedThreadPool = Executors.newCachedThreadPool();
- CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
- //执行查询语句
- return result;
- }, 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、调整相关参数:数据库系统提供了许多参数用于调整其性能和行为。例如,可以调整缓冲池大小、日志设置、连接数等参数来优化数据库的性能。根据数据库的负载情况和硬件资源,适时地调整这些参数可以提升数据库的吞吐量和响应速度。具体如下:
通过innodb_buffer_pool_size
来调整缓冲池大小:建议根据系统的内存资源,将缓冲池大小设置为物理内存的 50%-70%。过小的缓冲池会导致频繁的磁盘读写操作,影响性能;而过大的缓冲池可能会占用过多的内存资源,影响系统的稳定性。日志设置:数据库的日志设置涉及到事务日志、错误日志、慢查询日志等。对于事务日志,可以调整参数
innodb_log_file_size
和innodb_log_buffer_size
来设置事务日志文件的大小和缓冲区的大小。对于错误日志和慢查询日志,可以调整参数log_error
和slow_query_log
来设置日志文件的位置和是否开启慢查询日志等。当然,具体的还是根据实际业务需求和系统负载情况,适时地调整这些日志设置。通过参数
max_connections
来调整数据库的最大连接数:根据系统的并发请求量和硬件资源,合理地设置最大连接数。过小的连接数可能会导致请求被阻塞,影响系统的并发能力;而过大的连接数可能会占用过多的系统资源,导致资源浪费。禁用查询缓存(MySQL8.0已废弃):查询缓存可以缓存查询的结果,提高查询的性能。但是在高并发的场景下,查询缓存可能会成为性能瓶颈,因此在一些情况下可能需要禁用查询缓存。可以通过参数
query_cache_type
和query_cache_size
来控制查询缓存的开启和大小。建议根据实际情况评估是否开启查询缓存,以及设置合适的缓存大小。其他参数:还有许多其它的数据库参数可以调整,如排序缓冲区大小、临时表大小、并发控制参数等。根据实际情况,逐步调整这些参数以优化数据库的性能和稳定性。
合理的索引设计对SQL查询性能至关重要。根据实际查询需求和频率,选择合适的字段作为索引,并避免创建过多的索引。注意,索引的更新操作会对写入性能产生一定的影响,因此需要权衡读写操作的平衡。
选择合适的字段作为索引:根据实际查询需求和频率,选择合适的字段作为索引。通常情况下,经常用于查询条件、连接条件或排序的字段是较好的索引候选者。例如,对于经常出现在 WHERE 子句中的字段或者经常用于连接表的字段,可以考虑创建索引。
避免创建过多的索引:虽然索引可以提高查询性能,但是过多的索引也会增加数据库的维护成本,并且可能会影响到写入操作的性能。因此,需要权衡索引的数量和对性能的影响。避免创建不必要的索引,只保留那些真正有助于查询性能提升的索引。
使用联合索引:联合索引包含多个字段的索引,可以用于支持多个字段的查询。通过合理设计联合索引,可以减少索引的数量,同时提高查询的效率。需要根据查询条件和数据访问模式来设计合适的联合索引,避免创建过于复杂或冗余的索引。
另外,MySQL对联合索引做了很多优化,如索引下推、索引覆盖、索引跳跃等,大大加快了查询的一个效率,具体请参考:Mysql【索引覆盖、索引下推、索引合并、索引跳跃】介绍-CSDN博客
分析和优化慢查询是提高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导出,以下是常用的四种方式:
- mysqldumpslow s c t 10 /var/run/mysqld/mysqldslow.log # 取出使用最多的10条慢查询
- mysqldumpslow s t t 3 /var/run/mysqld/mysqldslow.log # 取出查询时间最慢的3条慢查询
- mysqldumpslow s t t 10 g “left join” /database/mysql/slowlog #得到按照时间排序的前10条里面含有左连接的查询语句
- 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的:
- -- 修改慢查询时间,只能当前会话有效;
- set long_query_time=1;
- -- 启用慢查询 ,加上global,不然会报错的;
- set global slow_query_log='ON';
- -- 是否开启慢查询;
- show variables like "%slow%";
- -- 查询慢查询SQL状况;
- show status like "%slow%";
- -- 慢查询时间(默认情况下MySQL认位10秒以上才是慢查询)
- show variables like "long_query_time";
- -- 最后用EXPLAIN分析一波
- EXPLAIN SELECT * FROM your_table WHERE ...;
-
具体的sql语句各种优化策略,可以参考:Sql优化总结!详细!-CSDN博客
当单表数据量过大时,可以考虑采用分表或分库的方式来分散数据负载,提高查询和写入性能。分表可以根据某个字段进行水平切分,而分库可以将不同表分布在不同的数据库中。此外,还可以使用分区表来进一步优化查询性能。
什么是分区?
在Innodb中,表存储主要依赖两个文件,分别是,frm文件和.ibd文件。.frm文件用于存储表结构定义信息,而.ibd文件则用于存储表数据。
分区之后,表面看其实还是一张表,但是它数据存储的位置就分开来了。表的分区意味着将表的数据存储在不同的.ibd文件中,而不是像普通的表一样将所有数据存储在一个.ibd和一个.frm文件中。每个分区对应一个.ibd和一个.frm文件,这些文件可以分布在不同的磁盘上,从而实现数据的分散存储,提高了I/O并发性能。
在分库分表的过程中,我们需要有一个字段用来进行分表,比如按照用户分表、按照时间分表、按照地区分表。这里面的用户、时间、地区就是所谓的分表字段。
那么,在选择这个分表字段的时候,一定要注意,要根据实际的业务情况来做慎重的选择。
比如说我们要对交易订单进行分表的时候,我们可以选择的信息有很多,比如买家Id、卖家Id、订单号、时间、地区等等,具体应该如何选择呢?
通常,如果有特殊的诉求,比如按照月度汇总、地区汇总等以外,我们通常建议大家按照买家Id进行分表。因为这样可以避免一个关键的问题那就是--数据倾斜(热点数据)。因为卖家的数据非常集中,比如某个商家一天卖了1000万单,那分的这张表还是很大,但是买家基本不可能有这种现象。
首先,做了分库分表之后,所有的读和写操作,都需要带着分表字段,这样才能知道具体去哪个库、哪张表中去查询数据。如果不带的话,就得支持全表扫描。
但是,单表的时候全表扫描比较容易,但是做了分库分表之后,就没办法做扫表的操作了,如果要扫表的话就要把所有的物理表都要扫一遍。
还有一旦我们要从多个数据库中查询或者写入数据,就有很多事情都不能做了,比如跨库事务就是不支持的。
所以,分库分表之后就会带来因为不支持事务而导致的数据一致性的问题。其次,做了分库分表之后,以前单表中很方便的分页查询、排序等等操作就都失效了。因为我们不能跨多表进行分页、排序。
除此之外,还有一些其他的问题,比如二次分表的问题,一致性ID的问题等等。
在选定了分表字段和分表算法之后,那么,如何把这些功能给实现出来,需要怎么做呢?我们如何可以做到像处理单表一样处理分库分表的数据呢? 这就需要用到一个分库分表的工具了。目前市面上比较不错的分库分表的开源框架主要有三个,分别是sharding-jdbc、TDDL和MyCat。
现在叫ShardingSphere(Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar这3款相互独立的产品组成)。它定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
TDDL 是淘宝开源的一个用于访问数据库的中间件,它集成了分库分表读写分离,权重调配,动态数据源配置等功能。封装 idbc 的 DataSource给用户提供统一的基于客户端的使用。
Mycat是一款分布式关系型数据库中间件。它支持分布式SQL查询,兼容MySQL通信协议,以Java生态支持多种后端数据库,通过数据分片提高数据查询处理能力。
ps:以下是我整理的java面试资料,感兴趣的可以看看。最后,创作不易,觉得写得不错的可以点点关注!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。