百万级数据,分页如何处理?

(点击便可跳转阅读)java

1. SpringBoot内容聚合面试

2. 面试题内容聚合算法

3. 设计模式内容聚合sql

4. 排序算法内容聚合数据库

5. 多线程内容聚合设计模式

正文:bash

最近遇到了这么一个状况,数据库里面的数据因为长期的堆积,致使数据量不断的上升,然后台的系统每次进行分页查询的时候,效率都会下降不少。后来查看了一下以后,发现此时的分页原理主要是采用了传统的物理分页 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的讲解在个人这篇文章中也有讲述: 阿里Canal框架(数据同步中间件)初步实践

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

经常使用的sql优化技巧小结:

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

2.适当的使用Explain能够对sql进行相应的深刻分析。

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

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

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

6.适当的时候采用覆盖索引能够提升查询的效率。

相关文章
相关标签/搜索