limit 20000 loading is very slow how to solve

How to optimize Mysql tens of millions of fast paging

MYSQL paging limit is too slow optimization method

MYSQL paging query optimization

 

select * from table limit m,n

Where m refers to the index of the beginning of the record, indicating the index of each start. The default starts from 0, which means the first record

n means starting from the m+1th item and taking n items.

 

select * from tablename limit 2,4

That is, take out the 3rd to 6th, 4th records

---------------------------------------------------------------------------------------------------------

In mysql, limit can achieve fast paging, but if the data reaches several million, our limit must be optimized to achieve paging effectively and reasonably , otherwise your server may be stuck.

 

Such as * from table limit 0,10 this is no problem, when the limit is 200000,10, the data read is very slow.

The meaning of limit10000,20 is to scan 10020 lines that meet the conditions, throw away the first 10000 lines, and return the last 20 lines. The problem is here.

LIMIT 451350,30 scanned more than 450,000 lines, no wonder the slow ones are blocked.

But a statement like limit 30 scans only 30 rows.

 

Then if we recorded the maximum ID before , we can make a fuss here

 

for example

 

Daily paging SQL statement

   select id,name,content from users order by id asc limit 100000,20

Scan 100020 lines

 

If the last maximum ID was recorded

   select id,name,content from users where id>100073 order by id asc limit 20

Scan 20 lines.

 

The total data is about 5 million, the following example

   select * from wl_tagindex where byname='f' order by id limit 300000,10 执行时间是 3.21s

优化后:

select * from (

   select id from wl_tagindex

where byname='f' order by id limit 300000,10

) a

left join wl_tagindex b on a.id=b.id

 

   执行时间为 0.11s 速度明显提升

   这里需要说明的是 我这里用到的字段是 byname ,id 需要把这两个字段做复合索引,否则的话效果提升不明显

 

   

总结

 

   当一个数据库表过于庞大,LIMIT offset, length中的offset值过大,则SQL查询语句会非常缓慢,你需增加order by,并且order by字段需要建立索引。

   如果使用子查询去优化LIMIT的话,则子查询必须是连续的,某种意义来讲,子查询不应该有where条件,where会过滤数据,使数据失去连续性。

   如果你查询的记录比较大,并且数据传输量比较大,比如包含了text类型的field,则可以通过建立子查询。

SELECT id,title,content FROM items WHERE id IN (SELECT id FROM items ORDER BY id limit 900000, 10);

 

   如果limit语句的offset较大,你可以通过传递pk键值来减小offset = 0,这个主键最好是int类型并且auto_increment

 

   SELECT * FROM users WHERE uid > 456891 ORDER BY uid LIMIT 0, 10;

 

   这条语句,大意如下:

SELECT * FROM users WHERE uid >=  (SELECT uid FROM users ORDER BY uid limit 895682, 1) limit 0, 10;

    

   如果limit的offset值过大,用户也会翻页疲劳,你可以设置一个offset最大的,超过了可以另行处理,一般连续翻页过大,用户体验很差,则应该提供更优的用户体验给用户。

------------------------------------------------------------------------------------------------------

数据表 collect ( id, title ,info ,vtype) 就这4个字段,其中 title 用定长,info 用text, id 是逐渐,vtype是tinyint,vtype是索引。这是一个基本的新闻系统的简单模型。现在往里面填充数据,填充10万篇新闻。

 

最后collect 为 10万条记录,数据库表占用硬盘1.6G。OK ,看下面这条sql语句:

 

select id,title from collect limit 1000,10; 很快;基本上0.01秒就OK,再看下面的

select id,title from collect limit 90000,10; 从9万条开始分页,结果?

 

8-9秒完成,my god 哪出问题了????其实要优化这条数据,网上找得到答案。看下面一条语句:

 

select id from collect order by id limit 90000,10; 很快,0.04秒就OK。 为什么?因为用了id主键做索引当然快。网上的改法是:

select id,title from collect where id>=(select id from collect order by id limit 90000,1) limit 10;

 这就是用了id做索引的结果。可是问题复杂那么一点点,就完了。看下面的语句

select id from collect where vtype=1 order by id limit 90000,10; 

 很慢,用了8-9秒!

 

