转载:《数据量很大,分页查询很慢,怎么优化?》

当需要从数据库查询的表有上万条记录的时候,一次性查询所有结果会变得很慢,特别是随着数据量的增加特别明显,这时需要使用分页查询。对于数据库分页查询,也有很多种方法和优化的点。

准备工作

为了对下面列举的一些优化进行测试,下面针对已有的一张表进行说明。

  • 表名:order_history
  • 描述:某个业务的订单历史表
  • 主要字段:unsigned int id,tinyint(4) int type
  • 字段情况:该表一共37个字段,不包含 text 等大型数据,最大为 varchar(500),id 字段为索引,且为递增。
  • 数据量:5709294
  • MySQL 版本:5.7.16 线下找一张百万级的测试表可不容易,如果需要自己测试的话,可以写 shell 脚本什么的插入数据进行测试。

一般分页查询

一般的分页查询使用简单的 limit 子句就可以实现。limit 子句声明如下:

  1. SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset

LIMIT 子句可以被用于指定 SELECT 语句返回的记录数。需注意以下几点:

  • 第一个参数指定第一个返回记录行的偏移量,注意从0开始;
  • 第二个参数指定返回记录行的最大数目;
  • 如果只给定一个参数,它表示返回最大的记录行数目;
  • 第二个参数为-1表示检索从某一个偏移量到记录集的结束所有的记录行;

针对这种查询方式,下面测试查询记录量对时间的影响:

  1. select * from orders_history where type=8 limit 10000,1;
  2. select * from orders_history where type=8 limit 10000,10;
  3. select * from orders_history where type=8 limit 10000,100;
  4. select * from orders_history where type=8 limit 10000,1000;
  5. select * from orders_history where type=8 limit 10000,10000;

三次查询时间如下:

  • 查询1条记录:3072ms 3092ms 3002ms
  • 查询10条记录:3081ms 3077ms 3032ms
  • 查询100条记录:3118ms 3200ms 3128ms
  • 查询1000条记录:3412ms 3468ms 3394ms
  • 查询10000条记录:3749ms 3802ms 3696ms

另外我还做了十来次查询,从查询时间来看,基本可以确定,在查询记录量低于100时,查询时间基本没有差距,随着查询记录量越来越大,所花费的时间也会越来越多。

测试查询偏移量对时间的影响:

  1. select * from orders_history where type=8 limit 100,100;
  2. select * from orders_history where type=8 limit 1000,100;
  3. select * from orders_history where type=8 limit 10000,100;
  4. select * from orders_history where type=8 limit 100000,100;
  5. select * from orders_history where type=8 limit 1000000,100;

三次查询时间如下:

  • 查询100偏移:25ms 24ms 24ms
  • 查询1000偏移:78ms 76ms 77ms
  • 查询10000偏移:3092ms 3212ms 3128ms
  • 查询100000偏移:3878ms 3812ms 3798ms
  • 查询1000000偏移:14608ms 14062ms 14700ms

随着查询偏移的增大,尤其查询偏移大于10万以后,查询时间急剧增加。

这种分页查询方式会从数据库第一条记录开始扫描,所以越往后,查询速度越慢,而且查询的数据越多,也会拖慢总查询速度。

使用子查询优化

这种方式先定位偏移位置的 id,然后往后查询,这种方式适用于 id 递增的情况。

  1. select * from orders_history where type=8 limit 100000,1;
  2. select id from orders_history where type=8 limit 100000,1;
  3. select * from orders_history where type=8 and
  4. id>=(select id from orders_history where type=8 limit 100000,1)limit 100;
  5. select * from orders_history where type=8 limit 100000,100;

4条语句的查询时间如下:

  • 第1条语句:3674ms
  • 第2条语句:1315ms
  • 第3条语句:1327ms
  • 第4条语句:3710ms

针对上面的查询需要注意:

  • 比较第1条语句和第2条语句:使用 select id 代替 select * 速度增加了3倍
  • 比较第2条语句和第3条语句:速度相差几十毫秒
  • 比较第3条语句和第4条语句:得益于 select id 速度增加,第3条语句查询速度增加了3倍

其实这种方式能够比一般分页查询快的原因是使用了 id 做比较,所以如果是连续遍历数据的情况下,可以根据上一页最后一条数据的 id 来获取下一页的数据:

  1. select * from orders_history where type=8 and id>1000 limit 100;

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/ndy806 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。