2018-01-08 分布式数据库分页方案

分布式数据库的分页方案

本篇文章介绍了在 DRDS 上做时间序数据分页展示的一种方法,初看简单,实则细节较多,需要一定的关系型数据库基础,也是我们用户在实际使用产品过程中碰到的问题之一,这里分享给大家。##

来实现一个分页吧

日常业务需求中,按时间序对数据进行分页展示是一个非常普遍的需求,单机关系型数据库有比较标准化的解决方案,但是这个需求在分布式数据库中实现,粗看比较简单,实际上却相当复杂,如果要做好,需要有比较扎实的关系型数据库认知。

这个问题分为两个部分:</br>

  1. 功能性要求:
      a.数据需按照时间顺序进行返回
      b.所有数据均需要被遍历到,不能有遗漏
      c.每页返回100条数据
      d.同一个时间精度内(例如同一个毫秒内),会有多条数据同时被插入
  2. 性能要求:
      a.性能不能随页码的增加而衰减,需要高效并且恒定
      b.性能不能随着数据量的增加而衰减,例如10W条数据的时候很高效,100亿条数据的时候也很高效
单机数据库的做法

  对于单机数据库而言,以MySQL 为例,功能部分,直接按照自增主键从小到大排列即可,因为自增主键的大小能够区分出数据生成的前后关系,所以功能上不存在问题。

  性能部分优化,在 SQL带其他过滤条件的情况下,可以将扁平的带 LIMIT m,n 语句优化成嵌套子查询以便让优化器做索引覆盖,避免在磁盘上遍历数据,SQL 如下所示:

SELECT *
    FROM table a JOIN(
    SELECT pk FROMtable
    WHERE some_column= ?
    ORDER BY pk LIMIT m, n) b ON a.pk= b.pk

在分布式数据库中实现的难点

  对于单机数据库来说,因为存在时间序的自增主键,这个需求变得比较简单,但是对于分布式数据库而言,这个问题就变得比较复杂,主要因为主键大小和数据生成时间并没有本质关联,即使是分布式强一致数据库,保证严格时间序代价也很高,这个导致只能通过类似 GMT_CREATE 这种时间字段进行排序分页,但是 GMT_CREATE 可能重复,或者存在大量重复,这个导致分页按时间排序处理变得更加复杂。

  另外性能层面,我们如同MySQL 不能使用扁平的带LIMIT m,n语句进行分页处理,但是同时也不能优化成带子查询的分页语句,因为数据分片的原因,需要将 LIMIT m,n优化成 LIMIT 0,m+n,分页挪到非常大的时候,需要返回到 DRDS 大量数据,再 skip掉不必要的数据,即使做了倒序优化,一张拆分表性能最差的一页数据查询需要返回表中一半的数据才能满足需求,这个在拆分表有10亿或者100亿数据的时候,很难满足性能需求。

DRDS的方案

  对这个问题仔细分析后,我们提出了一个方案。为了避免返回大量的中间结果数据,我们希望不指定 LIMIT 的 OFFSET,而是用上一页的最大值直接跳转到下一页的起始处,因此业务做一定的妥协,功能上增加如下约束:
  1. 提供下一页、上一页、首页、尾页功能
  2. 可以在当前页相邻的几页进行跳转(例如前后10页)
  3. 不允许做任意页的跳转
对于单机数据库,我们可以使用如下的 SQL 实现上述的需求:
SELECT * FROM table WHERE pk> 上一页pk最大值 ORDRE BY pk LIMIT n

分布式数据库:
  1. 增加 GMT_CREATE 字段,其默认值为CURRENT_TIMESTAMP

  2. 我们仍然按照 GMT_CREATE 时间字段进行第一个维度排序,但是 GMT_CREATE 有重复,我们希望有一个固定顺序,所以再以主键为第二个维度排序,所以排序部分为 ORDER BY gmt_create,pk

  3. 我们记住每一页 GMT_CREATEPK 的最大值作为下一页数据的起始值,但是前面提到过 GMT_CREATE 有数据重复,如果通过简单AND条件拼接,会导致漏数据,所以我们将条件写为:

    gmt_create>= ? AND(gmt_create> ?or pk> ?)

从查询逻辑上规避掉GMT_CREATE 重复带来的可能漏数据的状况,分页 SQL 变成了这个:

SELECT *
        FROM page_test
        WHERE gmt_create>= ?
        AND(gmt_create> ?
        OR pk> ?)
        ORDER BY gmt_create,
        pk LIMIT n

当然在MySQL 5.7上,我们还可以直接做二元组的比较(5.7之前的版本,多元组的比较 MySQL 无法利用到组合索引):

SELECT *
        FROM page_test
        WHERE(gmt_create, pk)> (?, ?)ORDER BY gmt_create, pk LIMIT n

结果验证和总结
如下图page_test3表,数据量为36亿多条,表结构如图所示,总共拆了96张表,4个RDS存储数据。

其中数据起始值如下:
如果我们挑选数据集中间的值,从下图看性能也很好,所以只要按照这套方案做分布式数据库分页或者全量扫描数据,性能将不会劣化,可以严格按照时间序排列,并且不会扫漏已经存在于数据库中的数据。

代码

package com.taobao.tddl.sample;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import com.alibaba.druid.pool.DruidDataSource;

public class PageSample {

        public static void main(String[] args) throws Exception {
    
    DruidDataSource ds = new DruidDataSource();
    ds.setUrl(
    "jdbc:mysql://drdsxxxx.drds.aliyuncs.com:3306/dbname?characterEncoding=utf8&rewriteBatchedStatements=true&clobberStreamingResults=true&allowMultiQueries=true");
    ds.setUsername("user");
    ds.setPassword("password");
    ds.init();
    int index = 0;
    boolean first = true;
    Object maxGmtCreate = null;
    long maxId = -1;
    while (true) {
    Connection conn = null;
    try {
    conn = ds.getConnection();
    PreparedStatement ps = null;
    if (first) {
    ps = conn.prepareStatement("SELECT * FROM page_test  order by gmt_create,id limit 99");
    
    first = false;
    } else {
    ps = conn.prepareStatement(
    "SELECT * FROM page_test where gmt_create >= ? and (gmt_create > ? or id > ?) order by gmt_create,id limit 99");
    ps.setObject(1, maxGmtCreate);
    ps.setObject(2, maxGmtCreate);
    ps.setLong(3, maxId);
    
    }
    ResultSet rs = ps.executeQuery();
    maxGmtCreate = null;
    maxId = -1;
    while (rs.next()) {
    System.out.println((++index) + " " + rs.getInt("id") + " " + rs.getString("gmt_Create"));
    maxGmtCreate = rs.getObject("gmt_create");
    maxId = rs.getLong("id");
    }
    if (maxId == -1) {
    break;
    }
    } finally {
    conn.close();
    }
    
    }
    
    }
}
转自 阿里巴巴数据库技术 公众号
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342