Archive for March, 2008

Heartbeat的切换策略-积分统计方法

Saturday, March 29th, 2008

在V2的Heartbeat中,为了将资源的监控和切换结合起来,同时支持多节点集群,Heartbeat提供了一种积分策略来控制各个资源在集群中各节点之间的切换策略。通过该积分机制,计算出各节点的的总分数,得分最高者将成为active状态来管理某个(或某组)资源。
如果在CIB的配置文件中不做出任何配置的话,那么每一个资源的初始分数(resource-stickiness)都会是默认的0,而且每一个资源在每次失败之后所减掉的分数(resource-failure-stickiness)也是0。如此的话,一个资源不论他失败多少次,heartbeat都只是执行restart操作,不会进行节点切换。一般来说,resource-stickiness的值都是正数,resource-failure-stickiness的值都是负数。另外还有一个特殊值那就是正无穷大(INFINITY)和负无穷大(-INFINITY)。如果节点的分数为负分,那么不管什么情况发生,该节点都不会接管资源(冷备节点)。随着资源的各种状态的发生,在各节点上面的分数就会发生变化,随着分数的变化,一旦某节点的分数大于当前运行该资源的节点的分数之后,heartbeat就会做出切换动作,现在运行该资源的节点将释放资源,分数高出的节点将接管该资源。
在CIB的配置中,可以给每个资源定义一个分数,通过resource-stickiness来设置,同样也可以设置一个失败后丢失的分数,通过resource-failure-stickiness来设置。如下:
<primitive id=”mysql_db” class=”ocf” type=”mysql” provider=”heartbeat”>
<meta_attributes id=”mysql_db_meta_attr”>
<attributes>
<nvpair name=”resource_stickiness” id=”mysql_db_meta_attr_1″ value=”100″/>
<nvpair name=”resource_failure_stickiness” id=”mysql_db_meta_attr_2″ value=”-100″/>
</attributes>
</meta_attributes>

<primitive />
上面的配置就是给mysql_db这个resource配置了两个分数,成功运行的时候所得到的分数(resource_stickiness)和运行失败会丢失的分数(resource_failure_stickiness),两项分数值一样多,成功则得100分,失败则-100分。
除了可以通过给每个资源单独设置两项的分数之外,也可以将所有的resource设置成相同的分数,如下:
<configuration>
<crm_config>
<cluster_property_set id=”cib-bootstrap-options”>
<attributes>

<nvpair id=”default-resource-failure-stickiness” name=”default-resource-failure-stickiness” value=”-100″/>
<nvpair id=”default-resource-stickiness” name=”default-resource-stickiness” value=”100″/>

</attributes>
</cluster_property_set>
</crm_config>

在这个配置中,就是给所有资源设置了两个默认的分数,省去单独每个资源都设置的麻烦。当然,如果在设置了这个default分数之后,同时也给部分或者全部资源也设置了这两个分数的话,将取单独设置的各个资源设置的分数而不取默认分数。
除了资源的分数之外,节点自身同样也有分数。节点分数可以如下设置:

<constraints>
<rsc_location id=”rsc_location_group_mysql” rsc=”group_mysql”>
<rule id=”mysql1_group_mysql” score=”200″>
<expression id=”mysql1_group_mysql_expr” attribute=”#uname” operation=”eq” value=”mysql1″/>
</rule>
<rule id=”mysql2_group_mysql” score=”150″>
<expression id=”mysql2_group_mysql_expr” attribute=”#uname” operation=”eq” value=”mysql2″/>
</rule>
</rsc_location>
</constraints>

