美文网首页
分库分表的排序问题

分库分表的排序问题

作者: Tomy_Jx_Li | 来源:发表于2019-01-18 22:10 被阅读17次

问题:分库分表之后,不免需要进行一些查询的排序操作。如果排序的键是分片字段,那么这个排序的动作比较简单,只需要定位到分片的表,然后进行排序就可以。但是如果不是分片字段的话,就有问题了。

传统的思路

传统的实现非分片字段的排序,就是将各表的结果集进行合并,然后再次排序。实现思路如下图:


获取第一页数据

但是呢,第一页比较好取,性能也可以。但是后面的第二页,第三页的话就需要进行更大范围的排序了。到第n页,每个表的排序记录数就需要达到n*pageSize数了。所以分页越是到后期,性能越差了。
如下图,就是获取第十页数据:


获取第十页数据

改进1(排序字段唯一)

思考之后改进思路如下:

  • 首先第一页的查询不变
  • 第二页及以后的查询,需要传入上一页排序字段的最后一个值,及排序方式。
  • 根据排序方式,及这个值进行查询。如排序字段date,上一页最后值为3,排序方式降序。查询的时候sql为select ... from table where date < 3 order by date desc limit 0,10。这样再讲几个表的结果合并排序即可。

改进2(排序字段不唯一)

  • 第一步不变
  • 第二步在传入上一个的基础上,还需要传入能确定该行记录的唯一性字段
  • sql需要进行修改为select ... from table where date = 3 order by date desc union select ... from table where date < 3 order by date desc limit 0,10。然后将结果合并之后排序。根据唯一性字段确定上一页最后一条记录,然后找出下面的分页记录。

验证

创建三张月表(即分表键是create_date),及一张全库表(这个主要是为了查看分页效果是否正确)。
三张月表及数据:

