分布式数据库——从线性扩展谈分布式JOIN

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 在首届阿里巴巴中间件峰会上,来自阿里巴巴DRDS团队的梦实分享了《分布式数据库——从线性扩展谈分布式JOIN》。他主要从OLTP数据库的线性扩展、水平扩容、IN查询、分布式JOIN四个方面进行了分享。在分享中,他主要通过买家与订单场景、家庭与孩子场景介绍了IN查询,通过同维度的JOIN、广播表的JOIN、Nested Loop Join详细介绍了分布式JOIN的坑与填坑。

在首届阿里巴巴中间件峰会上,来自阿里巴巴DRDS团队的梦实分享了《分布式数据库——从线性扩展谈分布式JOIN》。他主要从OLTP数据库的线性扩展、水平扩容、IN查询、分布式JOIN四个方面进行了分享。在分享中,他主要通过买家与订单场景、家庭与孩子场景介绍了IN查询,通过同维度的JOIN、广播表的JOIN、Nested Loop Join详细介绍了分布式JOIN的坑与填坑。

 

以下内容根据直播视频整理而成。

 

在数据库的使用过程中,我们难免会问到这样的问题,为什么分库分表?答案是为了达到线性扩展。在本次分享中,我们能够知道分布式数据库中线性扩展的含义,学会判定一个系统与查询能否达到线性扩展的目的,达到使用分布式数据库的目标。

OLTP数据库的线性扩展

数据库主要有两类:OLAP数据库,SQL一般比较复杂,执行时间可能在秒级至分钟级,响应时间越快越好(单SQL占据更多的资源,例如Map Reduce模型),提供尽可能高的并发度;OLTP数据库,SQL一般比较简单,执行时间一般在毫秒级,响应时间在可接受范围(例如10ms)内即可(单SQL一般只有一个线程执行),提供尽可能高系统容量。

对于OLTP,来说,是否机器越多,SQL执行越快?答案是否定的。对于OLTP数据库中的线性扩展,增加机器数,单SQL的响应时间基本不会发生太大变化;增加机器数,能线性增加整个系统的容量(并发度、吞吐量、TPS)。并且,在资源一定的情况下,从单机到分布式并不能带来更高的系统容量。

水平扩容

比如做一个全表的count(*)操作,每个分库上的时间可能是10ms,如果不带拆分键的话则需要到所有的表上去执行一个count(*)操作,如果将这些SQL并行的发下去,则会发现查询也只消耗了10ms的时间,与只在一个分库上执行的时间差不多。那为什么还要带拆分键?

比如我们有两台DB实例,如果带拆分键的话,往分布式数据库里面提交一个查询,到DB这边也是一个查询,那么提交6条查询之后,系统容量是6QPS。我们把提交到分布式数据库中的查询称为逻辑查询,把分布式数据库到底层所执行的查询称为物理查询。对于业务来说,分布式数据库是相对透明的,它关注的容量指的是逻辑QPS。

a616d9a2a11a977246b3133f2e79aade4acc5d16

发现系统容量不够时,需要进行扩容,我们需要加1台DB,成本提升了50%。如图所示,性能确实提高了50%。

e5bc6ac02280bc86091935c1786a434a609428f9

假如我们的SQL没有带拆分键,那么它需要路由到所有的分片上去执行。一条逻辑SQL会生成2条物理SQL。扩容之后,成本提升了50%,一条逻辑SQL会生成3条物理SQL,系统性能也没有变化。所以得出一个结论:系统一定要带拆分键,否则没有可扩展性。

IN查询

买家与订单场景

具体场景是:提供一个买家的所有订单ID,查出这些订单的信息,订单表按照订单ID进行拆分,SELECT * FROM ORDER WHERE ORDER_ID IN (?,?,?,?...),假设单DB容量是1000QPS。

cc1eca17b7f031b854d191116423cc78f4c90c8e

比如,2005年数据分片数为16,平均一个买家的订单数:2,IN查询涉及的分片数:期望值接近2,系统容量:16*1000/2=8000。到了2015年,数据分片数为64,平均一个买家的订单数:200,IN查询涉及的分片数:期望值接近64≈全表扫描,系统容量:64*1000/64=1000。在这种场景下,IN查询不具备线性扩展能力。

家庭与孩子场景

具体场景是:提供一个家庭的所有孩子ID,查出这些孩子的信息,孩子表按照孩子ID进行拆分,SELECT * FROM CHILD WHERE CHILD_ID IN (?,?),假设单DB容量是1000QPS。跟前面的区别在于,订单数会随着时间的推移飞速发展,但是孩子数不会随着时间发生太明显的变化。

