dble分页技巧_主键是拆分列_连续翻页

一 场景描述

对于订单、交易流水之类的表,常见是应用层会生成订单号、交易流水号之类的唯一编号,dble则是以这个唯一编号分库分表,而落到MySQL的物理表上,也是直接以这个编号字段作为表的主键。

在本文中,讨论在符合以下所有条件的场景下,查询的分页技巧:

  • dble的拆分列(sharding key)同时也是MySQL物理表的主键
  • 连续翻页
    • 每次查询的页只能是上一次查询的前一页或者后一页
    • 第一次查询必须为首页

1. 表结构

CREATE TABLE many_node_table (
    id CHAR(128) PRIMARY KEY,
    ts TIMESTAMP NOT NULL,
    branchId CHAR(5) NOT NULL,
    departId CHAR(10) NOT NULL,
    opType VARCHAR(20) NOT NULL,
    operator VARCHAR(20) NOT NULL,
    INDEX idx_ts (ts),
    INDEX idx_branchId_departId (branchId, departId)
) COMMENT 'This is an order table'

2. 拆分方式

<!-- schema.xml -->
<schema name="testdb" >
    <table name="many_node_table" rule="hash_by_id" dataNode="dn$0-127" />
</schema>
<!-- rule.xml -->
<tableRule name="hash_by_id">
    <columns>id</column>
    <algorithm>hash_128_datanodes</algorithm>
</tableRule>id

<function name="hash_128_datanodes" class="Hash">
    <property name="length">1</property>
    <property name="count">128</property>
</function>

二 直接翻页

MySQL语法支持LIMIT [start,] length语法来进行翻页,例如:

SELECT *
FROM many_node_table
WHERE ts BETWEEN TIMESTAMP('2019-01-01 00:00:00') AND TIMESTAMP('2019-03-31 23:59:59')
  AND branchId = 'user_specified_bratop Mch'
  Atop MD departId = 'user_specified_department'
ORDER BY id
-- n is the page number
-- M is the page size which means the max records of one page, should not change during the paging
LIMIT (n-1)*M, M

在获取首页(n=1)时,这个SQL的执行计划可优化为“每个MySQL各自返回符合条件的局部top M记录,然后dble对各个MySQL的局部top M记录进行进一步筛选,得到全局top M记录”。由于dble能够下推计算给MySQL(让各个MySQL计算局部top M),一方面,减少了dble需要处理的数据量,减少了对dble的空间占用和代价较高的网络传输量,另一方面,MySQL数量多于dble,下推给MySQL的计算相当于获得了并行计算的好处。因此,获取首页的理论性能并不差。

但是,在获取后续的页面时,该SQL的执行性能随着页码增大(n趋向于+∞)而不断劣化。原因在于此时现阶段的dble无法下推计算给MySQL。以获取第2页(n=2)为例,dble无法直接否定“第一页和第二页数据都在同一个dataNode上”这种场景,所以dble交给MySQL的LIMIT子句为了照顾这种场景,假设页体积为100,那么实际下推的只能是LIMIT 0, 200,以此类推,由于从第一页到第n页数据都在同一个dataNode上的牵制,dble为了保证执行计划的安全,只能让MySQL执行LIMIT 0, n*M,导致页码n越往后,dble要处理的数据量就越大,从而性能每况愈下。

三 最佳实践

为了克服直接翻页在页数靠后时的性能劣化问题,其中一种解决思路就是解决掉dble只能下推LIMIT 0, n*M的无奈。从操作上来说,我们最终的目标是让LIMIT子句与页码n无关,最好是恒定为LIMIT 0, M(即LIMIT M)。

至此,解决思路就很明显了:让dble下推SQL给MySQL时,告知MySQL不要返回已经拿到过了的记录就好了。

id NOT IN ( retrivedIds ... )这样的WHERE条件,在页码增大时,会导致需要列举的id过多,执行效率低下,语句也很容易超出max_packet_size的限制。因此,我们应该对结果集进行基于id的排序,然后就能使用更为简洁的WHERE条件id > maxId来在MySQL层面过滤掉不需要的记录了。

