带你整理面试过程中关于 SQL优化的相关知识

Posted 南淮北安

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你整理面试过程中关于 SQL优化的相关知识相关的知识,希望对你有一定的参考价值。

文章目录

一、为什么要对SQL进行优化?

我们开发项目上线初期,由于业务数据量相对较少,一些SQL的执行效率对程序运行效率的影响不太明显,而开发和运维人员也无法判断SQL对程序的运行效率有多大,故很少针对SQL进行专门的优化,而随着时间的积累,业务数据量的增多,SQL的执行效率对程序的运行效率的影响逐渐增大,此时对SQL的优化就很有必要。

二、SQL 优化

a. 是否能使用【覆盖索引】,减少回表查询,即查询时尽量指明对应的列,避免 select *
b. 考虑是否组建【联合索引】,如果组建,尽量将区分度最高的放在最左边
c. 通过 explain 命令来查看SQL的执行计划,看看自己写的SQL是否走了索引,走了什么索引
d. 开启事务后,有意识的减少锁的持有时间,比如在事务内插入和修改数据,可以先插入后修改,因为修改是更新操作,会加行锁。如果先更新,并发下可能会导致多个事务的请求等待行锁释放

(1)避免 SELECT *,只查询需要的字段,也就是避免回表查询,尽量覆盖查询
(2)尽量使用连接代替子查询,因为使用 join 时,mysql 不会在内存中创建临时表。
(3)对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
(4)应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描

select id from t where num is null    
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:    
select id from t where num=0 

(5)in 和 not in 也要慎用,否则会导致全表扫描

select id from t where num in(1,2,3)    
对于连续的数值,能用 between 就不要用 in 了:    
select id from t where num between 1 and 3  

(6)应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。

select id from t where num/2=100    
应改为:    
select id from t where num=100*2    

(7)避免频繁创建和删除临时表,以减少系统表资源的消耗。
(8)索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率, 因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。 一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

等等。。。可参考【2】


【参考】
【1】https://blog.csdn.net/HXNLYW/article/details/82979088
【2】https://blog.csdn.net/qq_38789941/article/details/83744271

三、Mysql中高性能的索引策略

1. 独立的列

如果查询中的列不是独立的,则MySQL就不会使用索引。“独立的列”是指索引列不能是表达式的一部分,也不能是函数的参数

因此应该简化WHERE条件,始终将索引列单独放在比较符号的一侧

SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;(无法使用actor_id列的索引)
SELECT actor_id FROM sakila.actor WHERE actor_id = 4;(可以使用actor_id列的索引)

2. 前缀索引和索引选择性