cdd95f701788f4b1f71e2f6a87202d692b91a1c4

假设,2005年,数据分片数:16,平均一个家庭的孩子数:1-2,IN查询涉及的分片数:期望值接近2,系统容量:16*1000/2=8000。2015年,数据分片数:64,平均一个家庭的孩子数:1-2,IN查询涉及的分片数:期望值接近2,系统容量:64*1000/2=32000。在这种情况下,成本提升了50%,系统容量也提升了50%,IN查询实现了线性扩展。

线性扩展

对IN查询来说,必须满足下述条件才能做到线性扩展:IN的值的数目远远小于分片数;一般情况下,IN的值的数目在2-3个;IN的值的数目不会随着业务的发展而增长。相反的,只要有一条不满足,那么IN查询就无法做到线性扩展。

分布式JOIN

分布式JOIN有很多种情况,主要分为两大类:可下推的JOIN,JOIN操作由存储完成,DRDS层针对JOIN的结果进行处理,效率会高一些,因为存储和计算在一起;不可以下推的JOIN,存储层只做单表的查询,DRDS完成JOIN的操作。

同维度的JOIN

可下推的JOIN中很重要的是同维度的JOIN,JOIN的两个表是按照拆分键做的JOIN,简单例子如下:

SELECT* FROM
user JOIN user_address
ON user.user_id= user_address.user_id

85fd8a5021cce5f04acbc32c59120c549b77608f

user与user_address需要JOIN,并且均以user_id为拆分键。这样的结果是,对于同一个用户,其地址与它在同一个分片内。所以只需要在存储层把JOIN做好就可以了。对于DRDS来说,这个JOIN操作由下面的MySQL或者RDS来完成。DRDS层只需要把JOIN结果返回去就可以了。这种JOIN线性判断的标准是与单表SQL相同。

广播表的JOIN

另外一个比较常用的JOIN是广播表的JOIN。有些表具有以下特点:比较小,总会与其他表进行关联,这时候就不适合将其放在其中一个库上面,那么,这个JOIN就没有办法下推了。广播表是指所有分片中都存在一个完整的副本,一般用于变更比较少,容量比较小,需要频繁与其表发生关联的表。一张拆分表JOIN一张广播表的简单例子如下:

SELECT* FROM
user JOIN user_address
ON user.user_id= user_address.user_id

fd9ee6aaf9e1b50cf27ad1fea1a0831c4e0c089f

此时,选择把level表作为广播表复制到每一个分片上去,这样的JOIN也可以做下推,只要做分片内的JOIN就可以了。这样一个查询的线性判断标准与单表SQL相同,由拆分表上的查询决定。

Nested Loop Join

Nested Loop Join是不可下推的分布式JOIN。具体例子如下:

SELECT* FROM
order JOIN user
ON order.buyer_id=user.user_id

c5a0f1db429dbe943f1c4c60a6d53b06d6d94b2a

user以user_id为拆分键,order(订单)以order_id为拆分键。JOIN不能由存储来完成,只能由DRDS层完成。具体的算法等价于在左表把需要的数据拿出来然后再去右表做应用查询,即以小表驱动(经过WHERE条件过滤后数据量较少的表为小表)。

对于这种JOIN,线性判断标准直观来说是对两个表的查询均需要能够线性。即对驱动表的判断与单表查询相同;对被驱动表的判断:被驱动表的JOIN列是否是拆分键,被驱动表做的IN查询的数目。

实例分析

主要有两张表,order表拆分键为order_id,user表拆分键为User_id。

SELECT* FROM order JOIN user ON order.buyer_id = user.user_id

结果分析:两张表均是全表扫描。

SELECT* FROM order JOIN user ON order.buyer_id = user.user_id WHERE order.id = 1

结果分析:order为驱动表,user表的JOIN列是拆分键,一个order只有唯一一个user。

SELECT* FROM user JOIN order ON user.user_id = order.buyer_id WHERE user.id = 1

结果分析:user为驱动表,并且带了拆分键上的等值条件,order表的JOIN列不是拆分键,对于order表是全表扫描。

SELECT* FROM user_oders JOIN order ON user_oders.order_id = order.order_id WHERE user_oders.user_id = 1

结果分析:user_orders记录了一个用户有哪些订单,拆分键为user_id,user_oders表为驱动表,并且带了拆分键上的等值条件,order表的JOIN列是拆分键,一个user对应的order会随业务的增长而增长,在order表做的IN查询也会增长。

买卖家问题

具体场景是:一个订单,包含买家id(buyer_id)与卖家id(seller_id),买家希望根据买家来查,而卖家则希望根据卖家来查。那么一张表通过买家来拆还是通过卖家来拆?

