开发者学堂课程【PolarDB-X 开源人才初级认证培训课程:分布式事务与数据分区】学习笔记,与课程紧密连接,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/1075/detail/15545
分布式事务与数据分区
内容介绍:
一、前景提要
二、分布式事务
三、数据分区
一、前景提要
PolarDB-X 是一个支持计算存储水平扩展,数据高可用基于存储计算shared-nothing架构和一致性复制协议的分布式数据库,定义分为两部分,第一部分是支持计算和存储能力的水平扩展,数据高可用,是从用户的角度表达一个用户对分布式数据处的需求,存储计算分离shared-nothing架构和一致性复制协议是PolarDB-X为实现需求所采用的系统架构。
其中一致性的处置协议主要解决的是数据高可用的问题,是明天分享的主要内容。
存储计算分离和shared-nothing架构是PolarDB-X为了提供水平扩展能力与单机数据库相比增加的两个新的设计,第一个是资源层面的设计,也就是将集群内的节点分为计算节点和数据节点两类,成为存储计算分离。
其中计算节点是无状态节点通过增加计算节点提供算力的水平扩展能力,第二个是数据层面的设计,将数据按照分区键切分为多个分区,通过将分区键迁移到新增的数据节点上提供存储容量水平扩展能力,由于分区后节点之间不再需要共享存储资源,也被称为shared-nothing架构,关于PolarDB-X在分布式事务和数据分区方面的探索,分享分布式事务和数据分区,一方面是分布式数据库中比较关心的两个话题,另外一方面分布式数据库本身也是一个数据库,用单机数据库一样,数据库有两个强需求,第一个强需求是希望数据库能够支持事务,第二个强需求叫做希望数据库能够支持sql,分布式数据库由于它在架构上的不同,特别是引入shared-nothing架构之后,对于事务的设计以及对于sql里面查询的设计都有很大的影响,分享的两部分内容就是在采用shared-nothing架构之后分布式数据库PolarDB-X是如何解决事务方面的问题以及解决查询方面的问题,就是分布式事务和数据分区方面的特殊设计。
二、分布式事务
1、数据库是用于存储数据的系统,使用数据库最主要的需求就是读写数据,设计良好数据库是尽可能屏蔽底层的实现,类比就像去银行,最主要的目的是存钱和取钱,不关心银行柜台后面到底发生什么事情,数据库本身是一个非常复杂的系统,经常要面对各种各样的复杂的情况,比如可能由于数据库本身的代码问题或者由于硬件故障,可能导致数据库在写入数据的任何阶段意外的崩溃,客户端的应用程序在一系列连续操作中会突然的退出,并且为提高吞吐量通常要允许多个客户端去并发的修改同一条记录,类似复杂的情况还有非常多,当复杂情况出现的时候,特别出现异常的时候,数据库需要保证失败的操作不会导致数据问题,并且还要引导用户继续完成业务需求,需要有一个关于数据可靠性的保证,用户按照保证或者约定使用数据库就可以规避一些数据上的问题,事务就是关于数据可靠性的一个保证,数据库提供给用户关于如何保证数据可靠性的约定,具体事务的定义是一组用表达业务含义的读写操作,满足acid特性。
转账例子,事务里面包含四条语句,第一行begin和最后一行的commit圈定事务的范围,事务中包含两个update的语句,第一条语句是在account表上id为alice的账户减30,第二条id为bob的账户加30,表达alice向bob转账30的业务语义,对于这样的事务,数据库提供acid四个方面的保障,a是原子性,代表如果出现异常,用户可以通过从事整个事务解决问题,不需要担心失败的事务会对数据产生影响,C是一致性,代表用户不需要担心数据操作会违反预定的约束,比如会违反唯一约束和违反外键。i是隔离性,代表用户可以认为只有自己的操作数据库,不需要担心多个客户端并发可能会产生一些数据方面的异常。
D是持久性,代表一旦事务提交成功,用户就不再需要担心事务产生的变更会因为其他异常而丢失,有事务的抽象以后可以认为存储是非常可靠的,事务只要提交,数据就一定不会丢失。
2、四个特性里面和分布式事务相关比较强的是原子性和隔离性。原子性强调的是事务提交后,事务的变更必须一起生效,比如事务1提交后必须是alice账户减30并且bob账户加30,不能出现alice减30而bob没加或者alice加30,bob没减的情况。
隔离性强调的是多个数并发执行的时候,数据库执行结果看起像是没有并发一样,数据库可能按照任意顺序收到事务1和事物2的四条update,但执行结果应该看起像是先执行事务一,再执行事务二或者是先执行的事务二,再执行事务一,如果事务一和事务二按照一个先一个后的执行方式,看到的账户里面的总额最后一定是206,但是如果隔离性出现问题,alice是按照转账后的值进行派息而bob是按照转账前的值派息,违反隔离性的一种表现。
3、在分布式事务中由于存在多个分区,原子性和隔离性都是需要重新实现,可能是因为本身实现的复杂性或者新的实现可能导致性能下降,2000年前后出现的nosql系统不支持跨分片事务,缺少跨分片事务是不符合用户的使用习惯,google也在论文里面总结到发现许多工程师将过多的精力放在处理数据的一致性上,原本封装在数据库内部的逻辑溢出到应用代码中,所以大幅的提高应用代码的复杂度,所以到2010年之后,市面上出现的OLTP类的分布式数据库将分布式事务作为一个B选项。分布式事务为保证原子性和隔离性都需要做哪些工作?
4、单分片上的原子性是由日志保证的,在写入每条数据的时候,首先记录一个回滚日志,遇到事务的异常就通过日志将数据恢复到先前的版本,对于跨分片数在单片事务的基础之上,还需要协调所有分区在提交阶段的行为,保证一起提交或者一起回滚,以事物1的转账为例,收到用户的commit请求之后,如果cn直接向每一个分片都下发一个commit语句,可能会出现分片1已经提交成功,但是分片2二发现有异常,直接分配事务回滚的情况,此时整个分布式事务是已经失败,但是分片一上的数据无法回滚,产生不一致,在业界通常使用两阶段提交协议解决问题,把参与的几个节点分成两种角色包括协调者和参与者,协调者是负责判断事物是否能够继续提交的一个角色,PolarDB-X中使用计算节点作为协调者,参与者代表是分布式事务涉及的数据节点,提交过程的第一个阶段称为prepare,当收到用户的请求,首先进入prepare阶段,协调者会询问每个事务的参与者,分支事物是不是能够提交,事务参与者执行一些校验或者写入日志之后的操作,如果发现能够提交,它们就会返回ok,协调者收到所有ok之后就认为完成可以进入提交阶段,进入第二个阶段commit阶段,协调者会通知每一个参与者,所有的分支都已经具备提交条件,所以可以继续提交,和一阶段一样,所有的参率者完成提交后返回一个ok,最后协调者确认所有参与者都完成提交之后就可以告知用户分布式事务完成,在一阶段prepare阶段成功之后通常会保存一条日志记录一阶段已经完成可以进入二阶段,记录日志的目的是如果在进入二阶段之前协调者出现crash出现宕机,重启之后依然可以根据日志判断事务是否成功,如果成功可以继续提交,如果没有成功可以把它回滚掉,如果有一个参与者失败会是什么样的情况。
5、prepare阶段依然是协调者,首先询问每一个参与者是否能够提交,有一个分片返回失败,于是在第二阶段协调者要通知所有参与者回滚事务,收到参与者的重复回复之后告知用户事物在提交的时候发生失败已经自动回滚掉,是现在业界最为常用的解决原子性提交的两阶段提交解议,两阶段提交协议本身是一个协议,在工程实现上最常见的实现有percolator和xa两个实现,percolator本身在提交阶段延迟比较高,而且只会在提交阶段汇报写入冲突,而且主要是支持乐观锁的场景,与传统的关系流数据布局悲观锁的模型有比较大的区别,因此PolarDB-X选择通过Xa协议实现两阶段的提交。这是分布式事务在保障原子性方面选择的技术方案。
6、在隔离性上做的处理,单分片事务上是如何保障隔离性,保障隔离性目前基本上所有的数据库在做单分片事务的时候都会采用MVCC的方案,MVCC全称是多版本并发控制,当有数据写入到表中的时候,更新alice和bob的的账户,将alice的账户余额设置为70,将bob的账户余额设置为130,
在做操作的时候,数据库会将老的数据,就是在他们转账之前各自账户都是100元的数据,会为它生成一个历史版本的快照,并且把每一行数据的快照和写入每一行数据的ID进行关联,当有一个事务要读取数据的时候,会首先根据每一行记录上的事务ID看事务是否提交,如果没有提交的事务,它会认为数据一定足够用,所以可以沿着版本往前找到一条已经提交的事务,找到一条已经提交的事务的写入的数据之后,它又会根据自己的事务id和数据上的事务id做一个对比,判断数据是在事务开始之前写入,还是在开始之后写入,如果是在开始之前能看见,如果是在开始之后就看不见版本,所以就可以达到确定数据到底是不是可见,mvcc并发控制协议的好处是解决写和读并发的情况,231号事物更清alice和bob账户的事务没有提交,但是事物ID为220的事物依然可以继续读历史版本,达到一个比较好的并发度,因为不关心还没有提交的数据,所以直接用一个历史的数据接着做下面要做的事情,所以就是为什么所有单机数据库上,或者分布式数据库也是一样,都支持基于mvcc的实现,在单分根片上通过MvCC加事物ID已经实现隔离性,到分布式事务,当引入多个分区的情况下需要做哪些改进,最主要的一点是原先采用的事物ID,它是在单个分片上进行分配,现在有多个分片,有多个分区,就需要有全局的统一分配事务ID的地方或者采取时间排序的方式,需要有一个唯一的地方获取获取时间,获取事务id,获取用于判断是不是可以看见数据的版本号,业界目前有两种方案,一种还是像单机数据库一样,基于一个活跃事务列表GTF,区别在于把分发事务ID的地点从每个分片上去挪到一个公共的服务上,每次读写事务开始和写事务提交的时候,都会去活跃事务列表里面做更新读取,方案对GTM本身的压力比较大,它会非常依赖中心化的数据管理器,相对比较容易出现系统瓶颈,因此PolarDB-X选择另外一种方案,也就是基于TSO的mvCC方案支持隔离性,对于读写的操作都有一定的改变,在数据写入的过程中依然是两阶段提交,但是在一阶段结束之后会读取一个提交时间戳,把提交时间在commit阶段下发到每个分片上,将提交时间戳和数据关联到一起。一阶段的日志在每一个分片上都保留一张事务的日志表,一阶段的日志就会根据第一个写的是哪个分片,就会把日志落在哪个分片上,如果出现cn的宕机恢复,会扫描所有分片上的输入日志,看有哪些事务是在上一次宕机的时候,处于一阶段结束二阶段还没有开始的情况,进一步推进它的提交和回滚。隔离性在数据写入方面的影响,就是在提交之前要获取提交时间戳,在数据读取的时候,同样也要获取一个时间戳,主要是用判断数据是否可见,过程就变为首先TSO服务,又叫GMS服务,在上面获取时间戳作为自己的snapshot timestamp和数据相关的数据上的时间戳做对比,如果时间比数据时间戳的大并且它所属的事务已经提交掉,认为数据应该是能看见的,反之如果没有提交或者时间戳比它要小,数据就不应该被看到,PolarDB-X在实现分布式事务隔离性方面所做的一些设计。
7、PolarDB-X上的分布式事务开启之后,首先会向TSO获取一个sstart_ts作为读的快照,开始接受用户所有的请求,过程中根据对应的事务状态和拍照时间戳对数据和对应的提交时间戳,判断数据是否可见,保证隔离性,在节点提交的过程中,还采取方案,首先是通知所有参与企业操作的分区执行prepare,记录事务的状态,最后通知所有的提交者进行提交,在两阶段提交的记录输入状态成功之前,就是记录事务状态为提交阶段,在这一步骤之前,所有产生的异常都会产生事务回滚,导致原子性,采用2pc和TSO+mvcc方案实现分布式事务经常被质疑的问题,提交阶段会增加延迟,因为毕竟以前提交一步就完成,现在变成两步,另外一个问题是tso会不会也存在单点,因为有两个地方都需要从tso上获取一个时间戳,针对两个问题,PolarDB-X都进行工程上的优化,首先是对两阶段提交的优化,两阶段提交由于增加prepare阶段,它的提交延迟肯定会高于单分片事务,但是在实际上对于单分区事务无论读是否划分区,依然是可以采用一阶段提交保障原子性,PolarDB-X支持自动识别情况,减少此类场景下的提交延迟。另外一个问题是Tso方案由于采用斑点授值,一个潜在的问题是可能存在单点故障和单点性能瓶颈的风险,PolarDB-X的GMS是部署在三节点的集群上,所以首先通过x-paxos协议保证服务的高可用,同时PolarDB-X对多种场景进行优化,使得在拆分开条件的检查,点写可以不依赖GMS,d提升查询性额能同时也降低GMS的压力,另外对于单个CN进程默认采取的plan方式将同一时间内发生的多个tso请求合并为获取操作,很多事务可能在并发进行,在任意卡一个时间点可能都会有一批事务在等待获取同时间戳或者提交时间戳,把一批获取时间操作,合并成一个web操作,通过一个自定义的指令发送给TSO服务,一次性的获取到需要的所有的时间戳,进一步保证CN和GMS之间交互的指令包不会太多,避免GM成为系统瓶颈。
8、分布式事务需要在原子性和隔离性方面做一些改造,PolarDB-X采用的是2PC加TSO和MvCC的方案解决问题,同时对于2PC和TSO本身可能存在的一些问题做工程上的的优化。展示flashback query的例子,事务里面隔离性采取MvCC方案,它会把历史的版本采取一个列表的方式记录下来,可以通过在查询中指定时间戳的方式,看到历史版本里面的数据。首先登录PolarDB-X数据库实例,在数据库中已经创建两张测试表,查看表结构,account和user表都是默认使用主键拆分的分区表,向两张表中插入数据,查看插入结果,目前有alice和bob两个账号,并且他们的账户余额都是100块钱,记录当前的时间点,进行一些写入操作,首先是一个转账测试,alice向bob转账30块钱,新增一个名为tom的账户,查看目前表中的数据情况,包含三个账户,并且alice和bob发生过一次转账,使用flashback query语法可以查询数据库中的历史版本,通过记录的时间戳,可以查询到更新数据之前的版本,可以看到更新数据之前是两个账户,且他们账户里面都只有一百块钱。
As of语法同样可以使用再join当中,可以为join的其中的一张表指定时间戳,而另外一张表不指定,对user表指定时间戳,而account表不使用,预期看到的效果就是alice和bob发生了转账,但是tom的账户并没有插入到表中,flashback query语法最重要的一个用途是帮助用户恢复误删的数据,模拟记录当前的时间戳,删除account表中过的数据,通过insert select的语句再select中指定删除前的时间点,将历史版本的数据重新写回到表中,查看一下表中的数据,可以看到之前被删除的数据又恢复回来。
三、数据分区
1、数据分区,当采用shared-nothing架构将数据全部按照分区键进行分区之后,它对数据的查询有一定影响。影响是什么?解决方案是什么?
2、有一张按照组件id做分区键的一张表,t1表就是id,当提供查询是id上的等值条件的时候,对于id=14,等值计算出id的哈希值,可以知道数据在p3分区上,问题是如果要按照内部的条件查该怎么办?粗略的看起只能把所有的分区都扫描一遍,代价就是变高,数据库是如何解决问题,在mysql中同样的表结构,实际上是b+树,按照主键id有序,当按照id进行查询的时候,实际上数据会在b+树做二分查找,从而快速的定位到数据所在的叶子节点,同样当以内部节点进行查询的时候就没有办法做二分查到,只能从头到尾将整个b+树垫底,代价比较高,它也有一个专门的名字叫做全表扫描,在mysql中为避免全表扫描,通常会在内容上创建一个索引,索引就是创建另外一个b+树,按照内部进行排序,在它的所有的叶子节点中,包含内部对应的主键id的值,等于name上的主键查询,数据库会先在二级索引上进行阿帕奇,在b+树上进行二分查找,找到对应的id之后,再用id回到主键的b+树上进行查找,回找操作,二级索引的思路属于计算机里面比较常见的思路,就是一个典型的空间换时间的思维,在mysql中设计主键的时候,通常只需要考虑它在业务上是唯一的,并不用太多的在意主键是不是包含在所有的查询条件中,因为在mysql上创建索引的成本非常低,查询里面有什么条件,都可以按照条件创建一个索引,可能也成为使用单机数据库的固定思路,没有太大问题,分布式数据库在按照主键拆分之后按照name列进行查询避免全表扫描,参考空间换时间的思路,以name分区键,再将数据做一份冗余,将name映射到能主键id,可以达到索引同样的效果,冗余的数据就称为全局二级索引,全局的意思是索引的数据和主表的数据并没有保存在同一个分片上,通常是因为拆分维度的不同,按照name拆和按照ID拆,会保存在不同的分区上,有了全局二级索引之后,按照name等于megan进行查询的时候,先通过全局二级索引定位到megan所在的分片,找到megan的id值,到主表上查到整条记录所在的分区,和单机上是一样的也称为回表,把分布式数据库上创建全局二级索引的代价使用体验和单机数据库上过程比较接近,也可以像使用单机数据库的时候一样不太关心分区键,所以如何实现全局索引是一个非常关键的点。
3、通过一个全局索引优化查询性能的例子实际感受PolarDB-X的全局二级索引大概做成什么样子。登陆一个PolarDB-X数据实例,创建测试使用的数据库,使用sysbench脚本创建测试使用的表和填充数据,测试脚本经过修改,会显示使用的建表语句,可以看到是一个采用mysql原生语法的建表语句,查看建表结果,默认以主键id作为分区键,表包含16个分区,表中包含一万条数据,均匀分布在16个分片上,进行查询测试,首先是主键上的点查,查询当中包含了主键上的等值条件,可以看到查询性能非常不错,通过explain语法查看一下执行计划,由于包含了分区键上的等值条件,整个查询可以路由到一个具体的分片上,所以查询性能非常的好,如果不包含分区键上的等值条件会是什么情况呢?
第二个测试用例使用k上的条件作为查询条件,将qps日志导入到一个文档中,可以在网页上查看,可以看到qps只有两千五,执行计划,由于没有包含分区键上的条件,需要扫描全部16个分片才能得到查询结果优化语句可以通过PolarDB-X提供的查询建议工具,看查询建议,优化建议是通过增加索引提升查询性能,同时给出增加索引的语句,增加索引过程是online schema change,不会阻塞查询的执行,可以看到索引加上之后,整个查询的性能得到了一个很大的提升,再看一下此时的查询计划,可以看到bkajoin代表了回表操作,整个查询的执行过程是通过索引上的拆分条件k,定位到一个索引的具体分片,从中查到需要返回的行,再回到主表上,根据主键得到所有的查询结果,对于回表操作,还可以通过PolarDB-X提供的聚簇索引来进一步优化,聚簇索引可以保证索引表和主表始终保持相同的结构,可以通过索引扫描来代替主表上的扫描,可以看到增加聚簇索引之后,整个查询性能又得到了很大的提升,基本上已经达到了最初的两千五的十倍,再看一下此时的查询计划,直接通过索引扫描就能拿到最终的结果,并且整个查询只需要扫描一个索引上的分片,经过两次增加索引之后,现在的表结构包含一个聚簇二级索引都是全局索引,各自包含了16个分片,如果用户可以确定自己的业务场景主要是以k作为查询条件,还可以通过变更区分键的方法来提升查询性能,首先删除之前创建的全局索引,可以看到查询性能立即恢复到了最初的两千五,通过分区变更语法,将分区键调整为k,可以看到调整分区键之后,查询性能又再次得到了很大的提升,查看此时的表结构,表中只包含了一个分区键k上的局部索引,并且包含了16个分片,再看一下此时的查询计划,和一开始主键拆分的情况比较类似,也是直接将查询下发到了一个具体分片,获得了比较好的性能。曲线是通过回表的全局索引,提供了一定的查询性能,增加了一个全局的聚簇索引,全局聚簇索引的区别就是聚簇索引不需要回表,进一步提高到更高的性能,把两个索引都删除掉,查询性能回到一开始的情况,再通过online schema change调整表的拆分键,使得拆分键和查询条件能对上,让查询条件再次得到很高的性能。通过全局索引优化性能满足几个特点,第一是数据是准确的,第二整个键索引和调整索引的过程都是online,键索引通过常量操作索引。
4、移动芯片是一台ECS吗?不一定,因为分片它本身是一个相对独立的概念,可以多个分片落在同一台DN上,也可以一个分片落在一个dn上,没有强制绑定。
5、一个表可以有多个聚簇索引,PolarDB-X设计上可以有多个聚簇索引。
6、对于全局索引,最高评价是如果能够使用体验和mysql中的索引一样已经是一个非常好用的场景。
|
MySQL中的索引 |
不透明的“全局索引” |
一致性 |
强一致 |
最终一致、弱一致、XX一致 |
创建方式 |
DDL语句 |
第三方工具,订阅BINLOG |
使用方式 |
优化器自动选择 |
手动选择 |
兼容其他DDL |
支持 |
不支持 |
前缀查询 |
支持 |
不支持 |
BigKey |
无太大影响 |
单节点瓶颈 |
MySQL中的索引是强一致,不会有延迟,不会有不一致,设计全局索引,如果采用最终一致,弱一致或者其他各种一致都不是一个非常透明的设计,用户在使用索引的时候都需要思考很多问题,不是一个很好的选择,PolarDB-X的全局索引采取的就是强一致的方案,因为PolarDB-X支持强一致事务,可以通过分布式事务保证图表里的数据的强一致。
7、创建过程中不会阻塞用户的读写,在分布式事务中要实现online schema change主要的挑战是schema变更过程中不同的CN节点上的事务,看到的元数据的版本可能是不一致的,PolarDB-X采取存储计算分离的一个架构,CN节点和DN节点是独立的,CN节点的状态可以水平扩展,可以有很多的CN节点,当要让CN节点加载一个源数据,由于网络延迟或者通知上的延迟,先后顺序不同,CN加载新版本的顺序,不少会有时间差,时间差可能很细微,但是它有可能带来数据不一致的风险。
图上有两个CN,左侧的CN叫做CN0,右侧的CN叫做CN1,考虑现在添加一个全局索引,索引基本的物理结构已经准备好,通知CN节点,新来的读写请求,需要考虑维护索引的搭配一致,因为通知的先后区别,可能CN0已经知道全局索引的存在,但是CN1还不知道d全局索引的存在。CN0上收到一个请求,由于它知道全局索引的存在,它会把数据在主表上写一条,在索引表上也写一条,没问题,但是问题出现在CN1并不知道有全局索引,它这时候又收到对数据进行delate操作时候,有问题,因为它不知道全局索引的存在,所以它只会删除表上的数据,不会去删索引表,索引表比删除表多一条数据,会导致数据不一致,不符合对索引表的强一致的要求,所以同样的问题对单机数据库也存在,单机数据库解决问题的办法是通过对源数据加锁保证任意时刻所有事务都只能看到一个源数据,mysql上比较熟悉的当加索引的时候,在加索引换meta过程中会持有CN加锁使得变更源数据操作是在使用老版本源数据的所有事务结束之后才去进行的,分布式系统不能采用这样的方式,原因是由于网络延迟存在不确定性,实现一个跨节点的CN加锁,实现一个跨节点的锁要在每个CN上都取一把锁可能会由于网络延迟导致非常明显的卡顿,甚至有可能锁由于一部分CN没有响应迟迟不能完成加锁的动作,有可能十个CN有一个响应卡顿,导致其他九个都已经持有锁,但是就是不能进入下一步的切换和解锁过程,所以在分布式系统当中不能采取大锁的方式实现,因为它不太符合online的定义,PolarDB-X实现方法online schema change参考f1的出现,出过一篇online schema change介绍自己在f1中实现的论文,具体的做法会稍微复杂一点,可以参考公众号,通过增加两个相互兼容的中间状态使得系统中允许同时存在两个源数据版本,使得递调过程无需加锁,不会堵塞读写请求,它的做法是破坏要求整个系统中只有一个源数据版本的前提条件,它允许有两个,所以CN1拿到新版本CN1还在上一个版本,如果允许情况出现,就没有问题,有支撑PolarDB-X中可以非常轻松的使用create index语句创建全局索引不需要依赖任何第三方组件。
8、索引创建出来,在平时的使用中对于整个表的变更也是可能会涉及到对索引的影响,在各种操作中维护索引表的一致也是一项工作量比较大的内容,PolarDB-X非常细致的对各种ddl都设计online ddl实现,根据ddl的类型可以自动选择执行方式并且能够自动的维护全局索引,虽然使用全局索引,但在ddl操作方面是没有太多的限制,也是为了向单机数据库使用索引的体验,索引创建完成之后,使用索引的时候还需要让它自动的应用在查询里面,做法有手动和自动两种,手动有for index或者use index的方法,但是如果要求每一条查询都手动指令索引肯定不是一个非常好的体验,所以PolarDB-X也支持基于CBO的自动索引选择,在实现上由于索引本身也是一张表并且回表操作实际上是可以表达为索引表和主表在主键上去做一个妆,在工程实现上可以大部分的复用分区表上的估算执行计划代价的方法,估算索引扫描或者索扫描加回表的执行计划代价,难点在于,如果两个表数做壮并且两个表各自都有自己的全局索引,它产生的执行计划空间和两个角并没有任何全局索引,有很大差异,最直观的差异就是它的执行计划空间会非常的大,所以要求CPU有更合理的枚举算法,更合理的减支算法以及它本身的性能要更好,PolarDB-X在这方面做了很多优化,详细的内容可以关注公众号,里面有一些介绍的文档,总结成一句话就是PolarDB-X支持基于代价优化器并且能够自动完成索引选择。
9、分区算法本身在设计上有哪些细节的思考,希望索引能够支持前缀查询,要求分区算法本身能够提供这样的支持,当mysql上创建很多列的索引的时候,其实是把每一列索引的数据按顺序拼在一起,当只有前面几列的时候依然可以拼出前缀,再进行查找,如果分区算法默认采取的是一致性hash值的算法,如果不做特殊处理,依然是把所有数据拼在一起,做整个hash是没有办法做全局查询的,因为一部分数据的hash和整个所有列的数据hash肯定是不一样的,设计就是会分别对每个列做hash,在匹配到前缀的情况下,一样可以做分区裁减,相当于可以做前缀的查询。
10、每一个索引相当于对数据换了一个拆分维度,随着拆分维度越来越多,总有一两个维度会出现数据的热点,把热点定义为bigkey,某个拆分维度下某一个键值的行数非常多,或者写入量非常大,数据量其实不太会成为系统瓶颈,要想成为系统瓶颈,一个单一的key的数据量就超过了一个单分片的容量,可能导确实存不下,单个分片上的容量,通常都在TP级别,单一T超过Tp的数据量是比较罕见的。
常见的就是写入方面的热点,在单机数据库中建索引不太需要考虑热点的问题,比如在性别的列上建一个索引,索引的区分度很低只有两个,写入的时候可能会有一定的争抢,但不会有太大的问题,如果是分布式数据库,如果索引只有两个值进行哈西,只落在两个分区上,业务写入量比较大,相当于所有的写入压力都压在这两个分区上,导致整个数据库不再有数据扩展能力,不论怎么压,最后这两个分区肯定会首先成为瓶颈,因此需要解决写入热点的bigkey。
PolarDB-X是通过将主键和所有的key一起作为分区键解决问题,当热点出现的时候还能够按照主键对写入热点的分区,做进一步的分裂,从而达到消除热点的目的。使用体验已经和单机数据库非常接近。
11、PolarDB-X分布式数据库和别的分布式数据库不太一样的地方就是能支持将join操作下推到存储节点。下推join本身是对于tp类的业有相对很大收益,t P类的业务不像aP一样join的数据越join越多,join的结果会小一到两个数量级,如果能在D N上提前把join执行掉,返回的数据就会少很多,降低网络的开销。
而分布数据库里网络开销,io开销是大头,所以下推join有比较大的收益,但是下推join有一定的前提条件要求,要求拆分算法和分区分布必须是完全相同的两张表做join,或者一张表一张索引表或者两张索引表,只有当它们的拆分算法和分区分布完全相同的时候,才支持将join下堆到存中节点上,因为PolarDB-X支持分区分裂和迁移,如果不对分区操作做任何限制,那就可能会出现由于分区分裂合并或者分区迁移导致两张表的分布变得不同,进而导致join在下推的情况。从用户的角度会出现,当分区迁移之后,反而导致查询性能下降令人困惑的场景,为了解决问题,PolarDB-X在后台引入了两个新的概念叫做table group和partition group,table group是global index的合集,就是一组全局索引的合集,系统会保证同一个table group中的索引具备相同的数据分布,当发生分区的分裂合并的时候,同一个table group中所有的全局索引会一起进行分裂合并,首先保证了它的数据的分区数量一致。
partition group在同一个group中的global index相同区间的分组都落在一号分区的分组,认为它是一个partition group,当PolarDB-X自动的将分区在不同dn之间迁移的时候,可以保证同一个partition group中的分区始终落在同一个dn上,通过这样的情况就可以保证,同一个partition group的全区索引分区数量一样,并且序号相同的分区都是落在相同的dn上,可以保证join操作下推,通过合理划分table group,table group默认有一个划分方法,也可以通过根据实际使用,可以确认哪些表需要下推,也可以手工的划分table group,通过合理的规划table group可以降低分区迁移操作带来的代价,PolarDB-X使用的table group和partition group用来解决全局索引,join下推优化避免分区变更操作导致join优化失效。
12、PolarDB-X是一个基于存储计算分离和shared-nothing架构的分布式数据库,支持数据高可用和水平扩展。支持强一致分布式事务,基于2PC和TSO+MVCC,通过1PC优化降低提交延迟, TSO合并优化确保GMS不成为瓶颈。通过引入透明全局索引,良好的兼容了单机数据库上索弓的使用体验,解决了shared-nothing架构带来的跨分区查询性能下降问题。
结合分布式事务和透明全局索引两项技术,将设计分区方面的优化,综合为透明分布式技术,使用这项技术尽可能的可以让用户使用单机数据库使用tps分布式数据库,PolarDB-X以分布式事务和透明全局索引为核心的透明分布式技术,能够显著降低用户使用的使用门槛。









