文章最开始先给咱们两条sql,请猜猜他们履行会有什么区别?

SELECT * from student s where age < 17 and name ='zhangsan12' and create_time < '2023-01-17 10:23:08' order by age LIMIT 1
SELECT * from student s where age < 17 and name ='zhangsan12' and create_time < '2023-01-17 10:23:08' order by age LIMIT 2

这两条sql看似仅仅limit的数值不同,可是第一个履行耗时3ms,第二个履行耗时66s,相差2000多倍

故事的起因

今天要讲的这件事和上述的两个sql有关,是数年前遇到的一个关于MySQL查询功能的问题。主要是最近刷到了一些关于MySQL查询功能的文章,大部分文章中讲到的都仅仅一些常见的索引失效场合,所以我回想起了当初被那个离奇的“索引失效”支配的恐惧。

场景复现

因为事情已经曩昔多年,因而我只能凭仗记忆在本地的数据库进行模仿。首要创立数据库school,数据表student

CREATE TABLE `student` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `age` int DEFAULT NULL,
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `student_age_IDX` (`age`) USING BTREE,
  KEY `student_create_time_IDX` (`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

结构简单明了,其间agecreate_time使用BTREE构建了索引。

在使用存储过程往数据库填充了500w条左右的数据后,咱们使用如下的sql来进行测试:

SELECT * from student s where age < 17 and name ='zhangsan12' and create_time < '2023-01-17 10:23:08' order by age LIMIT 1

成果如下:

一次偶然机会发现的MySQL“负优化”

之后测验履行如下sql:

SELECT * from student s where age < 17 and name ='zhangsan12' and create_time < '2023-01-17 10:23:08' order by age LIMIT 2

一次偶然机会发现的MySQL“负优化”

这便是咱们开篇说到的那两个sql,功能距离是2000多倍。那么问题来了,为什么limit的值会影响sql功能,并且会差别如此之大?故事要从MySQL优化说起。

MySQL的“负优化”

在剖析sql功能的时候,咱们当然最常用的是EXPLAIN,将两个sql分别EXPLAIN,成果如下:

一次偶然机会发现的MySQL“负优化”

一次偶然机会发现的MySQL“负优化”
能够看到sql履行计划并无二致,那么为什么履行时间却相差这么远呢?

查找相关文档就能够在MySQL的官网找到如下的解释:

If you combine LIMIT row_count with ORDER BY, MySQL stops sorting as soon as it has found the first row_count rows of the sorted result, rather than sorting the entire result. If ordering is done by using an index, this is very fast. If a filesort must be done, all rows that match the query without the LIMIT clause are selected, and most or all of them are sorted, before the first row_count are found. After the initial rows have been found, MySQL does not sort any remainder of the result set.

大致意思便是LIMITORDER BY一起使用MySQL会在找到LIMIT设定的值后立即返回。虽然没有找到详细的原理性的解释,可是从上述的这个描述中咱们也能够大致理解这个思路了。

MySQLLIMITORDER BY是特别的组合,尤其是当ORDER BY中的存在BTREE索引的状况下。

一般的查询是依据条件进行筛选,然后在成果会集排序,然后获取LIMIT条数的数据,可是在具有上述条件的特别sql中履行逻辑是这样的,依据ORDER BY字段的B+树索引来查找满意条件的数据,直到凑满LIMIT设定的数值为止,这就存在一个问题,在成果会集的数据大于LIMIT的场景下,这个功能固然是十分棒的,可是如果最后的成果会集的数据小于LIMIT,就会存在永久凑不满的状况,所以最终这个MySQL的功能优化就会变满足表扫描的“负优化”。

依据上述的状况来看的话咱们能够斗胆猜想,既然是索引导致的优化问题,那么是不是把age字段的索引去掉反而会更快?

手动履行DROP INDEX student_age_IDX ON school.student删除索引,然后履行句子,果然履行速度变成了毫秒级:

一次偶然机会发现的MySQL“负优化”

检查履行计划发现在履行时使用了create_time的索引,因而其速度也能保持在毫秒级。

一次偶然机会发现的MySQL“负优化”

然后咱们爽性把create_time的索引也去除去:

一次偶然机会发现的MySQL“负优化”

一次偶然机会发现的MySQL“负优化”
能够看到没有索引的状况下耗时也不过是1秒出头,远远不是66秒。可见在这种状况下MySQL的功能优化乃至远远比不上无索引的查询。

一次偶然机会发现的MySQL“负优化”

总结

其实呈现这个问题的场景也不算十分特别,可是排查原因相当困难。当初是花了好几天查资料翻文档加上不断实验才找到了问题所在。只能说MySQL在解析和履行sql的背后做了很多的优化,可是这部分对于不够熟悉了解的人来说确实是太黑盒,遇到相似的问题排查也很困难。或许这便是程序员成长路上的必经之路吧。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。