分库后如何分页

前言

在实际应用中, 为了降低单表的数据量, 会对较大的表进行水平切分, 将单表的数据切分到多表多库中.

既然要切分, 就要有一个切分的依据, 比如说按照 ID 取模等. 那么多张表联合分页是如何做到的呢?

如果分表的依据是字段 A, 但是需要根据字段 B 进行分页查询, 针对这种情况应该如何处理呢?

为了后面方便说明, 这里举个例子.

有一个文章表 user_article

其中有一个文章的发表时间 publish_date. 这个时间用户是可以修改的.

按照 ID 取模分到了两个表中.

user_article_1 user_article_0

现在有这样一个需求:

按照文章的发表时间进行排序分页

单表

先来看在单表的时候, 我们是如何查询的, 之后再扩展到多表.

select * from `user_article` order by `publish_date` offset 0 limit 10;

单表查询很简单, 一条 sql 搞定.

多表

对于多表的情况, 将其抽象一下, 就是有两个有序列表:

[1, 5, 7, 8, 9]
[2, 3, 4, 6, 10]

现在要取合并后的有序列表第 n 页的数据.

方案一

想到的第一个方案, 将两个列表合并之后, 就可以退化为单列表的情况了.

对应到 sql查询中, 如果要取第三页的数据, 可以肯定的是, 每个列表都不会读到第四页, 所以我们可以将每个表的前三页拿出来, 在内存中进行合并后, 就可以拿到全局的第三页了.

# 取第三页的数据
select * from `user_article_0` order by `publish_date` offset 0 limit 3*10;
select * from `user_article_1` order by `publish_date` offset 0 limit 3*10;

这种方案确实可以获取到分页的数据, 但是查询的数据量随着页数的增大而增大. 如果查询第200页的数据, 那每张表都要返回2000条数据, 性能太低.

方案二

如果说, 我们按照页数依次获取, 中间不跳页的话, 那么就可以将上一次查询的终点保存下来, 供下一次查询使用.

比如, 上一次查询, 最后一条数据是8, 那么, 下一次查询从各个列表中取出大于8的10条数据, 内存排序后取前10条, 同时将最后一条的值存下来供下一次查询使用.

对应到sql查询中, 就需要有一个全局的searchId.

select * from `user_article_0` where `publish_date` > 'searchId' order by `publish_date` limit 10;
select * from `user_article_1` where `publish_date` > 'searchId' order by `publish_date` limit 10;

每页查询数量固定, 效率较高. 但同时, 这种功能方案不能做到跳页, 如果要查询第 n 页的数据, 前提是查询了 n-1页. 同时, 此方案要求 publish_date字段无重复值, 如果有20条相同的publish_date, 在翻页的时候就会丢失部分数据.

方案三

那么, 有没有一种方案, 即能跳页查询, 查询数量也能保持在常量级呢?

说明

来了, 为了方便说明, 先从数组开始:

[1, 3, 5, 7, 11, 18, 23, 32, 41]
[2, 8, 9, 15, 17, 22, 27, 51, 60]

此时, 如果想获取全局: offset 4 limit 4 的数据. 因为我们不知道全局偏移量4在各个数组中的各自偏移量. 所以在方案一中需要进行大量的查询, 如果我们知道了, 问题不就解决了么.

第一步, 分别取各列表半个偏移量的数据

先分别取各个数据中offset 2 limit 4的数据. 结果如下:

[5, 7, 11, 18]
[9, 15, 17, 22]

注意: 这里的offset 2, 是通过全局偏移量/表个数算出来的.

拿到这个数据之后, 我们得到了什么? 其中的最小值5, 全局偏移量必定>=2.

  • 如果数据1中小于5的值大于2个, 那么第一次查询时结果必定不同
  • 如果数据2中存在小于5的值, 那么5的全局偏移量只会增加.

第二步, 获取最小值的全局偏移量

通过第一步的分析, 如果我们能够知道数据2中存在多少个小于5的值, 那么我们就能够计算出5的全局偏移量. 进而得到全局偏移量为4的数据.

查询数据2中, data > 5 and data < 9的数据, 结果如下:

[8]