下面就是基于这个思路的实践方法。

1. 获取首页

直接翻页的语句获取首页的效率已是最高,直接使用直接翻页的SQL,但对返回结果中,id字段的最小值和最大值分别记录为minId和maxId,用于后面的翻页动作。

SELECT
  *
FROM many_node_table
WHERE 
  ts BETWEEN TIMESTAMP('2019-01-01 00:00:00') AND TIMESTAMP('2019-03-31 23:59:59')
  AND branchId = 'user_specified_branch'
  AND departId = 'user_specified_department'
ORDER BY id
LIMIT M

2. 向后/向前翻页

以向后翻页为例。

替换以下SQL中的maxId后,交给dble执行。返回的记录本身按照id字段已经有序,直接就是下一页内容。记得更新minId和maxId。

SELECT
  *
FROM many_node_table
WHERE 
  ts BETWEEN TIMESTAMP('2019-01-01 00:00:00') AND TIMESTAMP('2019-03-31 23:59:59')
  AND branchId = 'user_specified_branch'
  AND departId = 'user_specified_department'
  -- tell MySQL do not return retrived rows --
  id > maxId
ORDER BY id
LIMIT M

同样道理,向前翻页就是替换以下SQL中的minId后,交给dble执行。千万要记得更新minId和maxId。

SELECT
  *
FROM many_node_table
WHERE 
  ts BETWEEN TIMESTAMP('2019-01-01 00:00:00') AND TIMESTAMP('2019-03-31 23:59:59')
  AND branchId = 'user_specified_branch'
  AND departId = 'user_specified_department'
  -- tell MySQL do not return retrived rows --
  id < minId
ORDER BY id DESC
LIMIT M

最佳实践的限制与注意事项

没有银弹方案,最佳实践由以下限制或注意事项:

  • dble的拆分列(sharding key)同时也是MySQL物理表的主键
  • 连续翻页
    • 每次查询的页只能是上一次查询的前一页或者后一页
    • 第一次查询必须为首页
  • 翻页SQL必须是单表SQL,因为两个表JOIN的时候,结果集里1条记录的字段可能实际上来自不同的表,而导致记录有多个拆分列值,无法按照本方法翻页
  • 翻页SQL必须要有ORDER BY子句
  • 翻页SQL的ORDER BY后缀必须为拆分列,继续上文的例子,可以是ORDER BY idORDER BY ts, id,但不能是ORDER BY id, ts
  • 无论是“获取首页”还是“向后/向前翻页”,其SQL一般都是广播语句(需要查询该表所有dataNode),广播语句对MySQL的max_connections连接数消耗明显,因此翻页查询应该要算到广播语句中,而广播语句的并发量建议不要超过单个MySQ的max_connections的10%,例如MySQL的max_connections为512,则包含翻页查询在内的所有广播语句的并发量建议不要超过51条
  • 从保护dble内存出发,建议每页最多记录数M与逻辑分片数量dataNodeCount乘积不多于8000,即M * dataNode <= 8000
  • 依赖dble的客户端控制翻页,增加了开发成本
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 一. Java基础部分.................................................
    wy_sure阅读 3,785评论 0 11
  • ORACLE自学教程 --create tabletestone ( id number, --序号usernam...
    落叶寂聊阅读 1,064评论 0 0
  • 什么是数据库? 数据库是存储数据的集合的单独的应用程序。每个数据库具有一个或多个不同的API,用于创建,访问,管理...
    chen_000阅读 4,028评论 0 19
  • 一、需求缘起 分页需求 互联网很多业务都有分页拉取数据的需求,例如: (1)微信消息过多时,拉取第N页消息 (2)...
    duzhongli阅读 446评论 0 3
  • 大葱和蒜蓉, 是小我十岁的龙凤胎小祖宗。 人前腼腆害羞男,懂事乖巧女。 姐前天下第一帅,无敌自恋拽。 我长得不像我...
    尛珂阅读 692评论 1 1