到了这里我相信很多人会和我一样,有崩溃感觉!vtype 做了索引了啊?怎么会慢呢?vtype做了索引是不错,你直接 select id from collect where vtype=1 limit 1000,10; 是很快的,基本上0.05秒,可是提高90倍,从9万开始,那就是0.05*90=4.5秒的速度了。和测试结果8-9秒到了一个数量级。从这里开始有人 提出了分表的思路,这个和discuz 论坛是一样的思路。思路如下:

 

建一个索引表: t (id,title,vtype) 并设置成定长,然后做分页,分页出结果再到 collect 里面去找info 。 是否可行呢?实验下就知道了。

 

10万条记录到 t(id,title,vtype) 里,数据表大小20M左右。用

 

select id from t where vtype=1 order by id limit 90000,10; 很快了。基本上0.1-0.2秒可以跑完。为什么会这样呢?我猜想是因为collect 数据太多,所以分页要跑很长的路。limit 完全和数据表的大小有关的。其实这样做还是全表扫描,只是因为数据量小,只有10万才快。OK, 来个疯狂的实验,加到100万条,测试性能。

 

加了10倍的数据,马上t表就到了200多M,而且是定长。还是刚才的查询语句,时间是0.1-0.2秒完成!分表性能没问题?错!因为我们的limit还是9万,所以快。给个大的,90万开始

 

select id from t where vtype=1 order by id limit 900000,10; 看看结果,时间是1-2秒!

 

 

why ?? 分表了时间还是这么长,非常之郁闷!有人说定长会提高limit的性能,开始我也以为,因为一条记录的长度是固定的,mysql 应该可以算出90万的位置才对啊? 可是我们高估了mysql 的智能,他不是商务数据库,事实证明定长和非定长对limit影响不大? 怪不得有人说 discuz到了100万条记录就会很慢,我相信这是真的,这个和数据库设计有关!

 

好了,我们的测试又回到 collect表,开始测试结论是: 30万数据,用分表法可行,超过30万他的速度会慢道你无法忍受!当然如果用分表+我这种方法,那是绝对完美的。但是用了我这种方法后,不用分表也可以完美解决!

 

答案就是:复合索引! 有一次设计mysql索引的时候,无意中发现索引名字可以任取,可以选择几个字段进来,这有什么用呢?开始的select id from collect order by id limit 90000,10; 这么快就是因为走了索引,可是如果加了where 就不走索引了。抱着试试看的想法加了 search(vtype,id) 这样的索引。然后测试

 

select id from collect where vtype=1 limit 90000,10; 非常快!0.04秒完成!

 

再测试: select id ,title from collect where vtype=1 limit 90000,10; 非常遗憾,8-9秒,没走search索引!

 

再测试:search(id,vtype),还是select id 这个语句,也非常遗憾,0.5秒。

 

 

综上:如果对于有where 条件,又想走索引用limit的,必须设计一个索引,将where 放第一位,limit用到的主键放第2位,而且只能select 主键!

----------------------------------------------------------------------------------------------------

limit 分页优化方法

 

   1.子查询优化法

   先找出第一条数据,然后大于等于这条数据的id就是要获取的数据

   缺点:数据必须是连续的,可以说不能有where条件,where条件会筛选数据,导致数据失去连续性

这种:

