Oracle数据库技术实用详解学习笔记:Log Buffer

时间:2022-10-28 08:16:42

现代关系数据库的一个重要特性就是事务,它有4个特性,也就是ACID,分别是原子性、一致性、隔离性、持久性,通过事务才能保证数据的准确。

在系统正常运行时,由于事务机制的保护,能保持数据的一致和准确。但有时事务机制是无效的,比如,由于事务机制是由oracle程序来实现的,当系统突然断电时,整个系统就停机了,程序也就停止运行了,所以事务机制起不了作用;再如,oracle或操作系统的程序bug,导致数据库内部逻辑结构损坏、磁盘介质损坏,事务机制不可能控制磁盘的操作是否正确,也不知道oracle中代码是否有bug,因为事务机制本身也只是oracle程序代码中的一部分。所以,这些都很有可能导致数据的错乱。

 

那怎样才能彻底保证数据的准确性能呢?

其实自然就能想到,如果在修改数据之前,把所有的操作都记录下来,并持久化到日志文件中,那么在出现问题时,就可以通过查询文件的记录,对系统进行修复。

比如,客户端发送过来一个update语句,那么服务器进程首先会解析语句,生成执行计划。接下来开始执行语句,首先看数据是否在内存,如果不在内存,那么从磁盘中读取数据放到内存,在修改数据之前,首先由服务器进程把修改的操作记录到文件中,接下来服务器进程开始修改内存中的数据,修改完成后提交事务,这个提交事务的操作也记录到文件,这时系统突然断电了,而dbwn进程还没有把修改过的数据写入磁盘本来这个就会导致数据的丢失,也就是事务提交了,但磁盘上的数据并没有改过来,而客户端以为数据修改就成功了,所以就导致了数据的不一致。

这时,DBA会重启主机,启动oracle数据库,于是开始把日志文件中的相关操作重新做一遍,于是那些在系统停机之前没有改过来的数据,就被修改过来,数据被恢复。

 

那么日志会记录哪些操作,采用什么方式来记录呢?

所谓日志,就是把数据库所有改变数据块的操作,都原原本本的记录下来,这些操作不仅包括:对数据表的DML命令,还有引起数据字典变化的DDL命令,还有索引的改变,以及对回滚段的数据块的改变。

oracle在记录日志的方式上,采用了逻辑和物理相结合的方式。也就是说,oracle针对每个数据块,记录了插入某个值或删除某个值的描述语句。比如,有个update更新了100个数据库,则会针对每个数据块记录一对delete旧值和insert新值的语句,当然还会记录这个数据块的物理地址,也就是说一共有100数据块的物理地址、100对语句。

 

日志是记录在哪里,又是如何持久化的呢?

为了临时存放所产生的日志信息,Oracle在SGA中开辟了一块内存区域,这个区域就是日志缓冲区log buffer。

不过日志缓冲区只是日志信息临时存放的区域,这个区域是有限的,而且每个数据库都是可以循环使用的,那么日志缓冲区的内容必须要写入磁盘的文件中,才能永久保留下来,这个文件就是联机日志文件。在每个日志缓冲区的日志块被重用之前,这个日志块必须先写入磁盘上的联机日志文件,每个日志缓冲区中的日志块对应到日志文件中的一个日志块。

当满足一定条件后,oracle通过使用名为LGWR的后台进程将log buffer中的日志信息,按照发生的先后顺序写入联机日志文件,来实现持久化,这些条件包括:

a、前台进程触发,一种是当发出commit或rollback语句时,需要触发LGWR将内存中的日志信息写入联机日志文件,这是由先写日志的机制决定的。一种是在日志缓冲区中找不到足够的内存来存放日志信息时,也会触发LGWR进程将一部分日志信息写入联机日志文件,从而使得这部分缓冲区能够被重用。

b、每隔3秒,LGWR启动一次。

c、在DBWn启动时,如果发现脏数据块所对应的重做记录,还没有写入联机日志文件,那么DBWn会触发LGWR进程,并等待LGWR写完以后才开始继续。

d、日志信息的数量达到了整个日志缓冲区的1/3时,就会触发LGWR。

e、日志信息的数量达到1MB时,也会触发LGWR。

 

如何保证联机日志的安全性,日志写满后又如何处理?

为了保证联机日志文件的安全,一般至少使用两个日志文件,组成一个日志文件组。由于日志缓冲区中的日志块会同时写入日志文件组中的每个日志文件,所以同一个日志文件组中的日志文件内容是一样的。

但是只要数据库一天不停止运行,就会不断产生日志信息,就不断写入联机日志文件,而文件又不可能无限大,所以联机日志文件总会有写满的时候。因此,联机日志文件与日志缓冲区一样,也会循环使用的,在几个文件中循环的写入,当一个日志文件写满后,会转换到另一个日志文件继续写的过程,这就是日志切换 log switch。

如果所有的联机日志文件都写满怎么办呢?

当一个联机日志文件写满时,可以选择把这个文件归档为脱机日志文件,这个脱机的文件就是归档日志文件。本质上这个文件就是个副本,就是将写满的联机日志文件复制到预先指定的目录。在生产数据库环境,应该选择归档的方式,这样在联机日志归档以后,这个联机日志才能继续重复使用。

 

2、log buffer的内存构成

上面说到,日志缓冲区用来存放事务对数据块的变化的日志信息。那么这些日志信息到底包含了哪些内容,是由哪些结构组成的呢?

oracle记录数据库变化的最小单位是改动向量change vector,它描述了对数据库中任何单个数据库所做的一次改动,

包含了:

a、被改动的数据块的地址

b、被改动的数据块的版本号、

c、事务操作代码等。

