笔者在之前的一次面试经历中,被面试官问到这一问题,当时答得不是很好,后来也查看了很多资料,今天做一个详细的总结。
在我们的日常开发中,肯定或多或少的遇到过 SQL 语句查询慢的问题,遇到这种问题无非就两种情况:一、大部分情况下都是执行的很快,偶尔需要很长时间。二、SQL 语句一直都执行得很慢。接下来将对这两种情况进行详细分析。
一、偶尔执行很慢
大部分情况下执行都是正常得,部分时间执行很慢,首先可能确认 SQL 语句本身是没有多大的问题的,那是什么原因呢?
1、数据库在刷新脏页
脏页 是指当内存中的数据页和磁盘数据页中的不一致时,我们把内存数据页称作“脏页”;否则称之为“干净页”。
当我们对数据库进行更新操作(增、删、改等)时,数据库首先会在内存中对数据进行更新,但这并不会直接同步到磁盘中,而是把这些更新的记录写道 redo log 日志中去,然后等系统空闲时再写到磁盘中。
刷新脏页的情况:
- redo log写满了,和其他容器一样,redo log 的容量也是有限的,当我们一直对数据库进行操作,redo log 很快就会被写满,这时数据库就不能等到系统空闲时刷新脏页了,就只能暂停其他操作,尽快把数据同步到磁盘当中,因此,有时候我们的 SQL 语句执行的会比较慢的情况。
- 内存不够用了,上面介绍了,数据库会在内存中进行操作,当我们一次性查询很多数据,而这些数据都不在内存当中,更可恶的是此时的内存不够用了,这时候就需要释放一部分内存数据页,如果这些数据页都是脏页,那么将刷新脏页;如果是干净页的话直接释放就好了。
- MySQL 认为系统空闲,这时候就会刷新脏页了。
- MySQL 正常关闭,当数据库关闭的时候,会把脏页刷新到磁盘中,在下一次启动时,直接从磁盘中读取数据。
2、拿不到锁
这个应该很好理解吧,数据库有各种锁,当我们执行一条语句的时候,可能会需要某种锁,但此时有其他用户在使用这个锁,那很无奈啊,只能等别人用完释放锁了。
二、一直执行很慢
在数据量一定的情况下,如果你的 SQL 语句执行的很慢,你就要好好想想你的 SQL 语句了。
首先我们建如下的表,为后续的说明打基础。
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY(`id`)
) ENGINE = InnoDB;
1、没有用到索引
SELECT * FROM `test` WHERE `c` < 10000;
SELECT * FROM `test` WHERE `id`-1 = 100;
以上的这两种情况都不会用到索引,第一个是因为字段c
本身没有索引,第二是在索引列做了运算操作。至于其他的一些会造成索引失效的情况,在这里就不详细介绍了。因为接下来的内容才是重点。
2、数据库自己选错了索引
数据库在执行语句的时候,会进行一个预测:走索引扫描的行数少还是直接全表扫描的行数少?扫描行数越少也好,扫描行数越少,I/O操作的次数也就越少。
主键索引存放的值是整行字段的数据,而非主键索引上存放的值不是整段的数据,而是主键字段的值。如果查询走非主键字段,会先查询对应主键的值,然后再走一遍主键索引,然后查询到整行数据。
系统怎么预测判断呢?
系统是通过索引的来判断的,一个索引上不同的值越多,意味着出现相同数值的索引越少,意味着索引的区分度越高。我们也把区分度称之为 基数 ,即 区分度越高,基数越大。
系统怎么获取这个索引的基数呢? 通过采样的办法,遍历部分数据。采样的方式可能会出现失误,一个索引的基数实际上可能很大,但在采样的过程中,很不幸,数据库遍历了那份基数很小的。然后就直接全表扫描了。
未完,待续!