注意这里节点分数的设置是放在configuration配置项里面的constraints配置项下的,通过rule来设置。这里是通过节点主机名来匹配的(实际上heartbeat的很多配置中对主机名都是很敏感的)。这里的value值就是节点的主机名,rule里面的score就是一个节点的分数。
通过上面的配置,我们可以作出如下计算:
a、在最开始,两边同时启动heartbeat的话,两边都没有开始运行这个resource,resource本身没有分数,那么仅仅计算节点的分数:
mysql1的分数:node+resource+failcount*failure=200+0+(0*(-100))=200
mysql2的分数:node+resource+failcount*failure=150+0+(0*(-100))=150
heartbeat会做出选择在mysql1上面运行mysql_db这个资源,然后mysql1的分数发生变化了,因为有资源自身的分数加入了:
mysql1的分数:node+resource+failcount*failure=200+100+(0*(-100))=300
mysql2的分数:node+resource+failcount*failure=150+0+(0*(-100))=150
b、过了一段时间,heartbeat的monitor发现mysql_db这个资源crash(或者其他问题)了,分数马上会发生变化,如下:
mysql1的分数:node+resource+failcount*failure=200+100+(1*(-100))=200
mysql2的分数:node+resource+failcount*failure=150+0+(0*(-100))=150
heartbeat发现mysql1节点的分数还是比mysql2的高,那么资源不发生迁移,将执行restart类操作。
c、继续运行一段时间发现又有问题(或者是b后面restart没有起来)了,分数又发生变化了:
mysql1的分数:node+resource+failcount*failure=200+100+(2*(-100))=100
mysql2的分数:node+resource+failcount*failure=150+0+(0*(-100))=150
这时候heartbeat发现mysql2节点比mysql1节点的分数高了,资源将发生迁移切换,mysql1释mysql_db相关资源,mysql2接管相关资源,并在mysql2上运行mysql_db这个资源。这时候,节点的分数又会发生变化如下:
mysql1的分数:node+resource+failcount*failure=200+0+(2*(-100))=0
mysql2的分数:node+resource+failcount*failure=150+100+(0*(-100))=250
这时候如果在mysql2上面三次出现问题,那么mysql2的分数将变成-50,又比mysql1少了,资源将迁移回mysql1,mysql1的分数将变成100,而mysql2的分数将变成-150,因为又少了资源所有者的那100分。到这里,mysql2节点的分数已经是负数了。heartbeat还有一个规则,就是资源永远都不会迁移到一个分数分数是负数的节点上面去。也就是说从这以后,mysql1节点上面不管mysql_db这个资源失败多少次,不管这个资源出现什么问题,都不会迁移回mysql2节点了。一个节点的分数会在该节点的heartbeat重启之后被重置为初始状态。或者通过相关命令来对集群中某个节点的某个资源或者资源组来重置或者查看其failcount,如下:
crm_failcount -G -U mysql1 -r mysql_db        #将查看mysql1节点上面的mysql_db这个资源的failcount
crm_failcount -D -U mysql1 -r mysql_db        #将重置mysql1节点上面的mysql_db这个资源的failcount
当然,在实际应用中,我们一般都是将某一些互相关联的资源放到一起组成一个资源组,一旦资源组中某资源有问题的时候,需要迁移整个资源组的资源。这个和上面针对单个资源的情况实际上没有太多区别,只需要将上面mysql_db的设置换到资源组即可,如下:
[...]

Heartbeat V2 模块分析

Saturday, March 29th, 2008

一、heartbeat模块:
    整个Heartbeat软件的通信模块,各个节点之间的任何通信都是通过这个模块完成。这个模块会根据不同类型的通信启动不同的事件handler,当监听到不同类型的通信请求后会分给不同的handler来处理。这个从整个Heartbeat的启动日志中看出来。
二、CRM:cluster resource manager
    从这个名字就可以看出这个模块基本上就是v2的heartbeat的一个只会中心,整个系统的一个大脑了,他主要负责整个系统的各种资源的当前配置信息,以及各个资源的调度。也就是根据各资源的配置信息,以及当前的运行状况来决定每一个资源(或者资源组)到底该在哪个节点运行。不过这些事情并不是他直接去做的,而是通过调度其他的一些模块来进行。
    他通过heartbeat模块来进行节点之间的通信,调度节点之间的工作协调。随时将通过heartbeat模块收集到的各个成员节点的基本信息转交给CCM某块来更新整个集群的membership信息。他指挥LRM(local resource manager)对当前节点的各资源执行各种相应的操作(如start、stop、restart和monitor等等),同时也接收LRM在进行各种操作的反馈信息并作出相应的决策再指挥后续工作。另外CRM模块还负责将各个模块反馈回来的各种信息通过调用设定的日志记录程序记录到日志文件中。