这里的数据块可以是表、索引、回滚段,但不是临时表空间的临时段,因为不会生成改动向量。

 

当多个改动向量按照先后顺序组合在一起,从而完成了对数据库的一次原子改动,这组改动向量就是重做记录redo record。一个事务会产生一个或多个重做记录,而oracle在用日志恢复数据库时,是以事务作为恢复的最小单位。

所以这3者的关系就是:一个事务对应多个重做日志,而一个重做日志对应多个改动向量。

日志缓冲区就是许多重做记录按照先后顺序组成的,日志文件也是由许多重做记录按照先后顺序排列一起而组成的文件。

 

下面举例说明产生重做记录和改动向量的过程,如在执行这个语句时:update 表名 set 字段名 = 'xxx' where id = x,会产生3个修改操作,也就是对应3个改动向量:

a、回滚段的段头中事务表的改动。往事务表中插入一条记录,包含的信息:被修改的数据块的地址、事务的状态(commit或active)、该事务所使用的回滚段的地址。这个insert操作,就会产生改动向量:事务表中的被改动的数据块的地址、这个数据块的版本号、事务操作代码(也就是上面讲到的日志的记录方式中的sql语句)。

b、将修改前的旧值放到回滚段的数据块,导致回滚段中数据块的改动。这个insert操作,产生的改动向量:被修改的回滚段中的数据块的地址、版本号、事务操作代码。

c、将修改后的新值放到表的数据块,导致对表中的数据块的改动。这个修改操作,产生的改动向量:被修改的表的数据块的地址、版本号、事务操作代码。

 

上面的update操作,产生了一个重做记录,包含了3个改动向量,当然可能还有其他情况会产生新的重做记录,比如,修改的列有索引,则必须修改索引,这时产生了第二个重做记录,包含了多个改动向量,用来描述对索引的数据块的修改。如果事务commit或rollback,需要更新事务表的这个事务的状态,就会产生第三个重做记录,包含了1个改动向量,用来记录对回滚段中事务表的修改。

 

3、log buffer的内部管理机制

日志缓冲区的内部管理分为两部分,一部分是生成重做记录,另一部分是重做记录写入日志文件。

下面举例说明:

假设会话1发出更新语句;update  test  set column = 'abc'   where  id = 1;

那么oracle首先会找到id = 1 的记录所在的数据块放入buffer cache,然后在回滚段的段头的事务表中插入要修改的数据块地址、事务状态、要用到的回滚段的数据块地址,把旧的值放入回滚段的数据块中,把新值放入要修改的数据块,同时生成重做记录。

假设会话2发出更新语句:udpate  test_a set column  = ‘xxx’  where   id = 2;

那么oracle首先会找到id = 2 的记录所在的数据块放入buffer cache,然后在回滚段的段头的事务表中插入要修改的数据块地址、事务状态、要用到的回滚段的数据块地址,把旧的值放入回滚段的数据块中,把新值放入要修改的数据块,同时生成重做记录。

这时,会话1又发出更新语句:yupdate  test  set column = 'abc'   where  id = 2; 并提交。

每次提交,系统都会产生一个scn号,scn号越小,说明发生的越早,那么重做记录就排在越前面。另外,一旦用户发出commit,系统就触发LGWR进程,那么LGWR会把所有重做记录都写入联机日志文件中,这些重做记录也包含还没有提交事务的重做记录。

在LGWR写这些重做记录的同时,又有其他会话发出了更新语句,并提交。那么这时LGWR在写完刚才的第一次commit的重做记录后,就会马上开始写第二次commit的重做记录,写完后,这2次所写的重做记录所对应的日志缓冲区内存,就被释放了。

 

 4、log buffer的设置

log buffer的大小,可通过设置初始化参数log_buffer来控制,单位是字节。日志缓冲区会进一步分为多个块,每个块的尺寸与操作系统中一个块的尺寸相同,都是512字节,我们可以通过如下方式获取日志缓冲区的块尺寸:

SELECT DISTINCT lebsz AS redo_block_size FROM x$kccle;

也可以通过下面的方式来计算日志缓冲区的块尺寸,不过不是很准确:

SELECT round((a.redosize + b.redowast) / c.redoblks) + 16 AS redo_block_size
FROM
( SELECT value AS redosize FROM v$sysstat WHERE name='redo size' ) a,
( SELECT value AS redowast FROM v$sysstat WHERE name='redo wastage') b,
( SELECT value AS redoblks FROM v$sysstat WHERE name='redo blocks written') c

对于日志缓冲区来说,设置太小,容易引起log buffer space等待事件。但也不是越大越好,设置过大,由于LGWR进程会不断把日志缓冲区的日志写入联机日志文件,所以可能根本用不上多余的空间,从而导致浪费。

 

在设置日志缓冲区时,可以考虑这个建议的公式来计算:1.5 * ( 平均每个事务产生的重做记录大小 * 每秒提交的事务数量 )

--1.系统总的事务数
SELECT a.value AS trancount
FROM v$sysstat a
INNER JOIN v$statname b
ON a.statistic# = b.statistic#
WHERE b.name = 'user commits';


--2.系统总的运行时间
SELECT TRUNC(SYSDATE - startup_time) * 24 * 60 * 60 AS seconds
FROM v$instance;


--3.所有重做日志大小
SELECT value AS redoblocks
FROM v$sysstat
WHERE name = 'redo blocks written';


平均每个事务产生的重做记录大小 = redoblocks / trancount

每秒提交的事务数量 = trancount / seconds

所以日志缓冲区的大小为:1.5 * (redoblocks / trancount) * (trancount / seconds) = 1.5 * (redoblocks / seconds)