一. 概述 我们知道,在oracle中,每修改一条数据都会生成一条重做数据(也就是redo,里面记录了修改后的内容)。目的就是为了将修改的数据备份,方便今后重做。现在有一个问题。oracle中只要修改数据,都会生成redo,这些redo会存放在一个叫做重做日志缓冲区里面。如果同时多个回话在修改数据,都要往重做日志缓冲区写入内容,就存在为同一片内存区域竞争的问题。存在竞争,就存在开销,这篇文章大概介绍一下,oracle如何尽量降低这种开销。 二. 问题概述 oracle中不断地修改数据,源源不断地生成重做日志。oracle先将这些日志写入到重做日志缓冲区(一片内存区域),然后调用LGWR进程将这些日志写入磁盘上的联机日志文件(一般是三个,一个50M的样子)。一个联机日志文件写满之后,切换到另一个日志文件。如果oracle开启了归档模式,在切换日志文件的时候,会将上一个写满的日志文件拷贝一份,叫做归档日志文件,保存到一个地方(或许是另外一台服务器,以防意外发生,方便做实例恢复或介质恢复)。看起来一切都很完美,问题就出在将日志写入到重做日志缓冲区,如果并发量大,并且每一个回话都修改很多数据,那么对重做日志缓冲区的竞争就会异常激烈。很有可能导致cpu会花费大量时间在latch自旋上(也就是占用了cpu的时间,但是cpu什么都没干)。 三. 解决方案 问题1: 一个会话不停地修改数据,需要不停地往redo buffer(重做日志缓冲区)写入内容,也就是要不断地获取redo allocation latch(保护重做日志缓冲区的一种锁,控制并发)。非常影响效率,特别是高并发的时候,你不一定能获取到锁。 这里是否可以先把所有的redo生成,然后就获取一次redo allocation latch锁?答案是可以的,oracle中10g版本之后,提供了一种 private redo,也就是为每一个事物分配一个私有的redo buffer,你先在这里生成所有的重做日志。当事务提交的时候,获取一次redo allocation latch锁,将私有redo里面的内容,拷贝到公共redo buffer里面去。 引申 问题2:如果不停地修改数据,就会不停地生成undo数据块,undo数据块的改变也会生成redo buffer。而且这些必须跟描述数据改变的redo buffer成对出现,而且必须同时写入到重做日志文件。原因如下:当将一个脏数据块,写入磁盘数据块的时候,必须要求先将对应描述这条数据改变的redo记录和记录这条数据旧数据的undo数据块对应的redo记录写入到磁盘的重做日志文件。否则就保证不了数据的一致性。oracle在没有引入private redo之前,将描述数据块改变前的undo块当做一般的数据块处理,undo块发生改变,也生成redo,同时输出到重做日志文件。以前的这种机制存在如下问题:如果另一个会话需要到undo块里面寻找旧数据,但是undo块已经被刷新输出到磁盘,那么还需要到磁盘去调取出来,存在一定的I/O开销。同时,在引入private redo之后,无法保证描述一条数据改变的redo和undo一起刷新输出到重做日志文件。 为此,oracle引入了一个叫做IMU机制,也就是在内存区域另外为一个事务开辟一处私有的内存区域,专门用来存放描述undo数据块改变的redo记录。一个事务如果修改了10条数据,就会生成10条undo数据,从而产生10条描述undo改变的redo记录,存放在IMU里面。另外,加上描述数据本身改变的10条redo记录存放在private redo里面。总共是20条redo记录,如果事务提交的时候,就会将这20条redo记录合并成一条,从IMU和private redo拷贝到公共的redo buffer。当然,如果事务还没有提交,但是dbwn进程需要将其中3条脏数据刷新输出到磁盘,就会从imu和private redo里面分别取到这三条记录对于的redo记录,总共6条,合并成一条,然后拷贝到公共的redo buffer,进而刷新输出到磁盘。等到redo 刷新输出到磁盘成功,dbwn才能将脏数据刷新输出到磁盘。 为此,如果我们一个事务修改了很多数据,oracle的大概操作如下: 1. 获取成对的私有内存结构(也就是private redo:用来存放描述数据块的改变 和 IMU:用来存放描述对应undo数据块的改变),开启事务 2. 修改数据,为每一个受影响的数据块打上标记(以表示“拥有私有内存结构”),但不真正改变数据。 3. 将每一条还原改变向量(也就是描述undo数据改变)写到IMU池。 4. 将每一条重做改变向量(也就是描述数据块改变)写到私有redo区。 5. 将这两个内存结构的向量合并成一条重做改变记录。 6. 将重做改变记录复制到重做日志缓冲区(也就是公共 redo buffer),并改变这个数据块。(这么看来,我上面写的,事务还没结束,dbwn进程将脏数据写入磁盘是不可能发生的,因为,最后才改变数据块)
|