一条SQL执行很慢的原因

一条SQL执行很慢的原因

References : 《苦逼的码农》公众号(帅弟nb😋)

image.png
功利一点来讲,这个问题就是帅弟腾讯面试时遇到的,我也极有可能遇到,因为这种类似于“浏览器输入一个url之后会发生什么”的问题,是比较主观的,根据回答的内容更加能够考察出一个人的技术深度,去除应试的因素,一条SQL语句执行的很慢这样的问题也确确实实会在具体的实践中遇到,这里记录一些这个问题的理解和总结


分情况讨论:

  • 大多数情况下是正常的,但是偶尔会很慢
  • 数据量不变,这条语句一直就很慢

一.偶尔很慢

1.数据库正在刷新脏页

当我们对数据库进行更新时,并没有直接修改磁盘文件,而是将内存数据页进行更新,然后再将更新的记录追加到redo log日志中,等到一定的时机,再将内存数据页刷新到磁盘数据页中去

当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。

上面说的是一定的时机刷新脏页,那么是什么时机呢?

  • redo log写满了:
    如果数据库一直很忙,更新操作又很频繁,那么这时候日志文件满了之后,就要进行类似于JVM中STW的操作,暂停一切更新操作,来全力的执行刷新脏页操作,这个过程更新操作性能下降,不,是消失为0,所以就会导致sql语句执行突然变慢,类似于抖动的感觉,下一次执行就很慢再出现这种情况了

  • 内存不够用了:
    当需要新的内存页时,要申请内存,但是这时出现内存不足的情况,就会通过相应的页面置换算法淘汰掉一部分页面,如果被淘汰的是干净页,那没啥说的,直接释放,如果是脏页还要进行向磁盘数据页中刷新脏页的操作

    InnoDB用缓冲池buffer pool来管理内存,缓冲池中的内存页有三种状态:

    1. 还没使用

    2. 使用了并且是干净页

    3. 使用了并且是脏页
      InnoDB的策略是尽量使用内存,因此对于一个长时间运行的库来说,未被使用的页面很少。当要读入的数据页没有在内存的时候,就必须到缓冲池中申请一个数据页。这时候只能把最久不使用的数据页从内存中淘汰掉:如果要淘汰的是一个干净页,就直接释放出来复用;当如果是脏页,就必须先讲脏页刷盘,变成干净页后才能复用。所以刷脏页是常态。

      引用自:https://gsmtoday.github.io/2019/02/08/flush/

  • mysql认为系统空闲的时候

  • mysql正常关闭的时候

2.拿不到锁

这种情况在并发访问数据库的时候也会发生,当我的sql语句要访问的表或者表的某些行加上了表级锁或者行级锁,并且其他人正在占用锁的时候,那么就只能慢慢等待人家释放锁了

在mysql中可以使用show processlist查看相应占用者

二.一直很慢

1.没用到索引

没用到索引还是两种情况:

  • 没有建立索引:这种情况没啥可说的,每一次都是全表扫描
  • 有索引但是没用上:这就需要考虑索引失效的问题,这里有尚硅谷周阳关于索引失效的笔记
    索引失效与优化详解

2.数据库自己选错索引

这个概念还是第一次听到,这里有一篇分析的非常好的文章,MySQL为什么有时候会选错索引?,可以仔细研读一下

三.总结

一个 SQL 执行的很慢,我们要分两种情况讨论:

1、大多数情况下很正常,偶尔很慢,则有如下原因

(1)、数据库在刷新脏页,例如 redo log 写满了需要同步到磁盘。

(2)、执行的时候,遇到锁,如表锁、行锁。

2、这条 SQL 语句一直执行的很慢,则有如下原因。

(1)、没有用上索引:例如该字段没有索引;由于对字段进行运算、函数操作导致无法用索引。

(2)、数据库选错了索引。

关于数据库选错索引需要着重理解一下,这里面是和mysql的优化器的统计工作相关的,里面还涉及到了索引的区分度等概念,值得重视起来