大数据分页方案

标签: tuicool | 发表时间:2016-08-16 08:00 | 作者:
出处:http://itindex.net/admin/pagedetail

软件开发中,常用要用到分页、计算总数,数据量超过千万、上亿的时候,往往 count的需要超过 1s 的执行时间,甚至 3-5s,对于一个追求性能的前沿团队来说,这个不能忍啊!

为什么会慢?

mysql 会对所有符合的条件做一次扫描。

select count(*) from table_a where a = '%d' ...

如果 a=%d 的数据有 1000W 条,那么数据库就会扫描一次 1000W 条数据库。如果不带查询条件,那这种全表扫描将更可怕。

count(*) 和 count(1)、count(0)

  • count(expr) 为统计 expr 不为空的记录

  • count(*) 它会计算总行数,不管你字段是否有值都会列入计算范围。

  • coount(0),count(1) 没有差别,它会计算总行数

Example 1:

mysql> explain extended select count(*) from user;
...
1 row in set, 1 warning (0.34 sec)

mysql> show warnings;
+-------+------+--------------------------------------------------+
| Level | Code | Message |
+-------+------+--------------------------------------------------+
| Note | 1003 | select count(0) AS `count(*)` from `user` |

Example 2:

mysql> select count(*) from login_log
 -> ;
+----------+
| count(*) |
+----------+
| 2513 |
+----------+
1 rows in set (0.00 sec)

mysql> select count(logoutTime) from login_log;
+-------------------+
| count(logoutTime) |
+-------------------+
| 308 |
+-------------------+
1 rows in set (0.00 sec)

怎么解决?

MyISAM DB

MyISAM 引擎很容易获得总行数的统计,查询速度变得更快。因为 MyISAM 存储引擎已经存储了表的总行数。

MyISAM 会为每张表维护一个 row count 的计数器,每次新增加一行,这个计数器就加 1。但是如果有查询条件,那么 MyISAM 也 game over 了,MyISAM 引擎不支持条件缓存。

On MyISAM, doing a query that does SELECT COUNT(*) FROM {some_table}, is very fast, since MyISAM keeps the information in the index

其他 DB 引擎

受到 MySIAM DB 的启发,我们可以手动维护总数缓存在表的索引中了。

  1. 如果 ID 连续,且基本不会断开。直接取最大值 ID

  2. 如果表中存在连续的数字列并设为索引,那么通过页码即可计算出此字段的范围,直接作范围查询即可:

    start = (page-1)*pagesize+1 
    end = page*pagesize 
    select * from table where id >start and id <=end
  3. 涉及到总数操作,专门维护一个总数。新增一个用户,总数值加 1, 需要总数的时候直接拿这个总数, 比如分页时。如果有多个条件,那么就需要维护多个总数列。该方案的扩展性更好,随着用户表数量增大, 水平切分用户表,要获取用户总数,直接查询这个总数表即可。

分页正反偏移

数据库自带的 skip 和 limit 的限制条件为我们创建了分页的查询方式,但是如果利用不对,性能会出现千倍万倍差异。

简单一点描述:limit 100000,20 的意思扫描满足条件的 100020 行,扔掉前面的 100000 行,返回最后的 20 行,问题就在这里。如果我反向查询 oder by xx desc limit 0,20,那么我只要索引 20 条数据。

Example 3

mysql> select count(*) from elastic_task_log_copy;
+----------+
| count(*) |
+----------+
| 1705162 |
+----------+
1 rows in set (2.31 sec)

正向偏移查询。超级浪费的查询,需要先 skip 大量的符合条件的查询。

mysql> select id from elastic_task_log_copy order by id asc limit 1705152,10;
+---------+
| id |
+---------+
| 1705157 |
| 1705158 |
| 1705159 |
| 1705160 |
| 1705161 |
| 1705162 |
| 1705163 |
| 1705164 |
| 1705165 |
| 1705166 |
+---------+
10 rows in set (2.97 sec)

反向偏移查询。同样的查询结果,千差万别的结果。

mysql> select id from elastic_task_log_copy order by id desc limit 0,10;
+---------+
| id |
+---------+
| 1705166 |
| 1705165 |
| 1705164 |
| 1705163 |
| 1705162 |
| 1705161 |
| 1705160 |
| 1705159 |
| 1705158 |
| 1705157 |
+---------+
10 rows in set (0.01 sec)

