如何在 PolarDB-X 中进行 Online DDL_大数据开发_技术课程_开发者学堂

阿里云
为了无法计算的价值
打开APP
阿里云APP内打开
开发者社区> 开发者学堂> 全部课程> 如何在 PolarDB-X 中进行 Online DDL

如何在 PolarDB-X 中进行 Online DDL

2课时 |
60人已学 |
免费
课程介绍

PolarDB-X动手实践系列课程


第一讲:如何一键部署开源 PolarDB-X
第二讲:开始使用 PolarDB-X开始使用PolarDB-X
第三讲:如何将 PolarDB-X 与大数据等系统互通
第四讲:如何对 PolarDB-X 集群做动态扩缩容
第五讲:如何在 PolarDB-X 中进行 Online DDL
第六期:如何在 PolarDB-X 中优化慢 SQL
第七讲:用 PolarDB-X + Flink 搭建实时数据大屏
第八讲:用 PolarDB-X 搭建一个高可用系统
第九讲:PolarDB-X Replica原理和使用

 

【技术图谱】PolarDB-X动手实践系列:https://developer.aliyun.com/graph/polardbx

如何在PolarDB - X中进行Online DDL

 

内容介绍

一、内容简介

二、如何在PolarDB - X中进行Online DDL

三、PolarDB - X online DDL的原理

 

一、内容简介

今天是讲PolarDB - X动手实践系列的第五期,在PolarDB - X里面怎样做ddl这件事,首先讲一下动手实践系列包括的内容,讲的是PolarDB - X社区版,也就是开源的版本,对标的是阿里云商业版本的2.0这样一个版本,面向的群体是应用开发者,架构师,dba等等,对这个产品使用感兴趣的群体,内容会围绕使用PolarDB - X整个过程当中场景化的方式还讲解怎么使用PolarDB - X 。

 

 

按照使用流程前面已经讲了四节课,第一节课讲了如何使用PolarDB - X部署PolarDB - X,之后讲了怎么连接使用它,然后讲了一个与Binlog相关的,之后讲了扩缩容,今天讲解DDL。

 

二、如何在PolarDB - X中进行Online DDL

1.环境准备

今演示需要的环境和之前差不多,最后是需要另外两个常见开源的系统,一个叫做普罗米修斯,另外一个是官方的,这两个做系统监控.

 

 

在讲解实验之前先讲一下PolarDB - X系统的架构,PolarDB - X是一个分布式的数据库,分布式会有若干个组件来组成,具体来说,有四个核心的组件,第一个是cn,也就是分布式的计算层他会负责数据的分片,拆分的计算,以及分布事务协调这样一个角色。第二个是DN也就是存储节点,为了便于理解,大家可以简单的把它理解为mySQL,只不过是定制化的mySQL,第三个是GMS也就是原数据中心,会提供全局的时间戳维护全局的原信息等等这样一个功能,也可以将它理解为特殊角色的DN,最后一个是cdc全局Binlog日志的生成的组件,提供和mySQL Binlog完全兼容的增长日志。

 

2.演示

了解完前面这些内容之后,来说一下今天分享的内容,今天分享内容包括两个部分,第一个是演示,第二个是讲解一下原理,演示会看小图

 

是这样的一个场景,首先假设业务用了PolarDB - X这样的一个数据库,业务层是用Sysbench的压测来模拟业务的操作,具体来讲就是的Sysbench的select的ID的查询以及select key就是另外一个非主件的查询,这两类查询来作为业务的负载,然后搭建了一个监控的链路,就是PolarDB - X监控的数据他涂到了普罗米修斯里面,然后再有普罗米修斯给到官方进行最终的展示,因为这个过程需要一些资源,所以证明依然按照惯例在阿里云上面ECS来进行所有组件的部署,通过这台电脑连到ECS进行相关的操作,今天演示的场景就与DDL相关,比如在业务发展的过程中最常用的DDL,想加列,加表,删表或者是加索引,删索引,在分布式的场景下,还会涉及到一些独有的DDL比如加全局IP索引然后可能你会将一个单表转成一个分布式的表,将一个单表整为广表等等这样的DDL操作今天都会演示到,这是演示的场景,讲完这些,下面就进入今天的正题,正式开始演示。

 

