Oracle Logical Standby SQL Apply Architecture

这篇文章的本意不是讨论logical standby,而是源于一个问题的讨论。最近,我们在做redo解析工具,目前redo已经解析得差不多了,现在需要把解析出的SQL应用的目标数据库中。我们知道,数据库在同一时刻,有很多事务在并发执行,而事务与事务之间是有相关性的。通过解析redo,我们实际上得到的是一个数据变化的“流”(或者可以看作一个SQL队列)。如果我们要保持在目标数据库上事务的顺序与主库完全一致,最简单的方法是用一个单一进程按照事务提交的顺序进行处理,假设有3个事务,commit的时间顺序为:T1,T2,T3,我们在解析redo的时候,如果没有发现commit,我们不会将SQL应用到目标数据库,当发现T1事务commit时,这时把T1事务的所有SQL全部应用并提交,然后是T2和T3。这种方法保证了事务提交的顺序与主库完全一致,而不用分析事务之间的相关性。但是这个方法的性能很差,由于只有单进程处理,而且只有在发现commit时,才能提交这个事务的所有SQL,如果T1事务很大,会堵塞T2和T3事务。看来这个方法行不通,我们先看看别人是怎么考虑这个问题的。

如何实现并行的SQL Apply系统?其实最关键的问题是分析事务之间的相关性,我们看看Oracle logical standby是如何实现的。

Reader:读取归档日志。

Preparer:将block change转换为LCR(logical change records),存放在shared pool的LCR cache中。

Builder:将LCR按照事务分组。

Analyzer:分析事务相关性。

Coordinator:为Applier分配事务。

Applier:应用LCR,commit。

从这张图,我们看到只有Preparer和Applier是并行的,其他都是单一进程处理。事务通过Preparer分析后,暂存在SGA的LCR cache中,通过Analyzer分析事务相关性,就可以把相关的事务分配给同一个Applier来应用,而不相关的事务就可以并行执行。

事务在应用时被分成两类:小事务和大事务,小事务在commit时一次执行,而大事务则被分成很多的Transaction Chunks,分批应用。这样做一是为了节省LCR cache,另外可以减少大事务在commit时等待的时间。

在SQL Apply的时候,也提出了检查点的概念。如果一个事务长时间不提交,而SQL Apply意外中止,那么要重新读取之前的archived redo log以获取这些信息,这是代价很高的,所以Oracle每隔一段时间会checkpoint这些未提交的事务,并记录SCN,如果这些事务最终回滚,则SQL Apply会将他们删除。

通过DBMS_LOGSTDBY.APPLY_SET(’PRESERVE_COMMIT_ORDER’)可以设置事务执行的顺序,设置为true,则所有事务的提交顺序都与主库一致,设置为false,则只保证相关的事务的一致性。如果logical standby需要运行报表系统,对事务的一致性要求很高,需要设置为true。如果不要求事务的时间一致性,设置为false,性能会更好。

Oracle并没有详细介绍其内部实现细节,我们也无从得知,但是大致的原理应该大家都差不多。很明显,这套方案对于我们来说,尤其是分析事务的相关性和并发事务的分配,实现有些困难,毕竟我们还不是那么专业。所以我们实现了一个简单的方案,不分析事务的相关性,利用多个连接模拟SQL并发应用。还是刚才的例子,如果有T1,T2,T3三个事务并发执行,则我们通过三个并发连接根据SQL的执行时间顺序来处理,由于有多个进程同时处理,则不会出现大事务堵塞的情况,而且可以保证事务提交的时间一致性。但是在这个方案中,在同一时刻还是只有一个进程在处理,从宏观上看还是一个串行的系统,性能肯定要比完全并行的差一些。

从这个具体的问题跳开来看,其实解决问题的思路在很多地方都是相通的。这也是我一直倡导的从点到面,逆向思考的学习方法。学习知识,也许一开始我们关注的是某个命令,到后来我们关注其实现的原理,通过逆向思考的过程,理解其设计原理并不断得到启发,最终达到一通百通,无招胜有招的境界。

Kenny the movie

–EOF–

2 Responses to “Oracle Logical Standby SQL Apply Architecture”


  1. 1 jacky

    加入了一个表格,结果导致BLOG在IE下显示不正常,因为我一直用firefox,所以没发现。

  2. 2 askwan

    经典!学习了!

Leave a Reply