这两条 sql 是为查询最后一页的翻页 sql 查询用的。由于一次翻页往往只需要查询较小的数据,如 10 条,但需要向后扫描大量的数据,也就是越往后的翻页查询,扫描的数据量会越多,查询的速度也就越来越慢。

由于查询的数据量大小是固定的,如果查询速度不受翻页的页数影响,或者影响最低,那么这样是最佳的效果了(查询最后最几页的速度和开始几页的速度一致)。

在翻页的时候,往往需要对其中的某个字段做排序(这个字段在索引中),升序排序。那么可不可以利用 索引的有序性来解决上面遇到的问题。

比如有 10000 条数据需要做分页,那么前 5000 条做 asc 排序,后 5000 条 desc 排序,在 limit startnum,pagesize 参数中作出相应的调整。

但是这无疑给应用程序带来复杂,这条 sql 是用于论坛回复帖子的 sql,往往用户在看帖子的时候,一般都是查看前几页和最后几页,那么在翻页的时候最后几页的翻页查询采用 desc 的方式来实现翻页,这样就可以较好的提高性能。

游标:上一页的最大值或者最小值

如果你知道上一页和下一页的临界值,那么翻页查询也是信手拈来了,直接就告诉了数据库我的起始查询在哪,也就没有什么性能问题了。我更愿意称这个东西为游标 (Cursor)。

如果做下拉刷新,那么就直接避免掉分页的问题了。根据上一页的最后一个值去请求新数据。

mysql> select id from elastic_task_log_copy where id >= 1699999 limit 10;
+---------+
| id |
+---------+
| 1699999 |
| 1700000 |
| 1700001 |
| 1700002 |
| 1700003 |
| 1700004 |
| 1700005 |
| 1700006 |
| 1700007 |
| 1700008 |
+---------+
10 rows in set (0.01 sec)

缓存和不精准

数据量达到一定程度的时候,用户根本就不关心精准的总数, 没人关心差几个。看看知乎、微博、微信订阅号,不精准的统计到处都是。

如果每次点击分页的时候都进行一次 count 操作,那速度肯定不会快到哪里去。他们一般也是采用计数器的办法。每次新增加一条评论,就把值加 1。这样分页的时候直接拿这个总数进行分页。

其他大神的建议

  1. mysql 的数据查询, 大小字段要分开, 这个还是有必要的, 除非一点就是你查询的都是索引内容而不是表内容, 比如只查询 id 等等

  2. 查询速度和索引有很大关系也就是索引的大小直接影响你的查询效果, 但是查询条件一定要建立索引, 这点上注意的是索引字段不能太多,太多索引文件就会很大那样搜索只能变慢,

  3. 查询指定的记录最好通过 Id 进行 in 查询来获得真实的数据. 其实不是最好而是必须,也就是你应该先查询出复合的 ID 列表, 通过 in 查询来获得数据

  4. mysql 千万级别数据肯定是没问题的, 毕竟现在的流向 web2.0 网站大部分是 mysql 的

  5. 合理分表也是必须的, 主要涉及横向分表与纵向分表, 如把大小字段分开, 或者每 100 万条记录在一张表中等等, 像上面的这个表可以考虑通过 uid 的范围分表, 或者通过只建立索引表, 去掉相对大的字段来处理.

  6. count() 时间比较长, 但是本身是可以缓存在数据库中或者缓存在程序中的, 因为我们当时使用在后台所以第一页比较慢但是后面比较理想

  7. SELECT id 相对 SELECT 差距还是比较大的, 可以通过上面的方法来使用 SELECT id + SELECT... IN 查询来提高性能

  8. 必要的索引是必须的, 还是要尽量返回 5%-20% 的结果级别其中小于 5% 最理想;

  9. mysql 分页的前面几页速度很快, 越向后性能越差, 可以考虑只带上一页, 下一页不带页面跳转的方法, 呵呵这个比较垃圾但是也算是个方案, 只要在前后多查一条就能解决了. 比如 100,10 你就差 99,12 呵呵,这样看看前后是否有结果.

  10. 前台还是要通过其他手段来处理, 比如 lucene/Solr+mysql 结合返回翻页结果集, 或者上面的分表

  11. 总数可能是存在内存中, 这样分页计算的时候速度很快。累加操作的时候将内存中的值加 1。总数这个值要持久化,还是要存到磁盘上的,也就是数据库中 (可以是关系型数据库,也可以是 mongdb 这样的数据库很适合存储计数)。把总数放在内存中,只是避免频繁的磁盘 i/0 操作 (操作数据库就要涉及到磁盘读写)。