先要连接上ECS,这个ECS是一直用来做演示的ECS,进入今天的课程目录,首先看一下要建立的PolarDB - X的结构,今天依然使用K8S的部署模式来部署PolarDB - X

 

这是他的配置文件,集群的名字叫做online DDL,有一个cdc的节点,一个cn的节点,两个dn的节点,是这样的结构,为什么有一个cn因为后面监控链路目前开源的版本还没有完全做好,目前多个cn聚合的链路还没有打通,所以就只能采集一个cn,为了便于演示把cn设置成一个,这个集群之前已经提前建好了

 

可以看到他现在是一个running的状态,里面有一个gms,一个cn,两个DN和一个cdc,接下来做一个端口转发,把这个PolarDB - X的集群的3306这个端口给转发过来,转发完以后就可以在终端里面用mySQL client去连接上PolarDB - X,接下来再转发另外一个接口,其实在现在的PolarDB - X开源的版本中,已经有了监控数据采集的组件,他的数据可以直接吐到普罗米修斯里面,所以把监控组件8081的端口也转发出来,这样普罗米修斯端口可以去8081去收集监控数据,首先连接上ECS,然后再做端口转发

 

这样就是两边有了端口转发的行动,接下来看一下是在同样的ECS下已经部署好的general界面

 

现在普罗米修斯已经开始采集PolarDB - X集群里面的数据,general也会将它展示出来,大概过个30秒的样子,这个页面的监视数据就都出现了,因为功能还没有完全做完所以大家只需要关注这几个监控值,第一个是这里的逻辑的QPS当前系统的QPS是多少,第二个是rt Cdc端口的rt目前是多少现在是1.42毫秒,还有就是QPS监控的曲线图和这里rt的曲线图,这里主要关注这几个内容,监控数据已经开始正常采集了,再打开一个新的shell,去连接上PolarDB - X给大家看看连线的都有什么

 

可以看到这里面是直接使用mySQL plant去连接上PolarDB - X

目前除了系统原信息的库外没有其他的库,首先来创建一下今天演示所需要的库,

 

 

可以看到目前这个库是空的,接下来准备一下业务所需要的数据,也就是Sysbench的压测所需要的一些数据,同样这边已经把需要的一些配置写好了,准备数据的配置文件与上节课的是一样的,启动了一个K8 s的任务,这个任务用了一个Sysbench连接好了一个镜像,这个镜像会连接Online DDL的PolarDB - X的集群,在里面会创建一张表,同时在里面写16万行的数据,这是准备的过程,现在来创建一下任务

 

可以看到,目前正在创建当中,看一下port里面的运行情况。

 

速度比较快,现在这个任务已经运行完了,

Sysbench这张表已经创建,看一下这张表的表结构

 

可以看到这张表结构还是比较简单的,有四列,第一列是ID,接口分别是K C pad,这样的四列,其中ID和k上面加了索引,这张表是一个分表通过hash ID表的拆分,可以看一下它的topology结构,show topology from这是PolarDB - X特有的一个C口的语法,sbtest1可以看到这张表在分库里的一些分布

可以看到当前一共有16个分库,每个分库里面都有一个分表,这样就有16张表,这是目前的情况,再来看一下数据是否正确的写进去了

可以看到已经有了,接下来开始业务的流量,业务的流量用了两个Sysbench的脚本,第一个脚本是他的select ID也就是在主件上一个select查询,第二个是一个select K也就是用他的k作为一个while条件做了查询,一会儿可以给大家看一下具体的SQL是什么,现在想把对应的查询跑起来,首先来创建select的ID任务

可以看到select ID任务已经跑起来了,他目前的tps大概在八千七八这样的位置。

 

时可以看到监控数据里面也开始收到tps的数据,Tps会有一个从原来的15到现在8700的一个快速的提升,就是主件查询。现在再来增加另外一个业务的负载adninepolardb - X - demo ~/ class -5[ SIGTNT ]> kC apply - f Sysbench - select - k . yanl现在来创建一个这样的任务

 

因为这个k并没有命中到主件的索引,所以现在查询的效率比较低,有一千多一点点这样的QPS,再来看一下另外一个任务,就是select的主件