三、LRM:local resource manager
    LRM是整个Heartbeat系统中直接操作所管理的各个资源的一个模块,负责对资源的监控,启动,停止或者重启等操作。这个模块目前好像支持有四种类型的资源代理(resource agent):heartbeat自身的,ocf(open cluster framework),lsb(linux standard base,其实就是linux下标准的init脚本),还有一种就是stonith。stonith这种我还不是太清楚是一个什么类型的。
    四种类型的resource agent中的前三种所调用的脚本分别存如下路径:
        heartbeat:/etc/ha.d/resource.d/
        ocf:/usr/lib/resource.d/heartbeat/
        lsb:/etc/init.d/
    LRM就是通过调用以上路径下面的各种脚本来实现对资源的各种操作。每一种类型的脚本都可以由用户自定义,只要支持各类型的标准即可。实际上这里的标准就是接受一个标准的调用命令和参数格式,同时返回符合标准的值即可。至于脚本中的各种操作时如何的LRM并不care。
四、PE:CRM Policy Engine
    他主要负责将CRM发过来的一些信息按照配置文件中的各种设置来进行计算,分析。然后将结果信息按照某种固定的格式通过CRM提交给TE(Transition engine)去分析出后续需要采取的相应的action。PE需要计算分析的信息主要是当前有哪些节点,各节点的状况,当前管理有哪些资源,各资源当前在哪一个节点,在各个节点的状态如何等等。
五、TE:Transition engine
    主要工作是分析PE的计算结果,然后根据配置信息转换成后续所需的相应操作。个人感觉PE和TE组合成一个类似于规则引擎实现的功能,而且PE和TE这两个模块只有在处于active的节点被启动。另外PE和TE并不直接通信,而都是通过Heartbeat的指挥中心CRM来传达信息的。
六、CIB:cluster information base
    CIB在系统中充当的是当前集群中各资源原始配置以及之后动态变化了的状态,统计信息收集分发中心,是一个不断更新的信息库。当他收集到任何资源的变化,以及节点统计信息的变化后,都会集成整合到一起组成当前集群最新的信息,并分发到集群各个节点。分发动作并不是自己和各个节点通信,同样也是通过heartbeat模块来做的。
    CIB收集整理并汇总出来的信息是以一个xml格式保存起来的。实际上Heartbeat v2的资源配置文件也就是从haresources迁移到了一个叫cib.xml文件里面。该文件实际上就是CIB的信息库文件。在运行过程中,CIB可能会常读取并修改该文件的内容,以保证信息的更新。
七、CCM:consensus cluster membership
    CCM的最主要工作就是管理集群中各个节点的成员以及各成员之间的关系。他让集群中各个节点有效的组织称一个整体,保持着稳定的连接。heartbeat模块所担当的只是一个通信工具,而CCM是通过这个通信工具来将各个成员连接到一起成为一个整体。
八、LOGD:logging daemon(non-blocking)
    一个无阻塞的日志记录程序,主要负责接收CRM从各个其他模块所收集的相关信息,然后记录到指定额度日志文件中。当logd接收到日志信息后会立刻返回给CRM反馈。并不是一定要等到将所有信息记录到文件后再返回,日志信息的记录实际上是一个异步的操作。
九、APPHBD:application heartbeat daemon
    apphbd模块实际上是给各个模块中可能需要用到的计时用的服务,是通过watchdog来实现的。这个模块具体的细节我还不是太清楚。
十、RMD:recovery manager daemon
    主要功能是进程恢复管理,接受从apphbd所通知的某个(或者某些)进程异常退出或者失败或者hang住后的恢复请求。RMD在接受到请求后会作出restart(如果需要可能会有kill)操作。

DRBD笔记

Saturday, March 29th, 2008

一、主要功能
    DRBD实际上是一种块设备的实现,主要被用于Linux平台下的高可用(HA)方案之中。他是有内核模块和相关程序而组成,通过网络通信来同步镜像整个设备,有点类似于一个网络RAID的功能。也就是说当你将数据写入本地的DRBD设备上的文件系统时,数据会同时被发送到网络中的另外一台主机之上,并以完全相同的形式记录在一个文件系统中(实际上文件系统的创建也是由DRBD的同步来实现的)。本地节点(主机)与远程节点(主机)的数据可以保证实时的同步,并保证IO的一致性。所以当本地节点的主机出现故障时,远程节点的主机上还会保留有一份完全相同的数据,可以继续使用,以达到高可用的目的。
    在高可用(HA)解决方案中使用DRBD的功能,可以代替使用一个共享盘阵存储设备。因为数据同时存在于本地主机和远程主机上,在遇到需要切换的时候,远程主机只需要使用它上面的那份备份数据,就可以继续提供服务了。
