• MySQL大数据量分页查询方法及其优化


    如何优化Mysql千万级快速分页

    MYSQL分页limit速度太慢的优化方法

    MYSQL分页查询优化

    MySQL大数据量分页查询方法及其优化

    select * from table limit m,n

    其中m是指记录开始的index,表示每次开始的索引。默认从0开始,表示第一条记录

    n是指从第m+1条开始,取n条。

    select * from tablename limit 2,4

    即取出第3条至第6条,4条记录

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

    在mysql中limit可以实现快速分页,但是如果数据到了几百万时我们的limit必须优化才能有效的合理的实现分页了,否则可能卡死你的服务器哦。

    如 * from table limit 0,10 这个没有问题 当 limit 200000,10 的时候数据读取就很慢。

    limit10000,20的意思扫描满足条件的10020行,扔掉前面的10000行,返回最后的20行,问题就在这里。

    LIMIT 451350,30 扫描了45万多行,怪不得慢的都堵死了。

    但是limit 30 这样的语句仅仅扫描30行。

    那么如果我们之前记录了最大ID,就可以在这里做文章

    举个例子

    日常分页SQL语句

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

    扫描100020行

    如果记录了上次的最大ID

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

    扫描20行。

    总数据有500万左右,以下例子

       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.只查索引法
    ————————————————
    版权声明:本文为CSDN博主「cbjcry」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/cbjcry/java/article/details/84920169

  • 相关阅读:
    Mysql_大字段问题Row size too large.....not counting BLOBs, is 8126.
    Pycharm快捷键设置(鼠标滚动控制字体大小)
    python实现将base64编码的图片下载到本地
    [CentOS_7.4]Linux编译安装ffmpeg
    contenOs7
    文本特征提取方法研究
    Mahout中相似度计算方法介绍
    基于Mahout的电影推荐系统
    向Python女神推荐这些年我追过的经典书籍
    Mahout推荐算法API详解
  • 原文地址:https://www.cnblogs.com/xiami2046/p/12801803.html
Copyright © 2020-2023  润新知