他目前有1700,有一个陡降,原来是有8700加了负载之后降到了1700左右,说明新加的没有命中索引的select k本身还是非常耗系统资源的,因为他自己rt相对来说比较高,系统整体的负载占的比较多,所以就会导致整个系统目前QPS降到了2800大概这样一个水平,假如现在就是目前系统的状态,现在跑了一个数据库的系统,它叫做PolarDB - X,里面有一张表,它里面有几列,业务也正常在跑了,业务有两类的负载,第一个是基于主件的查询,第二个是基于非主件的查询,目前系统的QPS是在2800左右,来看一下select具体的SQL是什么,接近他写出来方便为大家展示,简单来说的就是一个select,Select pad这一列都从sbtest这张表,where是一个非常简单的ID等于什么都查询。

 

另外一个selectk是什么,他其实是将这张表所有的列选出来

这就是他的SQL,为了便于大家继续向后看操作,再来看一下sb test这张表的结构

它里面有四列,ID是他的索引列,Kc pad这样四列,是一个分表。业务的第一个需求通常是加一个列,所以目前为了演示PolarDB - X online DDL这样的能力,所谓的online就是业务正常在跑的时候,做一些DDL变更,先做一个简单的操作,在sb test1这张表里面加一列,例如现在基于业务需求加一个新的列叫做X,在业务上可能有某些意义,他还是一个char类型,长度是20,将他加在c之后,现在开始做加列的操作

,在这个中需要贯穿两件事情,第一个是ddl本身会不会报错,第二个是qps监控的曲线,如果出现了一个抖动,说明对业务的影响比较大,如果没有抖动,说明确实做到了Online这样一个效果,用了4.54秒就将这个列加好了,看下面的曲线,没有太明显的波动,稍微等一下,因为监控有一些延迟,可以看到他一直维持在2800的水平没有抖动,符合我们的预期,再来看一下这张表当前的结构

可以看到k列已经成功的加了进去,现在将他给删除

执行,完成,也用了四秒多就执行完成,可以看到qps依然没有大的变化,曲线也没有出现太大的波动,说明ddl过程对业务影响确实很小,接下来做一些比较重要的操作,例如现在业务量上涨了,这个系统的QPS不能满足我们的需求了,经过刚才简单分析之后,K列本身是没有命中的,所以导致整个系统的询效率非常低,PolarDB - X支持全局索引这样的功能,所以你可以把它看成单机数据库里的索引,所以如果查k列慢简单的操作就是把这列加入一个索引的列.

 

所以用加索引的语句给sb test1表加一个索引,加索引的语法是与mySQL保持一致的,前面再加一个global就是全局了,给他起一个名字叫做K2,全局索引时需要指明拆分的方式,用k做一个拆分,这样就创建一个全局二级索引K2是用hash k进行拆分的,对这个操作的同时观察下面曲线的抖动的情况,因为这个操作际上需要再建一个索引表把现有的这张表里的数据需要复制过去一部分,所以这里面会有数据拷贝的过程,执行时间就比较长,用了十七点七秒,执行完成之后再看一下当前系统到q PS也就是下方的曲线,可以看到q PS向上爬升,原来是在2800这样一个水平

现在到了5200水平,有一个接近两倍的提升,也就是说现在select k开始命中全局二级索引,这样就会将系统的QPS变得比较高,同时可以看到右边的rt也降低了,同时PolarDB - X支持二级索引里面支持去除索引,也就是可以在额外的把一些其他的列的内容拷贝到索引表里面,如果你的查询所有的列都在索引里面索引表本身就可以查到所有的数据,不需要进行回到主表这样的过程,减少一次查询,这样会让你的询rt更低,系统的效率会更高一些,那么现在来创建一个这样的索引,他的语法是这样的,首先在加一个全局二级索引,他的名字叫做K3,后面加一个,覆盖到哪些列,比如我们把所有列都覆盖去,按照刚才的经验,这个ddl也要七秒的样子,稍微等一下.

 

执行完成,再来关注一下系统的QPS以及rt的情况,可以看到q PS提高到了8000左右,Rt也有了一个进一步的降低,从2:23毫秒降低到了1.70毫秒,这就意味着select k这样的查询已经完全命中索引,并且从全局二级索引中获取到了所有的数据,减少了一次不必要的查询,所以系统的qps再次提高,这个过程都是符合预期的,在PolarDB - X 里面可以在线的做一些ddl的变更,这些简单的加列减列,还是添加索引,删除索引等等,

