2.3 LOG BUFFER 和LGWR 的算法
了解LOG BUFFER 和LGWR 的算法,有助于我们分析和解决相关的性能问题,因此我们需要花一点时间来了解LOG BUFFER 相关的基本算法。用一句话来概括,LOG BUFFER是一个循环使用的顺序型BUFFER。这句话里包含了两个含义,一个是LOG BUFFER 是一个顺序读写的BUFFER,LOG BUFFER 数据的写入是顺序的;第二个含义是LOG BUFFER是一个循环BUFFER,当LOG BUFFER 写满后,会回到头上来继续写入REDO LOG 信息。
LOG BUFFER 数据的写入是由前台进程完成的,这个写入操作是并发的,每个前台进程在生成了REDOLOG 信息后,需要首先在LOGBUFFER 中分配空间,然后将REDOLOG 信息写入到LOGBUFFER 中去。在LOG BUFFER中分配空间是一个串行的操作,因此Oracle 在设计这方面的算法的时候,把LOG BUFFER 空间分配和将REDO LOG 数据拷贝到LOG BUFFER 中这两个操作分离了,一旦分配了LOG BUFFER 空间,就可以释放相关的闩锁,其他前台进程就可以继续分配空间了(这里所说的前台进程只是一个泛指,是为了表述方便而已,读者一定要注意,因为后台进程也会对数据库进行修改,也需要产生REDO LOG 信息,后台进程的REDO 操作和前台进程是大体一致的)。
前台进程写入REDO 信息会使LOG BUFFER的尾部指针不停的向前推进,而LGWR这个后台进程不听的从LOG BUFFER 的头部指针处开始查找还未写入REDO LOG 文件的LOG BUFFER 信息,并将这些信息写入REDO LOG 文件中,并且将BUFFER 头部指针不停的向后推进,一旦LOG BUFFER 的头部指针和尾部指针重合,那么就说嘛了当前的LOG BUFFER 是空的。
而如果前台进程在LOG BUFFER 中分配空间会使LOG BUFFER 的尾部指针一直向前推进,一旦LOG BUFFER 的尾部指针追上了LOG BUFFER 的头部指针,那么说明目前LOG BUFFER 中无法分配新的空间给后台进程了,后台进程必须要等候LGWR将这些数据写入REDOLOG 文件,然后向前推进了头部指针,才可能再次获得新的可用BUFFER 空间。这个时候,前台进程会等待LOG FILE SYNC 事件。
为了让LGWR 尽快将LOG BUFFER 中的数据写入REDO LOG 文件,以便于腾出更多的空闲空间,Oracle 数据库设计了LGWR 写的触发条件:
1. 用户提交
2. 有1/3重做日志缓冲区未被写入磁盘
3. 有大于1M的重做日志缓冲区未被写入磁盘
4. 每隔3 秒钟
5. DBWR 需要写入的数据的SCN大于LGWR记录的SCN,DBWR 触发LGWR写入。
当事务提交时,会产生一个提交的REDO RECORD,这个RECORD 写入LOG BUFFER 后,前台进程会触发LGWR 写操作,这个时候前台进程就会等待LOG FILE SYNC 等待,直到LGWR 将相关的数据写入REDOLOG 文件,这个等待就会结束,前台进程就会收到提交成功的消息。如果我们的系统中,每秒的事务数量较大,比如几十个或者几百个,甚至大型OLTP 系统可能会达到每秒数千个事务。在这种系统中,LGWR由于事务提交而被激发的频率很高,LOG BUFFER 的信息会被很快的写入REDO LOG 文件中。
而对于某些系统来说,平均每个事务的大小很大,平均每个事务生成的REDO LOG 数据量也很大,比如1M 甚至更高,平均每秒钟的事务数很少,比如1-2 个甚至小于一个,那么这种系统中LGWR 由于事务提交而被激发的频率很低,可能导致REDO LOG 信息在LOG BUFFER 中被大量积压,oracle 设计的LOG BUFFER 中数据超过1M 的LGWR 激发条件就是为了解决这种情况而设计的,当LOG BUFFER 中的积压数据很多时,虽然没有事务提交,也会触发LGWR 将BUFFER 中的数据写入REDO LOG 文件。
除此之外,Oracle 还通过了_LOG_IO_SIZE 这个隐含参数来进一步控制LGWR 写操作,当LOGBUFFER 中的数据超过了这个隐含参数的规定的大小,也会触发LGWR 被激发。这个参数的缺省值是LOGBUFFER 大小的1/3,这个参数单位是REDO LOG BLOCK。这个参数可以控制当LOG BUFFER 中有多少个数据块被占用时,就要触发LGWR 写操作,从而避免LOG BUFFER 被用尽。
如果一个系统很空闲,很长时间都没有事务提交,LOG BUFFER 的使用也很少,就可能会导致LOG BUFFER 中的数据长期没有被写入REDO LOG 文件,带来丢失数据的风险,因此Oracle 还设计了一个LGWR 写的激发控件,设置了一个时间触发器,每隔的尾部已经追上了LOG BUFFER 的头部,那么前台进程就要等待LGWR 进程将头部的数据写入REDOLOG 文件,然后释放LOGBUFFER 空间。这���时候,没有做提交操作的前台进程都会等待LOG FILE SYNC 事件。这种情况下,加大LOG BUFFER 就可能可以减少大部分的LOG FILE SYNC 等待了。
加大LOG BUFFER 的大小,可能会带来另外一个问题,比如LOG BUFFER 从1M 增加到30M(关于LOG BUFFER 是否需要大于3M 的问题,以前我们已经多次讨论,因此在这里不再讨论了,大家只需要记住一点就可以了,LOG BUFFER 大于3M 浪费空间,对性能影响不大的观点是错误的),那么_LOG_IO_SIZE 自动会从300K 增加到10M,在一个平均每秒事务数较少,并且每个事务的REDO SIZE 较大的系统中,触发LGWR 写操作的LOG BUFFER 数据量会达到1M。
一般来说,在一个大型的OLTP 系统里,每次LGWR 写入REDO LOG 文件的大小在几K 到几十K 之间,平均LOG FILE SYNC 的时间在1-10 毫秒之间。如果平均每次写入的数据量过大,会导致LOG FILE SYNC 的等待时间变长。因此在这种情况下,就可能需要设置_LOG_IO_SIZE 参数,确保LOG FILE SYNC 等待不要过长。
如果每次写入REDO LOG 文件的数据量也不大,而LOG FILE SYNC 等待时间很吵,比如说超过100 毫秒,那么我们就要分析一下REDOLOG 文件的IO 性能了,如果REDO LOG 文件IO 性能不佳,或者该文件所在的IO 热点较大,也可能导致LOGFILE SYNC 等待时间偏大,这种情况,我们可以查看后台进程的LOG FILE PARALLEL WRITE 这个等待事件,这个等待事件一般的等待时间为几个毫秒,如果这个等待事件的平均等待时间较长,那么说明REDO LOG 文件的IO 性能不佳,需要将REDOLOG 文件放到IO 量较小,性能较快的磁盘上。
在OLTP 系统上,REDO LOG 文件的写操作主要是小型的,比较频繁,一般的写大小在几K,而每秒钟产生的写IO 次数会达到几十次,数百次甚至上千次。因此REDO LOG文件适合存放于IOPS 较高的转速较快的磁盘上,IOPS 仅能达到数百次的SATA 盘不适合存放REDO LOG 文件。另外由于REDO LOG 文件的写入是串行的,因此对于REDO LOG文件所做的底层条带化处理,对于REDO LOG 写性能的提升是十分有限的。