CREATE TABLE `student_2018_11` (
  `id` int(11) NOT NULL AUTO_INCREMENT,#主键
  `name` varchar(255) DEFAULT NULL,#姓名
  `xueke` varchar(10) DEFAULT NULL,#学科
  `fenshu` int(11) DEFAULT NULL,#分数
  `create_date` datetime DEFAULT CURRENT_TIMESTAMP,#创建时间
  PRIMARY KEY (`id`),
  KEY `idx_fenshu` (`fenshu`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `student_2018_12` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `xueke` varchar(10) DEFAULT NULL,
  `fenshu` int(11) DEFAULT NULL,
  `create_date` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_fenshu` (`fenshu`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `student_2019_01` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `xueke` varchar(10) DEFAULT NULL,
  `fenshu` int(11) DEFAULT NULL,
  `create_date` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_fenshu` (`fenshu`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `xueke` varchar(10) DEFAULT NULL,
  `fenshu` int(11) DEFAULT NULL,
  `create_date` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_fenshu` (`fenshu`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
//数据
INSERT INTO test.student_2018_11 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx@jj.com","phone":"182"}', 'gaoshu1', 20, '2018-11-17 03:18:52');
INSERT INTO test.student_2018_11 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx1@jj.com","phone":"1821"}', 'gaoshu2', 26, '2018-11-17 03:18:52');
INSERT INTO test.student_2018_11 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx@jj.com","phone":"182"}', 'gaoshu3', 33, '2018-11-17 03:18:52');
INSERT INTO test.student_2018_11 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx1@jj.com","phone":"1821"}', 'gaoshu4', 43, '2018-11-17 03:18:52');
INSERT INTO test.student_2018_11 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx@jj.com","phone":"182"}', 'gaoshu5', 53, '2018-11-17 03:19:08');
INSERT INTO test.student_2018_11 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx1@jj.com","phone":"1821"}', 'gaoshu6', 64, '2018-11-17 03:19:08');
INSERT INTO test.student_2018_11 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx1@jj.com","phone":"1821"}', 'gaoshu7', 88, '2018-11-17 03:19:08');
INSERT INTO test.student_2018_11 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx1@jj.com","phone":"1821"}', 'gaoshu8', 70, '2018-11-17 03:19:08');
INSERT INTO test.student_2018_12 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx@jj.com","phone":"182"}', 'xiaohei1', 30, '2018-12-17 03:18:59');
INSERT INTO test.student_2018_12 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx1@jj.com","phone":"1821"}', 'xiaohei2', 31, '2018-12-17 03:18:59');
INSERT INTO test.student_2018_12 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx@jj.com","phone":"182"}', 'xiaohei3', 43, '2018-12-17 03:18:59');
INSERT INTO test.student_2018_12 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx1@jj.com","phone":"1821"}', 'xiaohei4', 79, '2018-12-17 03:18:59');
INSERT INTO test.student_2019_01 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx@jj.com","phone":"182"}', 'wodetian1', 25, '2019-01-17 03:19:08');
INSERT INTO test.student_2019_01 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx1@jj.com","phone":"1821"}', 'wodetian2', 34, '2019-01-17 03:19:08');
INSERT INTO test.student_2019_01 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx@jj.com","phone":"182"}', 'wodetian3', 58, '2019-01-17 03:19:08');
INSERT INTO test.student_2019_01 (name, xueke, fenshu, create_date) VALUES ('{"email":"jiyx1@jj.com","phone":"1821"}', 'wodetian4', 59, '2019-01-17 03:19:08');
// 月表数据导入冗余表
insert into student(name, xueke, fenshu, create_date) select name,xueke,fenshu,create_date from student_2018_11;
insert into student(name, xueke, fenshu, create_date) select name,xueke,fenshu,create_date from student_2018_12;
insert into student(name, xueke, fenshu, create_date) select name,xueke,fenshu,create_date from student_2019_01;

验证改进1

分页验证前提:每页展示2条信息。前三页是以排序字段唯一进行验证。以分数进行排序。
第一页:

第一页分页sql
结果是否一致:这里因为都是一个sql,所以结果没得说,相同。
第二页: 第二页分页sql
结果是否一致:一致。
第三页: 第三页分页sql
结果是否一致:一致。
改进1验证完毕,结果合格,还需要进行大数据量的性能验证。通过上述的三幅图可以看出,除了第一页的sql外,其他两页改进的sql更加不易出错。因为传统型需要进行4次修改,而改进后的只需要三次修改。

验证改进2

验证前提:每页展示2条信息。以分数进行排序。

改进2后的第五页分页sql
结果: 改进2后的第五页分页结果
实际期望的结果: 实际期望结果1
实际期望结果2
这里有两种期望结果,是因为不管是那种排序规则,只要保证在规则内,也就是在期望结果1中一直保持这种排序结果,就是合理的。但是大的情况,可能是两种甚至是多重排序结果,因为不同的程序对除了排序字段外的字段的排序处理不一样。不过只要保证在相同规则下每次的排序结果一致即可。
通过以上对比,发现直接使用sql较难实现预期的分页效果(个人sql能力有限,技术流泪)。所以我这里只是进行查询,并将查询结果返回客户端程序。然后客户端进行分页处理。
也就是只执行查询sql,不进行分页:
查询sql展示
然后客户端根据传入的唯一确定一行记录的字段,确定上次分页最后的一行记录,然后向下提取pageSize个记录返回给调用方即可。
因为一般的分页的每页展示记录,都不会很大,最多100条消息。所以执行的select * from student_2019_01 where fenshu < 43 order by fenshu desc limit 0, 2查找的记录不会很多。但是如果非唯一字段的区分度很低,导致了select * from student_2018_12 where fenshu = 43查询出来的记录很多的话,也会让应用系统内存吃紧。

改进3(对非唯一字段进行再次改进)

前提:分布式或者其他情况下,总是有一个唯一id或者其他字段的。那么可以根据这个唯一字段进行分页。这里的做法如下图:


改进后的sql

这样就可以直接在数据库完成分页了。
至此:分页效果已经达到。但是性能方面还未进行测试。

相关文章

  • 面试必备:我们为什么要分库分表?

    目录 什么是分库分表 为什么需要分库分表呢 如何分库分表 什么时候开始考虑分库分表 分库分表会导致哪些问题 分库分...

  • 分库分表中间件华山论剑

    分库分表中间件 分库分表的缺点: 引入分布式事务的问题; 跨节点 Join 的问题; 跨节点合并排序分页等聚合类S...

  • 分库分表的排序问题

    问题:分库分表之后,不免需要进行一些查询的排序操作。如果排序的键是分片字段,那么这个排序的动作比较简单,只需要定位...

  • 分库分表

    分库分表 为什么要分库分表? 超大容量问题 性能问题 如何去做到 垂直切分①垂直分库; 解决的是表过多的问题②垂直...

  • 架构师进阶实战随堂笔记十

    场景十:分布式数据管理问题 分布式事务和分库分表方法介绍与实践经验分享 目录 分库分表 为什么分片 分库分表策略 ...

  • Mysql的分库分表,水平拆分-垂直拆分

    参考文章MySQL分库分表总结参考数据库分库分表策略,如何分库,如何分表?MySQL分库分表原理 MySQL单库数...

  • 数据库小结

    常见问题 分库分表 对于海量数据,且有一定的并发量的分库分表,绝不是引入某一个分库分表中间件就能解决问题,而是一项...

  • 分库分表

    【分库、分表】MySQL分库分表方案 - MrSunny - 博客园 总结下Mysql分表分库的策略及应用 - 周...

  • 分库分表

    Reference 分库分表需要考虑的问题及方案

  • 分库分表

    数据库分表可以解决单表海量数据的查询性能问题,分库可以解决单台数据库的并发访问压力问题。 分库分表目前有很多的中间...

网友评论

      本文标题:分库分表的排序问题

      本文链接:https://www.haomeiwen.com/subject/uspodqtx.html