有时候需要索引很长的字符列,这会使索引变得大且慢。通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。索引的选择性是指,不重复的索引值(也称为基数)和数据的记录总数(#T)的比值,范围从1/#T到1之间。

索引的选择性越高则查询效率越高。唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的

一般情况下某个列的前缀的选择性也是足够高的,足以满足查询性能。对于BLOB,TEXT或很长的VARCHAR类型的列,必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。

通常情况,我们应该尽量使前缀的“基数”接近于完整列的“基数”

前缀索引的缺点 : MySQL无法使用前缀索引做ORDER BY和GROUP BY,也无法使用前缀索引做覆盖扫描。

3. 多列索引

在多个列上建立独立的单列索引大部分情况下并不能提高MySQL的查询性能。此时应该考虑建立多列索引。

4. 选择合适的索引列顺序(针对 B-Tree 索引)

当不需要考虑排序和分组时,通常将选择性最高的列放到索引最前列。
有时可能需要根据那些运行频率最高的查询来调整索引列的顺序。

5. 覆盖索引

如果一个索引包含(或者说是覆盖)所有需要查询的字段的值,我们就称为“覆盖索引”,使用覆盖索引能够极大地提高性能。

6. 使用索引来做排序

MySQL可以使用同一个索引既满足排序,又用于查找行。因此,如果可能,涉及索引时应该尽可能地同时满足这两种任务。只有当索引的列顺序和ORDER BY子句的顺序完全一致,并且索引列的排序方向(倒序或正序)都一样时,MySQL才能使用索引来对结果排序。如果查询需要关联多张表,则只有当ORDER BY子句引用的字段全部为第一个表时,才能使用索引做排序。ORDER BY子句和查找型查询的限制是一样的:需要满足索引的最左前缀需求;否则,MySQL都需要执行排序操作,而无法利用索引排序。

7. 冗余和重复索引

重复索引是指在相同的列上按照相同的顺序创建相同类型的索引,应该避免这样创建的重复索引,发现后也应该立即移除。MySQL允许在相同列上创建多个索引。MySQL需要单独维护重复的索引,并且优化器在优化查询的时候也需要逐个地进行考虑,这会影响性能。

冗余索引和重复索引又一些不同。如果创建了索引(A,B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。大多数情况下,都不需要冗余索引,应该尽可能扩展已有的索引而不是创建新索引。但也有时候出于性能方面的考虑需要冗余索引,因为扩展已有的索引会导致其变得太大,从而影响其他使用该索引的查询的性能。

8. 未使用的索引

若一个索引不再被使用,则应该考虑删除。可以通过一些工具找到未使用的索引,如 Percona Toolkit中的 pt-index-usage

四、实际应用

选用的隔离级别是 读已提交,MySQL 默认使用的是可重复读

选用什么隔离级别,主要看应用场景,因为隔离级别越低,事务并发性能越高,一般互联网公司都会选用读已提交

因为像可重复读隔离级别,就有可能 间隙锁,导致的死锁问题

五、即便走索引,线上查询还是慢?

原因: 表的数据量太大

首先考虑,能否将旧的数据,删除掉一部分,数据量降低,检索速度自然就快了

只有极少的业务才允许可以删除

或者考虑,查询数据库前,加个缓存

主要看业务是否可以忍受非真正实时的数据,如果查询条件相对复杂且多变,走缓存也不是一种好的方法

或者考虑根据查询条件的维度,做相应的聚合表,线上的请求就查询聚合表,不走原表

比如,用户下单后,有一份订单明细,订单明细表的量级太大,单在产品侧透出的查询功能是以 维度,那就可以将每个用户的每天数据聚合起来,在聚合表就是一个用户一天只有一条汇总后的数据

思路大致就是以空间换时间

六、除了读外,写性能也遇到瓶颈?

如果是单库,可以考虑读写分离,

主库接收写请求,从库接收读请求,从库的数据由主库发送的binlog进而更新,实现主从数据一致

如果已经存在了主从架构,读写仍然存在瓶颈

可以考虑分库分表,对某个库的某个表进行拆分

比如现在有一张业务订单表,这张表已经在广告库中,假定这张业务订单表已经有1亿数据量,现在要分库分表
就会将这张表的数据分至多个广告库以及多张表
最明显的好处就是把请求进行均摊

以什么作为分库分表键: 一般按照用户id,主要看经常以哪个维度进行查询、

分库分表之后的id如何生成:mysql自增,redis自增,雪花算法

具体哪种技术,看公司的技术栈
雪花算法:生成一个64 bit 的 long 型的数字作为全局唯一 id

如果采取分库分表,如何迁移:采用双写的思路

a. 增量的数据往新表旧表各写一份
b. 将旧表的数据迁移至新库
c. 迟早新表的数据会追上旧表
d. 检验新表和老表的数据是否正常
e. 开启双读,一部分走新表,一部分走老表
f. 读流量全部切新表,停止老表的写入
同时需要提前准备回滚机制,临时切换失败,能恢复正常业务

以上是关于带你整理面试过程中关于 SQL优化的相关知识的主要内容,如果未能解决你的问题,请参考以下文章

带你整理面试过程中关于ThreadLocal的相关知识

带你整理面试过程中关于ARP 协议的相关知识点

带你整理面试过程中关于多线程中的线程池的相关知识点

带你整理面试过程中关于 Java 中的 异常分类及处理的相关知识

带你整理面试过程中关于 Mybatis 底层的相关知识

带你整理面试过程中关于Redis 中的字典及 rehash的相关知识点