对于业务的影响都是非常小的,那么PolarDB - X 还支持一些更高级低调的操作,例如现在可以将这样一个拆分的表转化成单表,比如业务在设计之初可能会这张表设计的数据量非常大,后来随着业务的变化,发现并不需要分表,只需要一个单表就可以了,那么PolarDB - X 是支持将分表转成单表的DDL操作,同时online也可以进行在线变更,接下来做这样一个演示,这个演示大家主要关注ddl是否能够正常的执行成功,语法是这样的

这里有个提示,从分表转化成单表时需要全局的二级索引给删掉,所以现将他给删掉,先将K2的删掉,然后再把K3删掉,这样全局二级索引就删掉了,再次尝试将分表转化成一个单表

看一下这个ddl会执行多久,这个ddl也涉及一个数据拷贝的过程,所以相对来说需要一些时间,22秒就完成了,再来看一下这张表的topology结构,可以看到他现在已经变成了单裤,在一个库里面存在,之前最早的时候是在16个库里面都进行分布,在可以演示一下,把它变成分库分表。语法如后面,alter table sbtest1 dbpartition by hash ( id );这次用了30秒,看一下这张表的topology结构

可以看到,又回到了当时的样子,分布在16个分库当中

看一下showds,可以看一下我们有几个存储,例如dn是0101可以看到他有两个dn,分别是零和1,然后这些库有哪些库是分布在dn上,有哪些是分布在DN0上,可以通过这个指令去看一下。这个demo就到此结束了,简单进行总结一下,一开始有一个库,还有一张表Sb test1,用Sysbench的Select  ID Select  k模拟流量,然后开始做ddl的一些操作,这个ddl操作包括加列,减列,添加二级索引,删除二级索引,保单表转成分表,把分表转成单表的这样一些操作,这些操作过程中的Sysbench流量也就是业务流量正常的执行以及监控曲线有没有一个明显的抖动,ddl本身完成这个也是PolarDB - X,下面简单讲解一下PolarDB - X online DDL的原理

 

三、PolarDB - X online DDL的原理

 

  1. mySQL online DDL基本原理

 

 

在了解这个之前,先看一下mySQL数据库,也就是单机数据库是怎么做的,以加索引这样一个ddl为例,还要看一下mySQL做一个online ddl的过程,mySQL做ddl分为三个步骤,初始化执行,最后完成数据的更新这样三个步骤。

加索引是在第一步会获取目标表上的MDL,也就是共享的锁,之后会进入一个执行的阶段,执行阶段首先会获取互斥的一个表,拿到这个锁之后,其他的 DDL,md l都不能再访问这张表的原数据,拿到这个锁之后,他会等待因为这个MDL要等前面的比如还有一些未完成的事务,这个时候他要等那些事务结束,才能够获取到这个互斥的md l的锁,其实就是等待之前使用旧版本的原数据的所有事物都结束这样一个机制,等完之后拿到这个锁就开始准备中间的一些内部的数据结构,这边叫做rowlog,可以简单的理解为记录索引的数据的变更的这样的数据结构,这些准备工作做完之后,把锁再次降低,降低之后,后续的dml就可以访问这张表的原数据了,拿到的还是与原来数据一样的结构,也就是在这个阶段会拿一个互斥锁,这个锁会等待之前所有的事物结束,同时会阻塞后面的DML,这是一个点,等他开始构建索引就是拷贝数据执行完包括把存量的数据复制到索引表以及把rowlog里面的增量数据全部应用到这张索引表,之后有一部应用,最后一块rowlog,这个时候他要再次进行一次加锁,这样的一个过程,同时禁止这张表里面相关的写入,也就是会达到一个这样的效果,最后一块rowlog里面的增量写入了,他把这部分数据在应用的索引表,应用完以后可以看成索引表里面的数据与原表的数据完全的一致了,之后再次释放这样一个锁,释放完成之后,完成了数据的更新,在这个过程中可以看到mySQL online DDL里面有三个地方加了一个md l互斥锁,加的过程会对业务有一些影响,所以即使每次mySQL online 有了DDL这个能力,通常在业务做运维时,如果对mySQL online做ddl操作时需要在业务的低峰期进行。

 

2.F1 online schema change 基本原理

