暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Oracle异常恢复实战第7讲 - redo的写入机制和原理_笔记

四九年入国军 2024-10-10
127
  感兴趣的还是记得去看原帖子,笔记有删减,原作者微信公众号: 
在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进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论