前面, 我们知道9数据2中的偏移量为2. 同时 数据2中, >5 and <9的数据, 个数为1, 计算可得, 数据2中小于5的数据个数为: 2-1=1

又因, 5数据1中的偏移量为2, 进而可得, 5的全局偏移量为: 2+1=3. (加上数据2中小于5的数量)

第三步, 整合数据并返回

我们将前后查询的结果整合一下, 得到如下数据:

[5, 7, 11, 18]
[8, 9, 15, 17, 22]

再将两数组合并为一个数组:

[5, 7, 8, 9, 11, 15, 17, 18, 22]

已知, 5的全局偏移量为3, 则偏移量为4的数据为7. 我们从7开始, 向后拿4个, 就是全局的offset 4 limit 4的数据了.

[7, 8, 9, 11]

问题

到这里, 你以为已经完成了么? 看下面这组数据:

[1, 2, 3, 4, 5, 6, 7, 8]

[9, 10, 11, 12, 13, 14, 15, 16]

很明显, 这组数据分布十分不均匀, 按照上面的操作获取分页数据offset 4 limit 4

第一步折半查询结果offset 2 limit 4

[3, 4, 5, 6]

[11, 12, 13, 14]

然后拿到3的全局偏移量2. 得到偏移量4的数据为5. 组合后返回结果为:

[5, 6, 9, 10]

这, 明眼人一看, 就知道结果应该是[5, 6, 7, 8].

很明显, 因为数据都在一张表上, 所以导致第一次获取数据没有取完. 但是, 到这一步, 我们获取到的偏移量是没有问题的.

也就是说, 全局偏移量为4的数据为5. 那么, 我们就可以退回到方案二 进行处理了.

当然, 如果对数据的精度要求没有那么高, 或者确信数据分布不会出现这种极限情况, 可以忽略.

貌似网上将这种方法称为二次查询, 没有找到文章提到这个问题, 难道说实际应用中不会遇到么?

sql

将上面方案转为 sql, 取第4页的数据, 既offset 30 limit 10

第一步, 分别取各表数据

select * from `user_article_0` order by `publish_date` offset 30/2 limit 10;
select * from `user_article_1` order by `publish_date` offset 30/2 limit 10;

我们在这一步, 统计出查询结果的最小值 M

第二步, 最小值全局偏移量

下方sql中的 M', 代表各个查询结果的最小值.

select * from `user_article_0` where `publish_date`>M and `publish_date`<"M'" order by `publish_date`;
select * from `user_article_1` where `publish_date`>M and `publish_date`<"M'" order by `publish_date`;

此时, 通过计算, 我们已经得到M的全局偏移量了. 同时, 也拿到了偏移量30的值S.

如果数据分布十分不均匀, 在这一步, 极端情况会将前面所有数据都拿出来.

第三步, 返回结果

如果确信不会出现前面提到的极限情况, 这里直接组合结果并返回即可.

否则, 继续执行下面查询并返回. 此时, 排序字段不可重复.

select * from user_article_0 where publish_date > S order by publish_date limit 10;
select * from user_article_1 where publish_date > S order by publish_date limit 10;

此方案执行了三倍的数据库查询, 但优点是查询效率恒定与页数无关, 且支持跳页.

方案四

因为我们的数据是按照 ID 取模, 根据概率分布, 两个库的数据分布应该是一样的. 那是不是可以每个表取半页数据, 拿回来拼上.

这样的话, 取第3页数据的 sql为:

select * from user_article_0 order by publish_date offset 20/2 limit 5;
select * from user_article_1 order by publish_date offset 20/2 limit 5;

这样的话, 数据不太准确, 拿到的数据顺序可能不对, 但重点是查询效率高啊. 应该是有对顺序精度没什么要求的场景吧. 想到了这种方案, 但是暂时没有想到应用场景.

如果是针对分表字段排序的话, 那么数据分布均匀, 此方案完美.

最后