来看一下PolarDB - X 里面是怎样做这件事情的,PolarDB - X 是一个分布式系统,怎么不是系统相对于单机来说引入了新的复杂度,目前业界比较主流的一种在分布式系统里面实现schema变更的方案是.F1 online schema change这样一个方案,他解决的问题是,如果你的原信息从老版本直接切换到新版本就像单机里面那样操作的话,会引入一些新的问题,这个就是为了解决这些问题的,一两个例子来看一下都有哪些问题,首先来看这张图最上面的左面,这是第一个问题,他的问题就是如果将数据直接从老版本切换到新版本会有什么问题,老版本是没有索引的,还是讲创建索引这样一个场景,如果老版本的schema里面这张表的这个列是没有索引的,在新的版本中加了索引,因为是一个分布式的系统,所以会有多个cn的节点,比如这张图里面会有两个cn的节点,这两个cn的节点没有办法同时进行原数据的生效,就会存在一段时间在cn0里面已经拿到了最新版本的原数据,他已经知道了这张索引表的存在,在老的cn1里面他还是拿到老版本的schema,这是他依然认为这张表是没有索引的,这个时候比如在业务上面来了两个请求,在cn1上是insert的请求是先到的,Cn1就会根据新版本的schema信息认为有索引,他会同时的往主表索引表插入两条数据来保证数据的一致性,这个时候cn1上面就收到了另外一条delete语句,他删除的刚好是前面的Cn 0的insert那条语句插入的数据,因为cn1上拿的还是老版本的schema信息,所以他并不知道索引表的存在,就只是把主表上的这条数据进行删除,这样就会导致索引表里面多出了一条数据,他在主表里面是找不到原来的数据的,这就是第一个问题。

 

第二个问题就是上面这张图的右边,还是一个类似的场景,如果cn1收到的是一个insert语句,因为上面还是一个老版本的schema信息,他不知道索引表,所以他只向主表去插入一条记录,这就会导致索引表相对于主表少了一条数据,这样两边还是有不一致的问题,这就是如果我们将老版本schema与新版本的schema之间直接切换引入的众多问题当中的其中两个问题。

在 online schema change这个论文里面,为了解决这个问题,引入了两个阶段,就是从索引不存在到索引存在这中间又加入了两个新的状态,叫做delete only,write only这样的两个状态,原来直接从不存在到存在切换会引入问题,那么经过严格的证明从不存在到delete only到write only再到可见也就是public这个过程就保证了不会出现这个问题,再来看一下是如何避免这个问题的。针对于刚才所讲的问题一,来看上面这张图的左边,还是刚才的场景,Cn 0和cn1拿到了不同版本的schema的信息,这个时候cn零就收到了一个insert语句,他拿到的是新版本的schema信息,此时因为拿到新版本对索引的描述是delete only,也就是不能向里面insert数据,Delete only本身就是这样的,所以cn 0拿到这样的insert语句之后指望主表去insert表数据,没有向索引表insert一条数据,那么这个时候如果cn1里面收到了delete语句,因为他拿的是老版本,他认为只有一个主表的存在,索引表此时还没存在,他就对主表里面的数据进行删除,这个时候大家发现最终主表和索引表里面的数据是一致的,也就是规避了第一个问题。

 

再来看一下是如何回避第二个问题的,例如现在的cn 0和cn 之间的schema信息都往前迭代,cn0已经到了delete only版本,C N1还是在上一个版本,此时cn 1收到了一条insert语句,那么按照delete only的约束是不能往索引表insert的数据的虽然他知道索引表存在,但是它不能向里面写数据,依然只向主表去写了一份数据,这个时候看上去索引表是少一个条数据的,但是没关系,再接下来的从whiteonly变成public这一步中间还有一个数据回填的过程,所谓的数据回填就是将图标里面的数据陈亮的复制到索引表的一个过程,在复制的过程当中,因为刚才cn1 insert里面的那条数据已经在主表存在了,所以在回填的过程中会将这条数据复制到索引表,也就解决了刚才所说的问题二,这就是论文的大概过程,同时他还会有一些其他问题,里面都会有一个详细的论述。

简单来说就是这篇论文解决了一个问题,在分布式的系统当中原数据怎么从老版本变成一个新的版本,在变化的过程当中对业务是没有影响,对数据也是保持能够一致的,能够保证一个这样的效果。

 

3.PolarDB - X online DDL的基本流程

 

