分库分表必会:跨库分页查询看此一篇就够了( 二 )


因此方案相比前三个方案理解起来相对复杂点 , 为了方便说明 , 所以先单一DB说起,以下单一DB中保存用户年龄数据,1到30岁,总共30条,如果要查询:
select * from T order by age limit 5 offset 10 那么会返回以下粉色标识数据,即【11-15】,请记住此结果 , 下面会讲解怎么分库查询以下结果 。

分库分表必会:跨库分页查询看此一篇就够了

文章插图
单一DB数据集
把以上所有数据进行拆分打散存放到3个分库中,如下:
分库分表必会:跨库分页查询看此一篇就够了

文章插图
分库数据集
通过上文介绍,在单一DB中查询limit 5 offset 10,返回了【11-15】结果,那如果在以上三个分库全局查询limit 5 offset 10怎么做?
第一步:语句改写将 select * from T order by age limit 5 offset 10 改写为 select * from T order by age limit 5 offset 3 , 并投递给所有的分库,注意,这个 offset 的 3,来自于全局offset的总偏移量 10,除以水平切分数据库个数 3 。
执行select * from T order by age limit 5 offset 3,结果如下(粉色标识数据),为了便于理解用青黄色标识库表前三条数据:
分库分表必会:跨库分页查询看此一篇就够了

文章插图
执行limit 5 offset 3数据集(青黄色表库表前三条数据)
第二步:找到返回数据的最小值
  • 第一个库,5 条数据的 age 最小值是10;
  • 第二个库 , 5 条数据的 age 最小值是 6;
  • 第三个库,5 条数据的 age 最小值是 12;

分库分表必会:跨库分页查询看此一篇就够了

文章插图
标识结果集最小值
故,三页数据中,age最小值来自第二个库,age_min=6,这个过程只需要比较各个分库第一条数据,时间复杂度很低
第三步:查询二次改写第一次改写的SQL语句是select * from T order by age  limit 5 offset 3 第二次要改写成一个between语句,between的起点是age_min,between的终点是原来每个分库各自返回数据的最大值:
  • 第一个分库,第一次返回数据的最大值是22 所以查询改写为select * from T order by age where age between age_min and 22
  • 第二个分库 , 第一次返回数据的最大值是20 所以查询改写为select * from T order by age where age between age_min and 20
  • 第三个分库,第一次返回数据的最大值是25 所以查询改写为select * from T order by age where age between age_min and 25
相对第一次查询 , 第二次查询条件放宽了,故第二次查询会返回比第一次查询结果集更多的数据,假设这三个分库返回的数据如下:
分库分表必会:跨库分页查询看此一篇就够了

文章插图
二次查询结果,(深蓝色表示)
分库一的结果集,比第一次多返回了1条数据,上图中深蓝色记录7
由于age_min来自原来的分库二,所以分库二的返回结果集和第一次查询相同,其实这次查询可以省掉
分库三的结果集,比第一次多返回了3条数据 , 上图中深蓝色记录8,9,10
第四步:找到age_min在全局的offset在每个结果集中虚拟一个age_min记录,找到age_min在全局的offset
分库分表必会:跨库分页查询看此一篇就够了

文章插图
标识全局offset
因为查询语句为 limit 5 offset 3,所以查询结果集中每个分库的第一条数据offset为4;
  • 分库一中,根据第一次查询条件得出的10的offset是4 , 查询又返回了一条数据向前推进一位索引,故虚拟age_min在第一个库的offset是2
  • 分库二没有数据变化所以age_min的offset=4
  • 分库三中 , 根据第一次查询条件得出的12的offset是4,查询又返回了三条数据向前推进三位索引,故虚拟age_min在第三个库的offset是0
因此age_min的全局offset为:2+4+0=6
第五步:查找最终数据既然得到了age_min在全局的offset , 就有了全局视野,根据第二次的结果集 , 就能够得到全局limit 5 offset 10的记录(下图黄色标识数据【11-15】)
分库分表必会:跨库分页查询看此一篇就够了

文章插图
标识最终结果数据(黄色表示)