考虑两条MySQL语句的执行,其中key1是二级索引,id是主键。

1
select * from my_table order by key1 limit 1;

该条语句,key1是二级索引,本身就是有序的,那么当查询一条语句之后,它就会直接回表查询具体的数据然后返回。

但是如果SQL变为下面的写法:

1
select * from my_table order by key1 limit 10000, 1;

执行这条语句就会发现它走了全表的扫描 + filesort,它并没有像想象中的那样,扫描到10001的索引,然后回表返回结果。

limit的执行

MySQL分为Server层和具体的存储引擎层,Server层是统一的,而存储引擎我们这里默认使用InnoDB。

当具体执行一个带有limit操作的SQL时,它并不会在存储引擎层进行过滤,存储引擎查询到所需要的所有数据,然后返回给Server层,Server层依据具体的查询需求来进行过滤,也就是说需要先查询10001条数据返回给Server,然后进行过滤。

优化器选择

优化器在执行这个查询时会有两种选择

  1. 全表扫描,然后返回,过滤掉不需要的数据。
  2. 第二种查询方案涉及到数据返回策略,InnoDB在执行查询索引时,查找到一条满足条件的数据后,会进行回表,查找完整数据,然后返回给Server层,Server层在返回给用户时发现还有limit,所以要做限制,就不能进行返回,并开始计数。直到引擎层给Server层返回了10001条数据后,才可以完成limit操作,之后再将结果返回给用户,也就是说这里需要进行10001次回表操作,并且要查询10001行数据,然后前10000行全部被Server层丢弃。

这两种方案会由优化器决定走哪一种,但不管走哪一种,都需要进行大量的回表操作。

结论

当执行 limit 1 和执行 limit 10000,1时,性能上的差距还是很大的。并且由于MySQL内存有限,在查询前10000行数据时,难免会有数据页被移出内存,然后从磁盘中加载新的数据页,这也会消耗一定的时间。