分库分表的排序问题

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

文章中的问题

过了一年,再看这篇文章,感觉自己的思路有些问题,这里先指正出来。
存在问题:
其实分库分表后的分页,不需要区分字段是否唯一,分库分表的分页类似于归并排序算法,或者mapReduce。从每个库中取出最小的,或者最大的N条记录,然后进行最大最小比较即可。不需要像下文中非唯一字段那样,还需要union。


以下正片开始


传统的思路

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


获取第一页数据

但是呢,第一页比较好取,性能也可以。但是后面的第二页,第三页的话就需要进行更大范围的排序了。到第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。然后将结果合并之后排序。根据唯一性字段确定上一页最后一条记录,然后找出下面的分页记录。这里为何不用select ... from table where date <= 3 order by date desc limit 0,10呢,因为这样只能取到10条记录,如果某台节点上的满足等于3的节点为11条,那么就会漏掉一条数据,导致查询结果不正确

验证

创建三张月表(即分表键是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中一直保持这种排序结果,就是合理的。但是大的情况,可能是两种甚至是多重排序结果,因为不同的程序对除了排序字段外的字段的排序处理不一样。不过,还是要尽量保证查询结果的每次结果一致,可以在date一致时,使用其他字段再次进行排序。但这个又需要进行循环判断,很明显方法不太高明。或者在排序时,默认加上一个全局唯一键。
通过以上对比,发现直接使用sql较难实现预期的分页效果(个人sql能力有限,技术流泪)。所以我这里只是进行查询,并将查询结果返回客户端程序。然后客户端进行分页处理。
也就是只执行查询sql,不进行分页:
查询sql展示

然后客户端根据传入的唯一确定一行记录的字段,确定上次分页最后的一行记录,然后向下提取pageSize个记录返回给调用方即可。
因为一般的分页的每页展示记录,都不会很大,最多100条消息。所以执行的select * from student_2019_01 where fenshu < 43 order by fenshu desc limit 0, 100查找的记录不会很多。但是如果非唯一字段的区分度很低,导致了select * from student_2018_12 where fenshu = 43查询出来的记录很多的话,也可能会让应用系统内存吃紧。

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

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


改进后的sql

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 转 # https://www.cnblogs.com/easypass/archive/2010/12/ 08/...
    吕品㗊阅读 9,723评论 0 44
  • 原文:https://my.oschina.net/liuyuantao/blog/751438 查询集API 参...
    阳光小镇少爷阅读 3,822评论 0 8
  • MYSQL 基础知识 1 MySQL数据库概要 2 简单MySQL环境 3 数据的存储和获取 4 MySQL基本操...
    Kingtester阅读 7,809评论 5 116
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,928评论 2 89
  • 算是为我的失恋日记划伤句号吧,原来我的感觉都是真的,我真的没有等到你来上海。我有很多话要说,却哽咽着不敢说。 ...
    super逗号阅读 164评论 0 0