感兴趣的还是记得去看原帖子,笔记有删减,原作者微信公众号:
在oracle数据库中,redo扮演着一个重要的角色(实际上任何关系型数据库都有类似的概念),包括我们在进行数据库恢复时,比如实例恢复,就涉及到redo。
那么,redo写入是如何来实现的呢?这其实是一个相当复杂的过程,但是我会尽可能用简洁的语言告诉大家。
首先开始之前,我们需要来了解一个基本的概念:
1. 理解oracle事务的的几个原则:
ACID原子性(Atomicity)
一致性(Consistency) --涉及到一致性读,后面章节会详细讲解
隔离性(Isolation,又称独立性)
持久性(Durability)
在这里,我主要描述下最后一个,持久性:比如在这些场景中:某些数据块被某些事务修改了,在被写入到disk上之前,如何发送内存错误?或者软件错误?甚至是主机出现错误比如reboot了。
针对这些场景,那么如何去保证用户已经commit的数据不丢?这就oracle为什么引入这个持久性。那么持久性是如何定义的呢 ?
实际上就是当oracle知道事务已经commit后,必须保证你commit后的数据是永久可用的。当然,如果说你redo损坏了,或者数据文件损坏了,那就另当别论了。
这里主要是针对比如内存错误等之类问题。那么oracle是如何通过哪几方面来实现这个持久性的呢 ?
1>memory 方面,为了实现更好的可伸缩性和高并发,oracle引入了redo buffer。理论上讲,尽可能的实现每次都将commit的block都flush到disk中。
不过,很多时候,如果业务量很大,那么这个可能也是一个硬件瓶颈,比如IO。
2>避免介质失败,关于这点,主要是通过进行冗余来实现,比如我们进行存储级别的raid或利用asm failgroup等等。
3>logging记录
通过logging记录操作变更,其实有很大好处,可以记录数据库操作变更的历史信息,而且引入redo buffer后,还可以避免 memory 挥发性错误,
因为oracle数据库中有一个日志写优先的说法,比如我们更改一个block,首先会在redo buffer进行分配,并记录去变更信息,当事务commit后,不必去flush buffer cache,
而只需要将redo buffer中的脏数据写入到redo logfile就行了。我们知道flush buffer cache的代价是很大的。
4>complete 失败
如果整个系统都出现损坏,那么针对这样的场景,oracle本身也提供了解决方案,比如dataguard,实际上本身我们的数据就可以进行冗余,并可以分别存放在不同的阵列上,当然,这个会增加成本。
2. redo的写入过程
在介绍oracle redo的写入过程之前,我们需要来了解下redo logging的几种方式。
2.1)redo logging的几种方式
什么是logging?直白一点讲,就是产生redo记录的过程
--logical: 逻辑上是指那些被执行的sql语句会产生redo
通常也被称为操作logging。具体的操作比如我们的dml。每个操作都会被记录到一个record。虽然说每个操作都是记录在record里面,
但是要记住,一个事务可能涉及到多个record。oracle里面 实例恢复的进行redo重做apply的操作单元其实是以事务来进行的,如果某个事务失败了,
那么涉及到的 所有相关record apply都会失败。当然每种操作类型是通过opcode来标识的。
--physical: 物理上是指有多少byte发生了改变
通常是block级别,比如block 内容的变更甚至整个block的 copy。这里实际上有点类似undo里面存的block前镜像
--physilogical: 这实际上是上面2种的结合.
这实际上是一种学术上的称呼。从结构上来讲,redo record其实就是一个或多个的 change vector(改变向量) 组成的。
一个change vector就是一个单独的physical block change。再直白一点,就是一个change vector对应数据库里面一个data block的改变。
然而,每个record又表示数据库状态的改变(事务的改变),所以被认为是physical+logical的结合。
2.2) oracle中事务的持久性原则
在oracle数据库中,对于一个事务,oracle是如何来保证其持久性的呢 ?
--- log before data
--- lgwr before dbwn
--- flush log on commit
--- checkpoint before(redo log 重用)
针对上面4种涉及到的原则,我们进行分别描述:
1>log before data:日志写优先
简单的讲就是任何数据库块发生改变时,必须先写入redo 数据(实际上是redo buffer).
其目的是什么呢?
保证任何一个数据库发生改变后,都有对应的redo 信息。显然,这样有利于我们在进行实例恢复进行replay apply。
2>lgwr before dbwr: lgwr优先于dbwn
在某个时候,如果dbwn进行需要将buffer cache中的脏块写入到disk上,那么这个时候需要进行判断。如果该脏块对应的redo信息还在redo buffer中(还未被写入到redo log中)时,
是不允许dbwr进程去写的。只有那些在redo buffer中不存在的block才能被dbwr写入到disk中。当然,如果在这个时候,dbwn进行必须进行写操作,那么会触发lgwr进行去flush log buffer的。
然后dbwn进程再进行操作(此时dbwn进程会进行等待,直到lgwr进程将log buffer中的数据写入到redo logfile中才开始操作)。这实际上也是为了保证第一个原则
3> flush log on commit:
无论什么时候,只要事务commit,那么其redo buffer中对应的redo信息就将被写入到redo logfile中
实际上这个操作的完成要等到redo buffer中的信息都被写入到redo log中后才算完成。当然这里也涉及到存储的io性能问题。
我们知道redo也属于io密集型文件,所以我们尽可能的将redo logfile存放到io较快的disk上。不然,在压力较大的系统上,可能会出现大量关于redo io相关的等待.
4> checkpoint before redo reuse:
理解这一点其实很简单,就是说在redo logfile被重用之前,会触发检查点,会确保该redo logfile相关的所有脏块从buffer cache中写入到dbf 文件
实际上这里是ckpt进程发出消息通知dbwn进程去写,直到完成后,该redo才能被重用。这里大家可以想象,如果dbwn进程不够或者写能力不足,是不是都有可能导致等待呢 ?
2.3) 通过实验来展示redo 的写入过程
大概是如下一个过程:
1. user process (proc1) 进行操作,例如dml操作某个表;
2. buffer cache中对应的脏块在被dbwN进程写入到disk之前,这些block对应 change vector(改变向量) 会被copy到redo buffer中。
3. proc1 进程获得redo copy latch;表明该进程正在copy 内容到log buffer中。
4. proc1 进程获得redo allocation latch;因为进程需要copy 内容到log buffer中,那么必然要在log buffer中分配相关的空间,所以必须获得该latch;
5. proc1 进程从pga temporary buffer中copy change vectors到log buffer中。
6. proc1 进程完成copy以后,释放redo allocation latch;
7. proc1 进程释放redo copy latch;
8. proc1 进程post lgwr进程去flush log buffer.另外如果相关的事务已经提交或者达到log buffer写的要求时,lgwr进程会自己唤醒然后去把log buffer的内容写到redo logfile。
9. 当然,lgwr进程在写log buffer之前会先获得redo writing latch,每次写完以后会释放该latch
针对上面latch的顺序,简单总结一下,就是一个如下的流程:
server process在pga中产生redo entry
--> server process获取redo copy latch
--> (注意,redo copy latch存在多个) server process 获取redo allocation latch
--> 在log buffer中分配空间
--> 释放redo allocation latch
--> server process将redo entry信息写入到log buffer中
---> 当server process完成操作之后,释放redo copy latch.
2.4) lgwr写触发机制
--- 事务commit
--- log buffer脏数据大小超过1m
--- log buffer脏数据超过1/3 大小
--- every 3s
--- 检查点触发(文件、ts等检查点)
--- buffer cache 脏数据达到一定比例也会触发lgwr写redo,然后来确保刷脏
---session 1
开启strace跟踪lgwr进程:
strace -fr -o /tmp/16178.log -p 16178
---session 2
随便进行一系列的dml操作,如下:
SQL> delete from t1;
36281 rows deleted.
SQL> commit;
Commit complete.
SQL> alter system switch logfile;
System altered.
SQL>
[root@test fd]# ls -ltr
总用量 0
lrwx------ 1 oracle oinstall 64 10月 9 19:32 9 -> /home/oracle/app/oracle/product/11.2.0/db/dbs/hc_orcl.dat
lr-x------ 1 oracle oinstall 64 10月 9 19:32 8 -> /dev/zero
lr-x------ 1 oracle oinstall 64 10月 9 19:32 7 -> /proc/16178/fd
lr-x------ 1 oracle oinstall 64 10月 9 19:32 6 -> /home/oracle/app/oracle/product/11.2.0/db/rdbms/mesg/oraus.msb
lr-x------ 1 oracle oinstall 64 10月 9 19:32 5 -> /dev/null
lr-x------ 1 oracle oinstall 64 10月 9 19:32 4 -> /dev/null
lr-x------ 1 oracle oinstall 64 10月 9 19:32 3 -> /dev/null
lrwx------ 1 oracle oinstall 64 10月 9 19:32 266 -> /oradata/orcl/test3.dbf
lrwx------ 1 oracle oinstall 64 10月 9 19:32 265 -> /oradata/orcl/test2.dbf
lrwx------ 1 oracle oinstall 64 10月 9 19:32 264 -> /oradata/orcl/test01.dbf
lrwx------ 1 oracle oinstall 64 10月 9 19:32 263 -> /oradata/orcl/users01.dbf
lrwx------ 1 oracle oinstall 64 10月 9 19:32 262 -> /oradata/orcl/undotbs01.dbf
lrwx------ 1 oracle oinstall 64 10月 9 19:32 261 -> /oradata/orcl/sysaux01.dbf
lrwx------ 1 oracle oinstall 64 10月 9 19:32 260 -> /oradata/orcl/system01.dbf
lrwx------ 1 oracle oinstall 64 10月 9 19:32 259 -> /oradata/orcl/redo06.log
lrwx------ 1 oracle oinstall 64 10月 9 19:32 258 -> /oradata/orcl/redo05.log
lrwx------ 1 oracle oinstall 64 10月 9 19:32 257 -> /oradata/orcl/redo04.log
lrwx------ 1 oracle oinstall 64 10月 9 19:32 256 -> /oradata/orcl/control01.ctl
l-wx------ 1 oracle oinstall 64 10月 9 19:32 2 -> /dev/null
lr-x------ 1 oracle oinstall 64 10月 9 19:32 11 -> /home/oracle/app/oracle/product/11.2.0/db/rdbms/mesg/oraus.msb
lrwx------ 1 oracle oinstall 64 10月 9 19:32 10 -> /home/oracle/app/oracle/product/11.2.0/db/dbs/lkORCL
l-wx------ 1 oracle oinstall 64 10月 9 19:32 1 -> /dev/null
lr-x------ 1 oracle oinstall 64 10月 9 19:32 0 -> /dev/null
[root@test fd]#
GROUP# THREAD# SEQUENCE# BYTES BLOCKSIZE MEMBERS ARCHIV STATUS FIRST_CHANGE# FIRST_TIME NEXT_CHANGE# NEXT_TIME
---------- ---------- ---------- ---------- ---------- ---------- ------ -------------------------------- ------------- ------------------- ------------ -------------------
4 1 1 2147483648 512 1 YES ACTIVE 7502172 2024-10-09 17:28:55 7508113 2024-10-09 19:30:56
5 1 2 2147483648 512 1 NO CURRENT 7508113 2024-10-09 19:30:56 2.8147E+14
6 1 0 2147483648 512 1 YES UNUSED 0 0
[oracle@test:/home/oracle]$ more /tmp/16178.log | grep pwrite
16178 0.000138 pwrite64(257, "\1\"\0\0\f\t\1\0\1\0\0\0\20\200\0227\234\2\0\0\5\201\0\0w\220r\0\1\0\377\377"..., 1024, 34740224) = 1024
16178 0.000184 pwrite64(257, "\1\"\0\0\16\t\1\0\1\0\0\0\20\200\237[\250\16\0\0\r\0\0\0|\220r\0\1\0\22\7"..., 1048576, 34741248) = 1048576
16178 0.004366 pwrite64(257, "\1\"\0\0\16\21\1\0\1\0\0\0(\201\330U\"\0\0\0\n\0\27\0\330&\0\0\3\1\17\0"..., 16384, 35789824) = 16384
16178 0.000174 pwrite64(257, "\1\"\0\0.\21\1\0\1\0\0\0\20\200\231\257p\0\0\0\5\0\0\0}\220r\0\1\0\33\0"..., 208384, 35806208) = 208384
16178 0.000308 pwrite64(257, "\1\"\0\0\305\22\1\0\1\0\0\0\20\200e.p\0\0\0\5\0\0\0~\220r\0\1\0\22\7"..., 1048576, 36014592) = 1048576
16178 0.004772 pwrite64(257, "\1\"\0\0\305\32\1\0\1\0\0\0000\200W\323\0\0\0\0\3\4\300\0\6\1\34\0-\24\0\1"..., 14336, 37063168) = 14336
16178 0.000155 pwrite64(257, "\1\"\0\0\341\32\1\0\1\0\0\0\20\200\361\222\250\1\0\0\5\3\0\0\177\220r\0\1\0\1\0"..., 1048576, 37077504) = 1048576
16178 0.004949 pwrite64(257, "\1\"\0\0\341\"\1\0\1\0\0\0l\2008\345VALID\5\300\0N\1\24\0N\0\t\0"..., 6144, 38126080) = 6144
16178 0.000263 pwrite64(257, "\1\"\0\0\355\"\1\0\1\0\0\0\20\200\\\17\344\1\0\0\5\25\0\0\200\220r\0\1\0\3\1"..., 551424, 38132224) = 551424
16178 0.003174 pwrite64(257, "\1\"\0\0\"'\1\0\1\0\0\0t\200]S1:45:38\0VALID\1$\0"..., 516096, 38683648) = 516096
16178 0.000186 pwrite64(257, "\1\"\0\0\22+\1\0\1\0\0\0\20\200\2\33\250\1\0\0\5\1\0\0\201\220r\0\1\0\0\0"..., 1048576, 39199744) = 1048576
16178 0.004871 pwrite64(257, "\1\"\0\0\0223\1\0\1\0\0\0000\200\26\302\0\0\0\0s%\300\0\7\1\f\0\244\24\0\1"..., 11776, 40248320) = 11776
16178 0.000187 pwrite64(257, "\1\"\0\0)3\1\0\1\0\0\0\20\200\276Q\250\1\0\0\5\r\0\0\202\220r\0\1\0\300\0"..., 370688, 40260096) = 370688
16178 0.000169 pwrite64(257, "\1\"\0\0\3755\1\0\1\0\0\0\20\200\2n\334\1\0\0\5\22\0\0\203\220r\0\1\0\301\30"..., 1048576, 40630784) = 1048576
16178 0.005411 pwrite64(257, "\1\"\0\0\375=\1\0\1\0\0\0\224\200\206\213SS\0\0xq\10\30\f0\22\0xq\10\30"..., 12288, 41679360) = 12288
16178 0.000148 pwrite64(257, "\1\"\0\0\25>\1\0\1\0\0\0\20\200'\360\240\1\0\0\5\0\0\0\204\220r\0\1\0@\0"..., 1048576, 41691648) = 1048576
16178 0.005496 pwrite64(257, "\1\"\0\0\25F\1\0\1\0\0\0\304\200\264\255\0\0\0\0\\\0\30\0\0\24\0\0\0\0\0\32"..., 7168, 42740224) = 7168
16178 0.000181 pwrite64(257, "\1\"\0\0#F\1\0\1\0\0\0\20\200\304\f\234\1\0\0\5\0\0\0\205\220r\0\1\0\0\0"..., 1048576, 42747392) = 1048576
16178 0.003464 pwrite64(257, "\1\"\0\0#N\1\0\1\0\0\0\24\201\322\204\0\0\0\0\v\1\27\30\0\0\330&\2\r\0\0"..., 4096, 43795968) = 4096
16178 0.000150 pwrite64(257, "\1\"\0\0+N\1\0\1\0\0\0\20\200\227\354\344\1\0\0\5\1\0\0\206\220r\0\1\0r\0"..., 1048576, 43800064) = 1048576
16178 0.003711 pwrite64(257, "\1\"\0\0+V\1\0\1\0\0\0000\200\306\251\0\0\0\0\313&\300\0\n\1\n\0[\25\0\1"..., 7680, 44848640) = 7680
16178 0.000241 pwrite64(257, "\1\"\0\0:V\1\0\1\0\0\0\20\200P\221\220\1\0\0\5\0\0\0\207\220r\0\1\0\0\0"..., 1048576, 44856320) = 1048576
16178 0.005603 pwrite64(257, "\1\"\0\0:^\1\0\1\0\0\0h\200\233KD\0oUN\0\20\0N\0\0\0N\r\0\0"..., 21504, 45904896) = 21504
16178 0.000117 pwrite64(257, "\1\"\0\0d^\1\0\1\0\0\0\20\200\252\n\254\1\0\0\5\24\0\0\210\220r\0\1\0\2\1"..., 433152, 45926400) = 433152
16178 0.000243 pwrite64(257, "\1\"\0\0\262a\1\0\1\0\0\0\20\200\22I\230\1\0\0\5l\0\0\211\220r\0\1\0eN"..., 1048576, 46359552) = 1048576
16178 0.005121 pwrite64(257, "\1\"\0\0\262i\1\0\1\0\0\0\30\200\344\274\2\0\0\0-\0\0\0l\1\0\0\1\r\0\0"..., 16384, 47408128) = 16384
16178 0.000148 pwrite64(257, "\1\"\0\0\322i\1\0\1\0\0\0\20\200\374A\234\1\0\0\5\0\0\0\212\220r\0\1\0\330&"..., 441856, 47424512) = 441856
16178 0.000195 pwrite64(257, "\1\"\0\0001m\1\0\1\0\0\0\20\200zm\250\1\0\0\5\24\0\0\213\220r\0\1\0\0\0"..., 487936, 47866368) = 487936
16178 0.002564 pwrite64(257, "\1\"\0\0\352p\1\0\1\0\0\0\24\200z\207%\0\0\0p\1\0\0\1A\0\0\213\220r\0"..., 574464, 48354304) = 574464
16178 0.000107 pwrite64(257, "\1\"\0\0Lu\1\0\1\0\0\0\20\200c4l\0\0\0\5r\0\0\214\220r\0\1\0NO"..., 32256, 48928768) = 32256
16178 0.000120 pwrite64(256, "\25\302\0\0\26\0\0\0'\20\0\0\377\377\1\4j[\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384, 360448) = 16384
16178 0.000306 pwrite64(256, "\25\302\0\0\22\0\0\0'\20\0\0\377\377\1\4\256\235\0\0\0\0\0\0\0\0\0\0h\333UF"..., 16384, 294912) = 16384
16178 0.000210 pwrite64(256, "\25\302\0\0\20\0\0\0'\20\0\0\377\377\1\4\350<\0\0\200C\0\0\0\0\0\0\0\0\0A"..., 16384, 262144) = 16384
16178 0.000197 pwrite64(256, "\25\302\0\0\1\0\0\0\0\0\0\0\0\0\1\4\252.\0\0\0\0\0\0\0\4 \v@T\260e"..., 16384, 16384) = 16384
16178 0.000118 pwrite64(258, "\1\"\0\0\1\0\0\0\2\0\0\0\0\200\37\222\0\0\0\0\0\4 \v@T\260eORCL"..., 512, 512) = 512
16178 0.000235 pwrite64(257, "\1\"\0\0\1\0\0\0\1\0\0\0\0\200\317\6\0\0\0\0\0\4 \v@T\260eORCL"..., 512, 512) = 512
16178 0.000269 pwrite64(256, "\25\302\0\0\25\0\0\0(\20\0\0\377\377\1\4\343.\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384, 344064) = 16384
16178 0.000100 pwrite64(256, "\25\302\0\0\23\0\0\0(\20\0\0\377\377\1\4\352\270\0\0\17\0\0\0_yr\0\0\0\0\0"..., 16384, 311296) = 16384
16178 0.000204 pwrite64(256, "\25\302\0\0\277\0\0\0(\20\0\0\377\377\1\0049\272\0\0w\333UF\1\0\0\0\0\0\0\0"..., 16384, 3129344) = 16384
16178 0.000203 pwrite64(256, "\25\302\0\0\21\0\0\0(\20\0\0\377\377\1\4\223\235\0\0\0\0\0\0\0\0\0\0h\333UF"..., 16384, 278528) = 16384
16178 0.000195 pwrite64(256, "\25\302\0\0\17\0\0\0(\20\0\0\377\377\1\4w\177\0\0\0@\0\0\0\0\0\0\0\0\0\1"..., 16384, 245760) = 16384
16178 0.000192 pwrite64(256, "\25\302\0\0\1\0\0\0\0\0\0\0\0\0\1\4\265.\0\0\0\0\0\0\0\4 \v@T\260e"..., 16384, 16384) = 16384
16178 0.000182 pwrite64(258, "\1\"\0\0\2\0\0\0\2\0\0\0\20\200\223\372H\2\0\0\5\1\0\0\221\220r\0\1\0r\0"..., 1024, 1024) = 1024
16178 0.000107 pwrite64(258, "\1\"\0\0\4\0\0\0\2\0\0\0\20\2002\354\220\2\0\0\0050\0\0\227\220r\0\1\00001"..., 1024, 2048) = 1024
[oracle@test:/home/oracle]$
SQL> select 1048576/1024/1024 from dual;
1048576/1024/1024
-----------------
1
SQL>
从上面的跟踪我们可以得到什么?
1) lgwr 进程每3s触发一次.
2) lgwr 进程会更新controlfile header block.
3) lgwr进程每次写入的大小是不固定的,是以batch的方式写入,但是都是redo log block_size(512 byte)的整数倍.(大家可以去跟踪一下国产数据库会发现某些数据库并非batch写入)。
4) 对于inactive 状态的redo,oracle仍然会进行写操作,不过仅仅是更新redo logfile 头部.
5) 从我上面的实验来看,lgwr的一次性写大小达到了1048576 byte,等于1m大小,因此可以猜测该linux 版本的最大单次io大小是1m.
最后修改时间:2024-10-10 12:29:37
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