二、底层设备支持
    DRBD需要构建在底层设备之上,然后构建出一个块设备出来。对于用户来说,一个DRBD设备,就像是一块物理的磁盘,可以在商脉内创建文件系统。DRBD所支持的底层设备有以下这些类:
    1、一个磁盘,或者是磁盘的某一个分区;
    2、一个soft raid 设备;
    3、一个LVM的逻辑卷;
    4、一个EVMS(Enterprise Volume Management System,企业卷管理系统)的卷;
    5、其他任何的块设备。
三、配置简介
    1、全局配置项(global)
        基本上我们可以做的也就是配置usage-count是yes还是no了,usage-count参数其实只是为了让linbit公司收集目前drbd的使用情况。当drbd在安装和升级的时候会通过http协议发送信息到linbit公司的服务器上面。
    2、公共配置项(common)
        这里的common,指的是drbd所管理的多个资源之间的common。配置项里面主要是配置drbd的所有resource可以设置为相同的参数项,比如protocol,syncer等等。
    3、资源配置项(resource)
        resource项中配置的是drbd所管理的所有资源,包括节点的ip信息,底层存储设备名称,设备大小,meta信息存放方式,drbd对外提供的设备名等等。每一个resource中都需要配置在每一个节点的信息,而不是单独本节点的信息。实际上,在drbd的整个集群中,每一个节点上面的drbd.conf文件需要是完全一致的。
        另外,resource还有很多其他的内部配置项:
        net:网络配置相关的内容,可以设置是否允许双主节点(allow-two-primaries)等。
        startup:启动时候的相关设置,比如设置启动后谁作为primary(或者两者都是primary:become-primary-on both)
        syncer:同步相关的设置。可以设置“重新”同步(re-synchronization)速度(rate)设置,也可以设置是否在线校验节点之间的数据一致性(verify-alg 检测算法有md5,sha1以及crc32等)。数据校验可能是一个比较重要的事情,在打开在线校验功能后,我们可以通过相关命令(drbdadm verify resource_name)来启动在线校验。在校验过程中,drbd会记录下节点之间不一致的block,但是不会阻塞任何行为,即使是在该不一致的block上面的io请求。当不一致的block发生后,drbd就需要有re-synchronization动作,而syncer里面设置的rate项,主要就是用于re-synchronization的时候,因为如果有大量不一致的数据的时候,我们不可能将所有带宽都分配给drbd做re-synchronization,这样会影响对外提提供服务。rate的设置和还需要考虑IO能力的影响。如果我们会有一个千兆网络出口,但是我们的磁盘IO能力每秒只有50M,那么实际的处理能力就只有50M,一般来说,设置网络IO能力和磁盘IO能力中最小者的30%的带宽给re-synchronization是比较合适的(官方说明)。另外,drbd还提供了一个临时的rate更改命令,可以临时性的更改syncer的rate值:drbdsetup /dev/drbd0 syncer -r 100M。这样就临时的设置了re-synchronization的速度为100M。不过在re-synchronization结束之后,你需要通过drbdadm adjust resource_name 来让drbd按照配置中的rate来工作。
