MySQL 查询 limit 100000000, 10 和 limit 10 速度一样快吗?
MySQL内部分为server层和存储引擎层。一般情况下存储引擎都用innodb。
server层有很多模块,其中需要关注的是执行器是用于跟存储引擎打交道的组件。
执行器可以通过调用存储引擎提供的接口,将一行行数据取出,当这些数据完全符合要求(比如满足其他where条件),则会放到结果集中,最后返回给调用mysql的客户端。
两种查询方式。对应 limit offset, size 和 limit size 两种方式。
而其实 limit size ,相当于 limit 0, size。也就是从0开始取size条数据。
也就是说,两种方式的区别在于offset是否为0。
1. 以主键索引的 LIMIT 10 为例
LIMIT 10
查询的是结果集的前 10 行数据。这种情况下,MySQL 只需要扫描并返回前 10 行数据,操作相对简单且快速。
SELECT * FROM table_name LIMIT 10;
server层会调用innodb的接口,在innodb里的主键索引中获取到第0到10条完整行数据,依次返回给server层,并放到server层的结果集中,返回给客户端。
2. 以主键索引的 LIMIT 100000000, 10 为例
LIMIT 100000000, 10
查询的是从结果集的第 100000001 行开始的 10 行数据。这种情况下,MySQL 需要先扫描前 100000000 行数据并丢弃,然后再返回接下来的 10 行数据。这种操作会导致大量的行扫描和丢弃操作,效率很低。
SELECT * FROM table_name LIMIT 100000000, 10;
server层会调用innodb的接口,由于这次的offset=100000000,会在innodb里的主键索引中获取到第0到(100000000+ 10)条完整行数据,返回给server层之后根据offset的值挨个抛弃,最后只留下最后面的size条,也就是10条数据,放到server层的结果集中,返回给客户端。
可以看出,当offset非0时,server层会从引擎层获取到很多无用的数据,而获取的这些无用数据都是要耗时的。
以非主键索引的 LIMIT 10 为例
SELECT * FROM table_name LIMIT 10;
server层会调用innodb的接口,在innodb里的非主键索引中获取到第0条数据对应的主键id后,回表到主键索引中找到对应的完整行数据,然后返回给server层,server层将其放到结果集中,返回给客户端。
而当offset>0时,且offset的值较小时,逻辑也类似,区别在于,offset>0时会丢弃前面的offset条数据。
也就是说非主键索引的limit过程,比主键索引的limit过程,多了个回表的消耗。
但当offset变得非常大时,比如600万,此时执行explain。
可以看到type那一栏显示的是ALL,也就是全表扫描。
这是因为server层的优化器,会在执行器执行sql语句前,判断下哪种执行计划的代价更小。
很明显,优化器在看到非主键索引的600w次回表之后,摇了摇头,还不如全表一条条记录去判断算了,于是选择了全表扫描。
因此,当limit offset过大时,非主键索引查询非常容易变成全表扫描。是真性能杀手。
性能比较
LIMIT 10
:MySQL 只需扫描前 10 行数据,快速返回结果,性能高。LIMIT 100000000, 10
:MySQL 需要扫描前 100000000 行数据并丢弃,然后返回接下来的 10 行数据,性能低。
优化大偏移量的查询
当你需要从一个大偏移量开始查询时,可以采用以下优化方法:
1. 使用覆盖索引
确保查询使用了覆盖索引,这样可以减少全表扫描的开销。
SELECT col1, col2 FROM table_name FORCE INDEX (index_name) WHERE indexed_column >= value LIMIT 100000000, 10;
2. 通过主键范围查询
使用主键范围查询,先查找主键值,再根据主键值进行查询,避免大偏移量的扫描。
第一步:找到起始主键值
SELECT id FROM table_name ORDER BY id LIMIT 100000000, 1;
-- 假设起始主键值为 start_id
-- 第二步:基于主键值进行查询
SELECT * FROM table_name WHERE id >= start_id LIMIT 10;
3. 使用延迟关联(Deferred Join)
先根据索引字段查找主键,再根据主键进行关联查询,从而避免大偏移量的扫描。
-- 第一步:查找主键值
SELECT id FROM table_name ORDER BY id LIMIT 100000000, 10;
-- 第二步:基于主键进行关联查询
SELECT * FROM table_name WHERE id IN (id1, id2, ..., id10);
4. 使用分页技术
对于用户分页的场景,通常需要记录当前页和页数,采用基于主键或其他唯一标识的分页技术,避免使用 LIMIT
大偏移量查询。
结论
MySQL 中 LIMIT 100000000, 10
和 LIMIT 10
的查询速度并不一样。大偏移量的查询会导致性能问题,可以通过覆盖索引、主键范围查询、延迟关联等方法进行优化。