select * from Member where MemberID >= (select MemberID from Member limit 100000,1) limit 100 

 

   实验下

  

 mysql> set profi=1;
   Query OK, 0 rows affected (0.00 sec)
   mysql> select count(*) from Member;
   +———-+
   | count(*) |
   +———-+
   |   169566 |
   +———-+
   1 row in set (0.00 sec)
   mysql> pager grep !~-
   PAGER set to ‘grep !~-‘
   mysql> select * from Member limit 10, 100;
   100 rows in set (0.00 sec)
   mysql> select * from Member where MemberID >= (select MemberID from Member limit 10,1) limit 100;
   100 rows in set (0.00 sec)
   mysql> select * from Member limit 1000, 100;
   100 rows in set (0.01 sec)
   mysql> select * from Member where MemberID >= (select MemberID from Member limit 1000,1) limit 100;
   100 rows in set (0.00 sec)
   mysql> select * from Member limit 100000, 100;
   100 rows in set (0.10 sec)
   mysql> select * from Member where MemberID >= (select MemberID from Member limit 100000,1) limit 100;
   100 rows in set (0.02 sec)
   mysql> nopager
   PAGER set to stdout
   mysql> show profilesG
   *************************** 1. row ***************************
   Query_ID: 1
   Duration: 0.00003300
      Query: select count(*) from Member
   *************************** 2. row ***************************
   Query_ID: 2
   Duration: 0.00167000
      Query: select * from Member limit 10, 100
   *************************** 3. row ***************************
   Query_ID: 3
   Duration: 0.00112400
      Query: select * from Member where MemberID >= (select MemberID from Member limit 10,1) limit 100
   *************************** 4. row ***************************
   Query_ID: 4
   Duration: 0.00263200
      Query: select * from Member limit 1000, 100
   *************************** 5. row ***************************
   Query_ID: 5
   Duration: 0.00134000
      Query: select * from Member where MemberID >= (select MemberID from Member limit 1000,1) limit 100
   *************************** 6. row ***************************
   Query_ID: 6
   Duration: 0.09956700
      Query: select * from Member limit 100000, 100
   *************************** 7. row ***************************
   Query_ID: 7
   Duration: 0.02447700
      Query: select * from Member where MemberID >= (select MemberID from Member limit 100000,1) limit 100

 

    从结果中可以得知,当偏移1000以上使用子查询法可以有效的提高性能。

 

2、使用 id 限定优化

这种方式假设数据表的id是连续递增的,则我们根据查询的页数和查询的记录数可以算出查询的id的范围,可以使用 id between and 来查询:

select * from orders_history where type=2 and id between 1000000 and 1000100 limit 100;

 查询时间:15ms 12ms 9ms

 

这种查询方式能够极大地优化查询速度,基本能够在几十毫秒之内完成。限制是只能使用于明确知道id的情况,不过一般建立表的时候,都会添加基本的id字段,这为分页查询带来很多遍历。

 

还可以有另外一种写法:

select * from orders_history where id >= 1000001 limit 100;

 

当然还可以使用 in 的方式来进行查询,这种方式经常用在多表关联的时候进行查询,使用其他表查询的id集合,来进行查询:

select * from orders_history where id in
    (select order_id from trade_2 where goods = 'pen')
limit 100;

 这种 in 查询的方式要注意:某些 mysql 版本不支持在 in 子句中使用 limit。 

 

   2.倒排表优化法

   倒排表法类似建立索引,用一张表来维护页数,然后通过高效的连接得到数据

   缺点:只适合数据数固定的情况,数据不能删除,维护页表困难

 

   3.反向查找优化法

   当偏移超过一半记录数的时候,先用排序,这样偏移就反转了

   缺点:order by优化比较麻烦,要增加索引,索引影响数据的修改效率,并且要知道总记录数

   ,偏移大于数据的一半

   引用

   limit偏移算法:

   正向查找: (当前页 – 1) * 页长度

   反向查找: 总记录 – 当前页 * 页长度

   做下实验,看看性能如何

   总记录数:1,628,775

   每页记录数: 40

   总页数:1,628,775 / 40 = 40720

   中间页数:40720 / 2 = 20360

   第21000页

   正向查找SQL:

   Sql代码

   SELECT * FROM `abc` WHERE `BatchID` = 123 LIMIT 839960, 40

   时间:1.8696 秒

   反向查找sql:

   Sql代码

   SELECT * FROM `abc` WHERE `BatchID` = 123 ORDER BY InputDate DESC LIMIT 788775, 40

   时间:1.8336 秒

   第30000页

   正向查找SQL:

   Sql代码

   1.SELECT * FROM `abc` WHERE `BatchID` = 123 LIMIT 1199960, 40

   SELECT * FROM `abc` WHERE `BatchID` = 123 LIMIT 1199960, 40

   时间:2.6493 秒

   反向查找sql:

   Sql代码

   1.SELECT * FROM `abc` WHERE `BatchID` = 123 ORDER BY InputDate DESC LIMIT 428775, 40

   SELECT * FROM `abc` WHERE `BatchID` = 123 ORDER BY InputDate DESC LIMIT 428775, 40

    时间:1.0035 秒

   注意,反向查找的结果是是降序desc的,并且InputDate是记录的插入时间,也可以用主键联合索引,但是不方便。

 

   4.limit限制优化法

   把limit偏移量限制低于某个数。。超过这个数等于没数据,我记得alibaba的dba说过他们是这样做的

   5.只查索引法

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325994133&siteId=291194637