d40f7e110f4f10ae6d775bf15a13a1753ac92623

传统的解法是空间换时间,模仿单机数据库中建索引的这种方式,将数据存两份,一份按买家id拆分,一份按照卖家id拆分。这个方案的缺点是占的空间太大,因为把整个表都冗余起来了。

c7f608e149509440f586be838423a4b71deb8c5d

或许有人认为,一种较好的解决方案是只冗余id列,只需要买家id到卖家id的映射关系和卖家id到买家id的映射关系。在JOIN查询时,随着时间发展,一个买家对应的卖家会越来越多,那么这就是一个错误的方案。更好的方案是把关联关系做一个全冗余,使其避免出现这样的JOIN查询,用更多的空间来换取时间、线性扩展的能力。

DRDS

如何快速判断一个正在使用DRDS的系统是否能做到线性扩展?在DRDS监控页面里面有两个监控指标,一个是物理QPS,一个是逻辑QPS。判断的方法是,看这两个指标是否接近1:1。如果接近1:1说明系统很大概率是可以线性扩展的。若远远大于1:1,那么就不能线性扩展。
相关实践学习
Polardb-x 弹性伸缩实验
本实验主要介绍如何对PolarDB-X进行手动收缩扩容,了解PolarDB-X 中各个节点的含义,以及如何对不同配置的PolarDB-x 进行压测。
相关文章
|
1月前
|
Oracle 关系型数据库 分布式数据库
分布式数据库集成解决方案
分布式数据库集成解决方案
200 0
|
3月前
|
Cloud Native 关系型数据库 分布式数据库
掌阅科技采用云原生数据库PolarDB,大幅降低使用成本,提升业务稳定性和扩展性
掌阅科技将数据库迁移到PolarDB后,数据压缩到之前的30%,整体成本节省50%。
92 0
|
3月前
|
Cloud Native 关系型数据库 分布式数据库
关系型分布式云原生数据库
PolarDB是阿里巴巴自主研发的下一代关系型分布式云原生数据库。在兼容传统数据库生态的同时,突破了传统单机硬件的限制,为用户提供大容量、高性能、高弹性的数据库服务。
37 1
|
3月前
|
分布式数据库 数据库 数据库管理
什么是分布式数据库
什么是分布式数据库
|
3月前
|
NoSQL 中间件 API
分布式锁【数据库乐观锁实现的分布式锁、Zookeeper分布式锁原理、Redis实现的分布式锁】(三)-全面详解(学习总结---从入门到深化)(下)
分布式锁【数据库乐观锁实现的分布式锁、Zookeeper分布式锁原理、Redis实现的分布式锁】(三)-全面详解(学习总结---从入门到深化)
80 2
|
23天前
|
存储 数据采集 数据挖掘
【软件设计师备考 专题 】数据仓库和分布式数据库基础知识
【软件设计师备考 专题 】数据仓库和分布式数据库基础知识
193 0
|
1月前
|
存储 SQL 分布式计算
TiDB整体架构概览:构建高效分布式数据库的关键设计
【2月更文挑战第26天】本文旨在全面概述TiDB的整体架构,深入剖析其关键组件和功能,从而帮助读者理解TiDB如何构建高效、稳定的分布式数据库。我们将探讨TiDB的计算层、存储层以及其他核心组件,并解释这些组件是如何协同工作以实现卓越的性能和扩展性的。通过本文,读者将能够深入了解TiDB的整体架构,为后续的学习和实践奠定坚实基础。
|
1月前
|
运维 关系型数据库 分布式数据库
客户说|从4小时到15分钟,一次分布式数据库的丝滑体验
识货APP致力于为广大用户提供专业的网购决策指导,为喜欢追求性价比的网购朋友带来及时劲爆的运动、潮流、生活、时尚等网购优惠资讯,产品覆盖国内外主流购物商城。它提供了全球范围内的时尚品牌、潮流单品的信息,帮助用户发现和购买最新、最热、最具性价比的时尚商品。近年来,各大电商平台上的商品信息持续增加,海量商品信息增加了消费者的选购成本。识货从用户视角出发,不断整合行业渠道供给,降低发现和筛选成本,帮助用户更高效地购买到最具性价比的产品。
|
1月前
|
运维 关系型数据库 分布式数据库
客户说|从4小时到15分钟,一次分布式数据库的丝滑体验
识货APP引入PolarDB分布式版,轻松完成分布式演进
客户说|从4小时到15分钟,一次分布式数据库的丝滑体验
|
1月前
|
Oracle 关系型数据库 分布式数据库
分布式数据库集成解决方案2
分布式数据库集成解决方案2
151 0