一条SQL执行很慢的原因呢有哪些?
当我们在日常工作中写SQL时,可能会遇到某条SQL执行得特别慢,甚至有时候会发生偶尔很慢或者持续很慢的情况。尤其是在面试中,如果你能快速诊断并给出原因,往往会给面试官留下深刻的印象。所以今天我们就来聊聊,一条SQL执行很慢的原因有哪些,并且如何识别和解决这些问题。
其实,一条 SQL 执行起来很慢,可以分为两种情况。
第一种情况:大多数情况下是正常的,只是偶尔有几次很慢。
第二种情况:在数据量一定的情况下,每次执行起来都很费劲,很慢。
偶尔很慢的情况
我们先来分析偶尔很慢的情况。
1. 数据库在刷新脏页
你可能会听说过“脏页”这个概念。脏页是指数据库内存中存储的页面,它们与磁盘上的数据不一致。当数据库的内存缓存达到一定的阈值,或者系统需要释放内存时,数据库会把这些脏页刷到磁盘上,保证数据的一致性。这个过程有时会消耗较长的时间,尤其是在数据库的读写负载较重时,就可能导致你的SQL在执行时变慢。
2. 拿不到锁被阻塞
数据库中很多操作是需要加锁的,尤其是涉及到写操作时,锁机制就尤为重要。假如你执行一个查询操作,但该数据正被其他进程修改,数据库为了保证数据的一致性,会让你的查询等待,直到其他操作释放锁。那执行这条SQL语句的时候拿不到锁,就只能等着咯。
如果要判断是不是真的在等待锁的释放,可以使用 show processlist 这个命令来查看当前的状态。
一直很慢的情况
如果某条SQL一直都很慢,通常是设计层面的问题,我们来看几个常见的原因。
1. 字段本来没有索引
如果说我想执行这个语句:
那如果 c 这个字段上本来就没有索引,那这个查询语句肯定就走全表扫描了,那么执行起来很慢也就是正常的了。
2. 有索引却没用到
如果你在 c 字段上加了索引,你执行了这个语句:
执行之后,你发现还是很慢,这是为啥?因为 c – 1 这是一个表达式计算,这样的情况下也是不会走索引的。所以这种情况是一定要注意的。
3. 对索引使用函数
如果上面的错误你都已经规避掉了,但是你又执行了这样一个语句:
这个语句实际上也是不会走索引的,因为你对索引使用了函数。
4.数据库自己选错了索引
现在,我们在 c 字段上创建索引,然后执行如下 SQL 语句:
尽管 c
字段上有索引,但数据库有可能会选择走全表扫描而不是使用索引,为什么会出现这种情况呢?
在 SQL 查询优化中,数据库最关注的一个问题是:如何高效地获取数据。为了做到这一点,数据库必须要预测查询会扫描多少行数据,也就是要进行行数预测。
行数预测是如何做的呢?
首先,数据库在查询时会估算条件筛选出的行数(即符合 100 < c AND c < 100000
条件的行数)。然后,数据库会选择两种方式中成本较低的一种来执行查询。要预测这个行数,数据库需要估算符合条件的行占总行数的比例,这个比例就是“选择性”。
选择性越高,说明符合条件的行数越少。选择性低,则符合条件的行数较多。
那么,这个行数是如何预测的呢?
这里引入一个索引基数的概念。索引基数是一个表示索引中不同值的数量的指标,它反映了索引的“分散程度”。具体来说,索引基数越大,意味着索引中的不同值的数量也越多。基数高通常意味着数据的分布比较均匀,查询时会更加高效。
举个简单的例子:
- 如果字段
c
存储的是人的年龄,且数据均匀分布在 0 到 100 之间,那么索引基数会相对较高,数据库通过索引查找符合条件的记录时会非常高效。 - 如果字段
c
存储的是一个很少有不同值的数据,比如性别(只有“男”和“女”两个值),那么索引基数就很低,数据库通过这个索引查找记录时效率反而不高。
在我们的查询条件 100 < c AND c < 100000
下,数据库会根据这个条件判断有多少符合条件的行。如果 c
字段的基数较低,那么符合这个条件的行数可能会非常多,导致即使有索引,数据库也不一定选择使用索引。
3那如何预测索引基数呢?
要预测索引基数,数据库并不直接计算所有的数据,而是通过“采样”来估算。采样是一种通过取数据子集来推测整个数据集特征的方法。在 MySQL 中,数据库会从数据表中随机抽取一些行,通过这些行的分布情况来估算整个表中字段 c
的基数。
举个例子:
假设你有一个表 t
,包含了 1000 万行数据,字段 c
存储了用户年龄数据。数据库会从表中随机抽取一部分数据,比如 1 万行。通过分析这 1 万行中 c
字段的分布,数据库就能估算出整个表中 c
字段的基数。
如果这些数据采样中 c
字段的值大部分集中在某几个范围内,那么数据库会认为 c
字段的基数不高,查询时使用索引可能不会提高效率,因此它可能选择全表扫描。
现在我们回到问题本身。即使字段 c
上有索引,数据库也有可能选择全表扫描。其原因在于:数据库通过预测查询的成本,发现使用索引可能带来的效果并不明显。特别是在以下两种情况下:
- 索引基数低:如前面提到的,如果索引基数低(例如
c
字段的值分布不均),数据库会发现即使使用索引,也不能有效减少需要扫描的行数。这时,数据库可能选择全表扫描,因为全表扫描有时比使用低基数索引更高效。 - 条件选择性低:在查询条件
100 < c AND c < 100000
中,如果大多数行都符合这个条件,那么即使c
字段有索引,查询的选择性仍然很低。数据库会评估使用索引可能不会减少扫描的行数,从而选择全表扫描。
总结
SQL执行很慢的问题可以分为偶尔很慢和一直很慢两种情况。偶尔很慢的情况多与数据库的内部操作有关,比如脏页刷新和锁竞争。而一直很慢的情况通常是设计不当,比如缺少索引、函数导致索引无法使用、或者数据库自身的原因选错了索引。