具体业务应该如何选择分页方式呢?

  • 如果不需要跳页, 直接选择方案二
  • 如果对顺序精度没什么要求, 直接选择方案四
  • 如果只需要查询前 n 页数据, 且 n 比较小. 那么方案一 可能更适合你
  • 如果需要查询所有数据, 且需要跳页, 可以选择 方案一 和 方案三 结合的方式. 优先选择当前效率更高的.
    • 对于访问频率较高的前几页数据, 选择 方案一
    • 对于页数比较大的数据, 选择 方案三

当然了, 前面说的情况, 排序字段与分表字段不同, 数据分布可能不均匀. 如果是相同的字段, 那就没这么多事了, 数据都是均匀分布的, 参考 方案四

最后, 对于排序使用的字段, 最好能够保证其唯一性, 如果不能, order by的时候, 请添加辅助字段排序.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/508376.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机是如何进行时间同步的

WHY 在网络世界中, 各个计算机之间要想协同工作, 时间同步是一个十分重要的基础. 在计算机内部是有自己的时间的, 这个时间通过内部的晶体振荡器差生的固定频率, 来模拟时间流逝进行计算. 虽然频率十分稳定, 但也是有误差的, 虽然现在的工艺水平误差已经十分小了. (关于震荡的…

WordPress架构简单剖析

前言 最近在搭建自己的博客站点时, 选择了网站使用较多的WordPress, 随着慢慢的使用, 它灵活的插件和主题令我折服. 基本上任何想要实现的功能, 都可以在上面通过插件的形式进行添加. 无论是在访问前的缓存、访问后的统计、访问中的过滤、各种流程的修改等等, 几乎都能够以插件…

Gale-Shapley算法

前言 最近看了一档综艺《心动的信号》(唉, 单身久了, 开始喜欢看别人谈恋爱了) 节目中共有n男n女, 他们会在节目的最后进行表白, 如果我喜欢你, 恰好你也喜欢我, 那么便就会在一起, 自此传为一段佳话. 于是, 我就在想, 如何用算法来实现这个匹配的过程呢? 单一匹配 将信息…

阿里云定时任务并自动释放

前言 最近写了一个爬虫脚本, 脚本跑在一台北京的 ecs 上. 但奈何因某种未知力量, 需要连接代理才能访问目标网站. 本来想着自己搭代理, 但是太贵了, 就暂时搁置了. 直到我发现了这个: 阿里云香港的服务器, 一个小时才5分钱. 如果脚本直接跑在香港服务器上不就可以了咩, 按照这…

智能优化算法应用:基于金豺算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于金豺算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于金豺算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.金豺算法4.实验参数设定5.算法结果6.参考文献7.MA…

pixiv小控件

前言 最近看到一个大佬, 开源了一款博客小插件, 地址. 可以将pixiv网站的日榜放到博客侧边栏. 看上去很炫酷. 于是我也引入到了自己的博客中. 在此向大佬表示感谢. 但是在使用过程中, 经常遇到访问很慢的情况, 查看之后才发现, 大佬的服务器架设在韩国, 难怪访问比较慢, 都走…

PHP-PDO参数绑定问题

前言 今天在执行这样一段代码: $data [username > hujingnb,address > beijing, ]; $dbh new PDO("mysql:host{$host};dbname{$dbname}", $username, $password); $statement $dbh->prepare(INSERT INTO test_user (username, address) VALUES (:usern…

Python 的协程

前言 最近在看部分Python源码时, 发现了async 这个关键字. 查了一下发现了Python中的协程. 协程这玩意, 在GO中我用过啊, 简单说, 就是一个轻量级的线程嘛, 由语言自己来实现不同协程的调度. 想着Python中可能也是差不多的东西吧. 但是我Google搜了一下, 前面的说明都给出了下…

PHP脚本调用命令获取实时输出

在写脚本的时候, 经常会有需要调用其他命令. 而在调用一些耗时命令的时候, 我们是需要能够实时掌握脚本进度的. 一般来说, 脚本的进度通常是通过脚本的输出来获得. 如果是一个bash脚本, 那么直接调用命令 A就可以将执行权交出去, 然后命令 A的输出就可以实时显示出来了. 如果…

如何使用git管理crontab任务

前言 在Linux系统上执行定时任务, 使用crontab还是很方便的(有关crontab的使用可看crontab指令笔记). 只需要一行命令就完成了. 但是, 美中不足的是, crontab通过命令行管理任务, 无法通过代码库对任务进行管理. 若要更换机器, 所有任务都要重新增加一遍. 更糟的是若服务器突…

Golang 反射操作整理

前言 反射是什么? 我们平常也是经常用到, 而且这名词都用烂了, 这里就不再详细介绍了. 简单说, 就是有一个不知道是什么类型的变量, 通过反射可以获取其类型, 并可操作属性和方法. 反射的用途一般是用作生成工具方法, 比如你需要一个ToString方法, 要将变量转为字符串类型,…

Wordpress不同页面显示不同小工具

问题 想做一个在右侧显示的文章目录, 使用文章目录的插件 Easy Table of Contents, 将其添加到右侧的侧边栏中, 很轻松做到了这点. 但是, 一个新的问题出现了. 这个目录的工具, 需要在文章页面显示, 而在其他页面不显示. 那么问题来了, 如何让不同的页面显示不同的侧边栏工具…

虚拟内存分页机制的地址映射

概述 在之前的文章虚拟内存对分页机制做了简单的介绍. 还有一个疑问, 那就是如何将虚存中的逻辑地址映射为物理地址呢? 今天就来简单分析一下. 对于一个分页的地址来说, 一般包含两个元素: 页号: 第几页偏移量: 当前页的第几个字节 以下以 addr_virtual(p, o)表示一个逻辑…

虚拟内存分页机制的页面置换

前言 之前简单介绍过虚拟内存是如何与物理内存进行地址映射的: 虚拟内存分页机制的地址映射, 但是仅仅地址映射是不够的, 在地址映射说过会有缺页的情况, 此时就需要操作操作系统将缺少的页加载到内存中. 但是, 如果内存满了怎么办呢? 毕竟虚拟内存一般都要大于物理内存的, 不…

Kubernetes各个组件的概念

前言 Kubernetes中的概念太多了, 什么Pod Service Deployment 等等等等, 给刚接触的我都整蒙了. 通过几天观察下来, 说一下我对各个组件的理解. 此文章仅仅对这些概念做一个简单的介绍, 不至于后面看其他文章的时候一头雾水. Node Node很好理解. 就是服务实际运行的实例, 可…

Kubernetes中Pod生命周期

在 Kubernetes中Pod是容器管理的最小单位, 有着各种各样的Pod管理器. 那么一个Pod从启动到释放, 在这期间经历了哪些过程呢? Pod自开始创建, 到正常运行, 再到释放, 其时间跨度及经历的阶段大致如下: 说一下各个阶段的作用以及是为了解决什么问题. 容器调度和下载镜像的过程就…

Kubernetes存储卷的使用

在Kubernetes中, 有这不同方式的内容挂载, 简单记录一下他们的配置方式. ConfigMap 配置内容 内容配置 apiVersion: v1 kind: ConfigMap metadata:name: test-config data: # 添加配置的 key-value 内容test-key: test-value引入 apiVersion: v1 kind: Pod spec: containe…

响应HTTP服务的shell脚本

以下内容均为第一版, 实际使用请查看最新信息, 转至: https://hujingnb.com/archives/729 前言 兄弟萌, 我实现了一个实用的小工具, 特来分享. 事情刚开始是这样的, 我需要一个脚本来实现代码仓库web hook的任务, 首先想到的是直接调用php, 但是php-fpm是以www-data用户运行…

wait函数的作用

前言 在编写C程序的时候, 通过fork函数来创建新的进程, wait函数来等待子进程结束. 那么就有一个问题了, 什么情况下父进程需要等待子进程结束后继续执行呢? 如果需要等待子进程结束, 那直接将操作放到父进程执行不就醒了么? 反正等着也是等着. 当然, 还有有一种情况, 任务…

OAuth1.0介绍

背景 为什么需要OAuth授权呢? 最典型的应用场景就是第三方登录了, 我们开发了一个网站希望用户可以QQ登录, 但是怎么能拿到用户的 QQ 信息呢? 用户将 账号密码告诉我们当然可以, 但是这样有如下隐患: 我们拿到了用户的密码, 这样很不安全. 而且任意一个应用被黑, 所有相关…