PolarDB - X 是基于那篇论文的一个思想来做原数据版本的变更,但是在他的基础之上做了一些优化,来时online的效果更online,就是没有阻塞业务的点,第二个是有些机制在保证,如果在执行DDL过程当中系统上任何节点挂了这个DDL本身是能够恢复的,也就是ddl也保证了ccID,首先来看一下在PolarDB - X 里面用户提交了一个ddl大概的处理流程,首先在这张图的最左边用户提交了一个ddl,他可能提交给了一个节点,这个节点就会把ddl这个任务写到gms,就是前面讲到的原数据中心里面,写成一个ddl任务,这些cn当中会有一个主节点,主节点查到这个任务之后会分派,最终把这个任务分配给系统当中能够执行这个ddl worker的节点,worker节点就会做与原数据相关的一些变更,比如先把dn版本里面的一些信息schema进行改变,现在dn里面也做一些相应的修改,做完之后会把gms里面的数据做一个更新,在这个过程当中他就会因为原数据变更的过程是一个阶段性的,比如从刚才的delete only到write only再到public会涉及到几次原数据版本的切换,每次结婚都会用到刚才所说的online schema change那样的流程来进行变更,这个工作全部是在ddl worker中进行的,例如在这里从初始的状态切换到delete only的时候,这个ddl worker中间有一部就是做完gms相关数据变更之后就会开始通知所有的其他的cn,现在的元素就已经从原来的状态变为了delete only状态,开始通知到所有cn的节点,这些cn节点都会在一段时间之后获取到最新的schema信息,等所有的cn节点都已经加载到了新版本之后,就开始切换到下一个状态,也就是delete only这个过程也类似,直到最后完成索引的添加,这个过程在上节课简单的提到过。涉及到这个过程里面具体的一部分,比如说具体怎么原数据完成变更之后怎么通知到所有cn节点让他们完成加载的。

 

 

首先还是以创建索引ddl,发起了创建ddl这样的一个操作,ddl worker节点也完成了相关的索引表以及原数据表的更新,之后开始通知所有的节点加载到新的schema信息,比如现在node 0 worker会通知到Node1,Node收到通知新版本加载之后,会首先检查本地上面的信息是不是已经让加载的信息更新了或者跟他一样,如果大于等于这个版本他可以忽略这个通知,如果不是比他旧就继续往下走这个流程,继续往下走,这里有部分流程应该是写错了,再往下首先应该去gms加载到原数据最新信息,加载完成之后就完成了节点里面缓存的schema信息,同时在这个节点上还运行着其他一些事务,这些事物里面可能用的还是版版本的schema,所以他要等那些用老版本信息的事物全部执行完成之后,才告诉worker节点已经彻底完成了schema的切换,这个时候他用了一种机制,PolarDB - X自己设计的机制叫mdl加索引这个机制,他会去尝试获取这个节点上面的老版本schema信息的mdl的一个锁,这个锁是一个排他锁,如果有事物持有了老版本的共享锁这个排他锁就会阻止,直到把前面的所有事务完成,一旦在这里面获取到这个锁,就意味着使用老版本的那些事物已经执行完成了,这个时候就通知到worker节点,Note1已经完成了新版本数据的加载,这个简单来说就是通知所有的节点加载原数据的一个过程,但是真实的逻辑要比这个复杂,比如要处理一个节点,没有响应怎么办这样的问题,如果ddl本身挂了怎么恢复,一系列非常复杂的过程,如果对这个过程感兴趣,可以去看一下代码。

 

总结一下PolarDB - X online DDL就是会有这样几个特点

第一个是不会对事物执行引入额外开销,这样保证系统的一个效率。

第二个是在没有异常节点的情况下,就是没有cn没有响应或者是挂了没有这样异常的情况下,schema版本变更不影响事务执行,并且通常没有数据回填的ddl操作毫秒级完成,果存在异常节点,系统仅仅阻止异常节点上的事务提交,其他节点不受影响,无数据回填的ddl操作分钟完成,MDL锁不阻塞后续DML这个与前面mySQL的dml是不一样的,mySQL加dml互斥锁会影响后续dml操作,处于等待的状态,在PolarDB - X 里面经过设计之后是不会阻塞的。

同时支持分布式md l死锁检测,同时很重要的一点是PolarDB - X 里面有acid这样一个事务保障。