问题:分库分表之后,不免需要进行一些查询的排序操作。如果排序的键是分片字段,那么这个排序的动作比较简单,只需要定位到分片的表,然后进行排序就可以。但是如果不是分片字段的话,就有问题了。
传统的思路
传统的实现非分片字段的排序,就是将各表的结果集进行合并,然后再次排序。实现思路如下图:
获取第一页数据
但是呢,第一页比较好取,性能也可以。但是后面的第二页,第三页的话就需要进行更大范围的排序了。到第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
结果是否一致:一致。
改进1验证完毕,结果合格,还需要进行大数据量的性能验证。通过上述的三幅图可以看出,除了第一页的sql外,其他两页改进的sql更加不易出错。因为传统型需要进行4次修改,而改进后的只需要三次修改。
验证改进2
验证前提:每页展示2条信息。以分数进行排序。
结果: 改进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
这样就可以直接在数据库完成分页了。
至此:分页效果已经达到。但是性能方面还未进行测试。
网友评论