五、资源管理
    1、增加resource的大小:
    当遇到我们的drbd resource设备容量不够的时候,而且我们的底层设备支持在线增大容量的时候(比如使用lvm的情况下),我们可以先增大底层设备的大小,然后再通过drbdadm resize resource_name来实现对resource的扩容。但是这里有一点需要注意的就是只有在单primary模式下可以这样做,而且需要先在所有节点上都增大底层设备的容量。然后仅在primary节点上执行resize命令。在执行了resize命令后,将触发一次当前primary节点到其他所有secondary节点的re-synchronization。
    如果我们在drbd非工作状态下对底层设备进行了扩容,然后再启动drbd,将不需要执行resize命令(当然前提是在配置文件中没有对disk参数项指定大小),drbd自己会知道已经增大了容量。
    在进行底层设备的增容操作的时候千万不要修改到原设备上面的数据,尤其是drbd的meta信息,否则有可能毁掉所有数据。
    2、收缩resource容量:
    容量收缩比扩容操作要危险得多,因为该操作更容易造成数据丢失。在收缩resource的容量之前,必须先收缩drbd设备之上的容量,也就是文件系统的大小。如果上层文件系统不支持收缩,那么resource也没办法收缩容量。
    如果在配置drbd的时候将meta信息配置成internal的,那么在进行容量收缩的时候,千万别只计算自身数据所需要的空间大小,还要将drbd的meta信息所需要的空间大小加上。
    当文件系统收缩好以后,就可以在线通过以下命令来重设resource的大小:drbdadm — –size=***G resize resource_name。在收缩的resource的大小之后,你就可以自行收缩释放底层设备空间(如果支持的话)。
    如果打算停机状态下收缩容量,可以通过以下步骤进行:
        a、在线收缩文件系统
        b、停用drbd的resource:drbdadm down resourcec_name
        c、导出drbd的metadata信息(在所有节点都需要进行):drbdadm dump-md resource_name > /path_you_want_to_save/file_name
        d、在所有节点收缩底层设备
        e、更改上面dump出来的meta信息的la-size-sect项到收缩后的大小(是换算成sector的数量后的数值)
        f、如果使用的是internal来配置meta-data信息,则需要重新创建meta-data:drbdadm create-md resource_name
        g、将之前导出并修改好的meta信息重新导入drbd(摘录自linbit官方网站的一段导入代码):
            drbdmeta_cmd=$(drbdadm -d dump-md test-disk)
            ${drbdmeta_cmd/dump-md/restore-md} /path_you_want_to_save/file_name
        h、启动resource:drbdadm up resource_name
六、磁盘损坏
    1、detach resource
        如果在resource的disk配置项中配置了on_io_error为pass_on的话,那么drbd在遇到磁盘损坏后不会自己detach底层设备。也就是说需要我们手动执行detach的命令(drbdadm detach resource_name),然后再查看当前各节点的ds信息。可以通过cat /proc/drbd来查看,也可以通过专有命令来查看:drbdadm dstat resource_name。当发现损坏的那方已经是Diskless后,即可。如果我们没有配置on_io_error或者配置成detach的话,那么上面的操作将会由自动进行。
        另外,如果磁盘损坏的节点是当前主节点,那么我们需要进行节点切换的操作后再进行上面的操作。
    2、更换磁盘
        当detach了resource之后,就是更换磁盘了。如果我们使用的是internal的meta-data,那么在换好磁盘后,只需要重新创建mata-data(drbdadm create-md resource_name),再将resource attach上(drbdadm attach resource_name),然后drbd就会马上开始从当前primary节点到本节点的re-synchronisation。数据同步的实时状况可以通过 /proc/drbd文件的内容获得。
        不过,如果我们使用的不是internal的meta-data保存方式,也就是说我们的meta-data是保存在resource之外的地方的。那么我们在完成上面的操作(重建meta-data)之后,还需要进行一项操作来触发re-synchnorisation,所需命令为:drbdadm invalidate resource_name 。
七、节点crash(或计划内维护)
    1、secondary节点
        如果是secondary接待你crash,那么primary将临时性的与secondary断开连接,cs状态应该会变成WFConnection,也就是等待连接的状态。这时候primary会继续对外提供服务,并在meta-data里面记录下从失去secondary连接后所有变化过的block的信息。当secondary重新启动并连接上primary后,primary –> secondary的re-synchnorisation会自动开始。不过在re-synchnorisation过程中,primary和secondary的数据是不一致状态的。也就是说,如果这个时候primary节点也crash了的话,secondary是没办法切换成primary的。也就是说,如果没有其他备份的话,将丢失所有数据。
    2、primary节点
        一般情况下,primary的crash和secondary的crash所带来的影响对drbd来说基本上是差不多的。唯一的区别就是需要多操作一步将secondary节点switch成primary节点先对外提供服务。这个switch的过程drbd自己是不会完成的,需要我们人为干预进行一些操作才能完成。当crash的原primary节点修复并重新启动连接到现在的primary后,会以secondary存在,并开始re-synchnorisation这段时间变化的数据。
        在primary节点crash的情况下,drbd可以保证同步到原secondary的数据的一致性,这样就避免了当primary节点crash之后,secondary因为数据的不一致性而无法wcitch成primary或者即使切换成primary后因为不一致的数据无法提供正常的服务的问题。
    3、节点永久性损坏(需要更换机器或重新安装相关软件的情况)
        当某一个节点因为硬件(或软件)的问题,导致某一节点已经无法再轻易修复并提供服务,也就是说我们所面对的是需要更换主机(或从OS层开始重新安装)的问题。在遇到这样的问题后,我们所需要做的是重新提供一台和原节点差不多的机器,重新开始安装os,安装相关软件,从现有整提供服务的节点上copy出drbd的配置文件(/etc/drbd.conf),创建meta-data信息,然后启动drbd服务,以一个secondary的身份连接到现有的primary上面,后面就会自动开始re-synchnorisation。
