当数据量达到百万级别的时候,分页该如何处理?

简介: 当数据量达到百万级别的时候,分页该如何处理?

最近遇到了这么一个情况,数据库里面的数据由于长期的堆积,导致数据量不断的上升,而后台的系统每次进行分页查询的时候,效率都会降低很多。后来查看了一下之后,发现此时的分页原理主要是采用了传统的物理分页 limit n,m 的方式。


为了方便演示,我特意创建了以下几张表进行实例演练:


表分别是商品表,用户表,用户选购商品记录表:


goods user g_u


三张表的关系比较简单,user的id和goods里面的id合并生成关联数据,存储在了g_u里面。三张数据库表的设计如下所示:


CREATE TABLE `goods` (
  `id` int(11) NOT NULL,
  `name` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
  `price` decimal(6,1) NOT NULL,
  `des` varchar(40) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
  `age` tinyint(3) NOT NULL,
  `sex` tinyint(1) NOT NULL COMMENT '年龄',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
CREATE TABLE `g_u` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `g_id` int(11) NOT NULL COMMENT '商品id',
  `u_id` int(11) NOT NULL COMMENT '用户id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2800001 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
复制代码


这个模拟的应用场景非常简单,用户和商品之间的关系维持在了一对多的关联中。为了方便进行后续的测试,我用jmeter批量创建了1900000条测试数据,模拟一次百万级的数据查询场景。


相应的数据脚本也已经存在百度云中了,需要的同学可以前往下载:


地址:


链接: https://pan.baidu.com/s/1BfddJ8MBtnpeiV84gNmClA 
提取码: 4kmp
复制代码


假设现在需求里面有这样的一个业务场景,需要我们对购买记录表里面的数据进行分页查询,那么对于常规的分页查询操作,常人会想到的方式可能是通过下述的语句:


SELECT * from g_u as gu ORDER BY id limit 1850000,100
复制代码


测试一下发现,查询的时间为:


网络异常,图片无法展示
|


当我们搜索的数据越靠后边的时候,搜索的速度就会越低下,因此这个时候,适当的创建索引就显得比较重要了。


首先我们来做一次explain的sql检测,检测结果为如下所示:


网络异常,图片无法展示
|


由于我们查询的时候,使用的是根据主键索引id进行排序,因此查询的时候key一项为

PRIMARY。


SELECT * FROM g_u WHERE id >=(SELECT id FROM g_u LIMIT 1850000,1) ORDER BY id  LIMIT 100
复制代码


此时查询有了一些许的提升,但是依旧查询缓慢


网络异常,图片无法展示
|


通过explain执行计划分析结果可见:


网络异常,图片无法展示
|


子查询用到了索引,外部查询用到了where的辅助索引


这个时候我们不妨可以试下通过利用主键id来提升我们的查询效率:


SELECT * FROM g_u as gu WHERE gu.id>($firstId+$pageSize*$pageSize)  limit 100
复制代码


查询的时间一下子大大缩短了许多:


网络异常,图片无法展示
|


通过explain分析一下该sql:


网络异常,图片无法展示
|


这里面,sql在运行的时候借助了主键索引的帮助,因此效率大大提升了。


但是这个时候,可能你会有这么一个疑惑。如果说数据的索引不是连续的该如何处理分页时候每页数据的完整性和一致性?


这里不妨可以试试另外的一种思路,通过建立一张第三方的表g_u_index表,将原本乱序的id存储在g_u_index中,在g_u_index一表中,我们可以通过该表有序的g_u_index.id来对应原本相应的无序的g_u.id。建表的sql语句如下所示:


CREATE TABLE `g_u_index` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `index` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_id_index` (`id`,`index`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1900024 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
复制代码


ps: 可以为id和index两者建立一套复合索引,提升查询的效率。


这里我们需要保证一点就是,g_u表中插入的数据顺序需要和g_u_index表中插入的顺序是一致的。然后查询分页指定的index时候可以这么来查:


SELECT g_u_index.index FROM g_u_index  WHERE id=($firstId+$pageSize*$pageSize)  limit 1
复制代码


通过执行explain分析后,结果变成如下所示:


网络异常,图片无法展示
|


查询时间为:0.001s


网络异常,图片无法展示
|


有了第三方表的帮助下,此时分页的sql优化可以调整为以下这种方式:


SELECT * FROM g_u as gu where gu.id>(
SELECT g_u_index.index FROM g_u_index  WHERE id=($firstId+$pageSize*$pageSize) limit 1
) limit 100
复制代码


通过构建了第三方表之后,数据的查询时间一下子大大缩减了:


网络异常,图片无法展示
|


查询的时候为了更加人性化,通常不需要显示这些无意义的id,需要的是商品名称和用户姓名,假设我们还是只采用最原始的无第三方表的方式进行查询的话,效率会比较底下:


SELECT gu.id,goods.`name`,`user`.username FROM g_u as gu ,goods ,`user` 
where goods.id=gu.g_id AND `user`.id=gu.u_id 
ORDER BY id limit 1500000,1000
复制代码


结果:


网络异常,图片无法展示
|


因此如果借助了第三方表查询的话,sql可以调整成下方这种类型:


SELECT goods.`name`,`user`.username FROM g_u as gu ,goods ,`user` 
where goods.id=gu.g_id AND `user`.id=gu.u_id 
and 
gu.id>=(
SELECT g_u_index.index FROM g_u_index  WHERE id=(9+1000*1900) limit 1
) limit 100
复制代码


查询的时间会大大减少:


网络异常,图片无法展示
|


通过explain执行计划分析之后,结果如下:




在实际的业务场景中,一张原来就有上百万数据的表要做出这样的id拆分,并且同步到第三方表的确实不太容易,这里推荐一种思路,可以借助阿里的中间件canal来实现对于数据库日志的订阅,然后自定义进行数据的同步操作。对于canal的讲解在我的这篇文章中也有讲述: blog.csdn.net/Danny_idea/…


对于sql的优化需要结合实际的业务需求来开展,总的来说,这部分还是需要有一定的实战演练才能变强。


常用的sql优化技巧小结:


1.数据量大的时候,应尽量避免全表扫描,应考虑在 where及 order by 涉及的列上建立索引,建索引可以大大加快数据的检索速度。


2.适当的使用Explain可以对sql进行相应的深入分析。


3.当只要一行数据时使用LIMIT 1。


4.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。


5.不要在 where子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。


6.适当的时候采用覆盖索引可以提高查询的效率。

目录
相关文章
|
8月前
|
存储 缓存 分布式计算
亿级数据如何分钟级别写入缓存?
亿级数据如何分钟级别写入缓存?
61 0
|
2月前
|
NoSQL 关系型数据库 MySQL
百万数据量优化实战
在现代互联网业务中,处理百万级别的数据量是家常便饭。传统的单体数据库架构在面对如此庞大的数据量时,往往显得力不从心。本文将分享一次实际的优化案例,探讨如何利用MySQL和Redis共同实现百万级数据统计的优化。
87 4
|
3月前
|
SQL 数据挖掘 关系型数据库
SQL查询次数大于1的记录:高效技巧与方法
在数据库管理中,经常需要统计某些操作的次数,特别是当需要找出哪些记录或值出现的次数超过一定阈值(如大于1次)时
|
3月前
|
SQL 缓存 分布式计算
C#如何处理上亿级数据的查询效率
C#如何处理上亿级数据的查询效率
47 1
|
缓存 Java easyexcel
如何高效的导出 百万级别的数据量 到 Excel?
如何高效的导出 百万级别的数据量 到 Excel?
248 0
|
8月前
|
SQL 关系型数据库 MySQL
MySQL 百万级数据量分页查询方法及其优化
MySQL 百万级数据量分页查询方法及其优化
264 0
|
存储 关系型数据库 MySQL
MYSQLg高级------批量插入百万级数据量
MYSQLg高级------批量插入百万级数据量
156 0
|
存储 数据采集 分布式计算
如何处理大规模数据量的应用?
如何处理大规模数据量的应用?
171 0
Kam
|
SQL 存储 关系型数据库
|
存储 缓存 数据库
百万QPS系统的缓存实践
标题有些吸引眼球了,但并不浮夸,甚至还会远远超过百万,现在的平均响应时间在1ms内,0.08ms左右 如此高的QPS,如此低的AVG,为什么会有如此效果,关键点可能就在多级缓存上 在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流
684 0
百万QPS系统的缓存实践