如果你还有更好的建议,请在评论里面告诉我吧。

相关 [大数据 分页] 推荐:

大数据分页方案

- - IT瘾-tuicool
软件开发中,常用要用到分页、计算总数,数据量超过千万、上亿的时候,往往 count的需要超过 1s 的执行时间,甚至 3-5s,对于一个追求性能的前沿团队来说,这个不能忍啊. mysql 会对所有符合的条件做一次扫描. 如果 a=%d 的数据有 1000W 条,那么数据库就会扫描一次 1000W 条数据库.

关于mysql大数据分页的一些方法。

- - CSDN博客编程语言推荐文章
select * from user  limit 0,10;   这种最普通的方法在数据量不大的时候是没问题的. 当数据量大于100W的时候 ,就要 select * from user limit 1000000,10 ;  此时数据库. 要先扫过前面的100W条记录,再来取10条,所以当数据量越来越大的时候,速度也会越来越慢.

谈大数据(2)

- - 人月神话的BLOG
对于大数据,后面会作为一个系列来谈,大数据涉及的方面特别多,包括主数据,数据中心和ODS,SOA,云计算,业务BI等很多方面的内容. 前面看到一个提法,即大数据会让我们更加关注业务方面的内容,而云平台则更多是技术层面的内容. 对于大数据会先把各个理解的关键点谈完了,再系统来看大数据的完整解决方案和体系化.

大数据之惑

- - 互联网分析
算起来,接触大数据、和互联网之外的客户谈大数据也有快2年了. 也该是时候整理下一些感受,和大家分享下我看到的国内大数据应用的一些困惑了. 云和大数据,应该是近几年IT炒的最热的两个话题了. 在我看来,这两者之间的不同就是: 云是做新的瓶,装旧的酒; 大数据是找合适的瓶,酿新的酒. 云说到底是一种基础架构的革命.

白话大数据

- - 互联网分析
这个时代,你在外面混,无论是技术还是产品还是运营还是商务,如果嘴里说不出“大数据”“云存储”“云计算”,真不好意思在同行面前抬头. 是千万级别的用户信息还是动辄XXXTB的数据量. 其实,大数据在我的眼里,不是一门技术,而是一种技能,从数据中去发现价值挖掘价值的技能. ”当我掷地有声用这句话开场时,正好一个妹子推门而入,听到这句话,微微一怔,低头坐下.

交通大数据

- - 人月神话的BLOG
本文简单谈下智慧交通场景下可能出现的大数据需求和具体应用价值. 对于公交线路规划和设计是一个大数据潜在的应用场景,传统的公交线路规划往往需要在前期投入大量的人力进行OD调查和数据收集. 特别是在公交卡普及后可以看到,对于OD流量数据完全可以从公交一卡通中采集到相关的交通流量和流向数据,包括同一张卡每天的行走路线和换乘次数等详细信息.

全球10大数据库

- - 译言-电脑/网络/数码科技
原文: Fiorenttini   译者: julie20098. [非商业性转载必须注明译者julie20098和相关链接. ,否则视为侵权,追究转载责任. 世界气候数据中心:气候全球数据中心, 220TB 的网络数据, 6PB 的其它数据. 国家能源研究科学计算中心,有 2.8PB 容量.

谈大数据分析

- - 人月神话的BLOG
对于数据分析层,我们可以看到,其核心重点是针对海量数据形成一个分布式可弹性伸缩的,高查询性能的,支持标准sql语法的一个ODS库. 我们看到对于Hive,impala,InfoBright更多的都是解决这个层面的问题,即解决数据采集问题,解决采集后数据行列混合存储和压缩的问题,然后形成一个支撑标准sql预防的数据分析库.

大数据的一致性

- - 阳振坤的博客
看到了一篇关于数据一致性的文章:下一代NoSQL:最终一致性的末日. (  http://www.csdn.net/article/2013-11-07/2817420 ),其中说到: 相比关系型数据库,NoSQL解决方案提供了shared-nothing、容错和可扩展的分布式架构等特性,同时也放弃了关系型数据库的强数据一致性和隔离性,美其名曰:“最终一致性”.

大数据Lambda架构

- - CSDN博客云计算推荐文章
1 Lambda架构介绍.          Lambda架构划分为三层,分别是批处理层,服务层,和加速层. 最终实现的效果,可以使用下面的表达式来说明. 1.1 批处理层(Batch Layer, Apache Hadoop).          批处理层主用由Hadoop来实现,负责数据的存储和产生任意的视图数据.