八、split brain的处理
    split [...]

时光飞逝,岁月如梭

Wednesday, March 26th, 2008

那么快阳春三月就要过去了,不带一点痕迹。
 真的感叹!

烦人的 “Recost for ORDER BY”

Friday, March 21st, 2008

起因是一个SQL走错了索引
SELECT ROWID AS rid
FROM tab_1 s
WHERE s.c_id = :1
ORDER BY s.gmt_r DESC
执行计划如下
——————————————————————————————
| Id  | Operation                                 |  Name                   | Rows| Bytes| Cost |
——————————————————————————————
|   0 | SELECT STATEMENT                  |                             |   858| 25740| 138K|
|* 1 |  TABLE ACCESS BY INDEX ROWID | tab_1                     |  858| 25740| 138K|
|* 2 |   INDEX FULL SCAN DESCENDING [...]

4层的索引,可以索引多少rows?

Tuesday, March 18th, 2008

比如一个index entry 是18bytes,一个8k的block,PCTFREE=10%,则可以存放8*1024*0.9/18=410,约为400个index entries
如果一个表有100,000,000 rows,那么需要250,000 leaf blocks
在算算branch:每个branch 记录了每个leaf的最大值和address,大约16 bytes。这样可以存储大约450 个 branch index entries
也就是说需要250000/450=556个branch block。
但是556个branch又需要多少个再上一级的branch block 来管理呢?
因为一个branch最多存储450个leaf entries,所以至少又需要2个branch blocks。
然而这两个branch blocks,还需要一个block来管理,这个block就是root 了。
BLEVEL 1 = 1 branch block
BLEVEL 2  = 2 branch blocks
BLEVEL 3 = 556 branch blocks
Leaf level(BLEVEL 4) = 250,000 leaf blocks
Table rows = 100,000,000 rows
所以一个4层的索引,至少就可以管理1亿条记录。

10G的compress表测试

Thursday, March 13th, 2008

做了个10G中的压缩表测试,发现压缩比例还是比较高的,关键看数据是否按照重复性高的列在排序
也对压缩表和非压缩表查询进行了对比,测试结果表明解压还是存在不少的CPU消耗,时间居然比普通表要久,
但数据很多是经过cache的,相信在IO比较多的查询上,IO量的节省带来的效益应该可以盖过CPU计算产生的消耗
  
cnckweblog@DWALI> create table nocompress_10G as select * from all_objects;
Table created.
cnckweblog@DWALI>create table compress_10G nologging compress as select * from nocompress_10G;
Table created.
cnckweblog@DWALI> select SEGMENT_NAME,sum(BYTES) from user_extents where segment_name in (’NOCOMPRESS_10G’,’COMPRESS_10G’) group by SEGMENT_NAME;
SEGMENT_NAME                   SUM(BYTES)
—————————— ———-
NOCOMPRESS_10G                 2134900736
COMPRESS_10G                    639631360
全表扫描,压缩表比较有优势
cnckweblog@DWALI>select count(*) from nocompress_10G;
  17995776
1 row selected.
Elapsed: 00:00:05.69
cnckweblog@DWALI>select count(*) from compress_10G;
  17995776
1 row selected.
Elapsed: 00:00:01.50
owner列应该是高度压缩的,选择他会引起CPU的消耗,结果压缩表比较慢
cnckweblog@DWALI>select count(*) from nocompress_10G where owner = [...]

一致读的步骤

Wednesday, March 12th, 2008

供参考:
1. Read the Data Block.
2. Read the Row Header.
3. Check the Lock Byte to determine whether there’s an ITL entry.
4. Read the ITL entry to determine the Transaction ID (Xid).
5. Read the Transaction Table using the Transaction ID. If the transaction has been committed and has a System Commit Number less than the query’s System [...]

阿里巴巴DBA出品