02 Oracle日志系统:一条SQL更新语句是如何执行的

上一节《Oracle基础架构:一条SQL查询语句是如何执行的》我们讲到了SQL查询语句在数据库中的执行过程,因为不涉及到数据的变更,因此其执行过程相对来说比较简单,所以我重点给大家介绍了SQL语句的解析过程。但数据库里不仅仅只有查询,还会有大量的变更操作。
关系型数据库中的变更操作主要有insert/update/delete,以及后来扩展出来的merge。虽然在数据写入的实现细节上有所差别,但在数据库内部的操作方式和数据流向上并没有太大的差别,为了让我们的讲述更加完整,本篇文章里我选择一条更新语句来给大家介绍,Oracle数据库是如何实现数据变更操作的。

更新语句也是SQL语句,因此我们在上一节讲到的SQL解析等过程也同样需要。不同的是,查询语句是只读的,不涉及数据的变化,因此将用户需要的数据返回到客户端,即完成了这次查询操作;但更新语句涉及到数据的变化,因此需要将用户的变更操作保存下来,实现的过程比单纯的查询语句要复杂很多。

Oracle的日志系统

关系型数据库操作需要满足ACID理论要求,其中D(Durability)持久性就是指一旦事务提交,数据库就要绝对保证数据的安全,任何情况下都不会丢失数据。因为我们的数据库是一个并发的系统,既要满足性能上的极致追求,又必须在各种极端情况下确保数据的持久性,看似是一个非常朴素的需求,却凝聚了很多精妙的设计。

Oracle的日志系统主要是由日志缓冲区(Redo Log Buffer)、在线日志(Redo Log)以及组成归档日志(Archived Log),当然归档日志面向的是介质损坏,数据文件丢失等更加极端的情况,这篇文章里不对这些场景做过多的讨论。

日志缓冲区

日志缓冲区是从SGA中分配的一块内存区域,专门用于存放数据库事务产生的数据变更记录,这些记录反应了对数据库所做的所有变更操作,是实现数据库恢复的核心组件。
在这里插入图片描述

Oracle日志缓冲区大小是固定的,通常不超过500M,采用循环写入的方式,没有写出到在线日志之前这些数据不能被覆盖,因此需要根据一定的策略将数据写出。主要的触发条件包括:

  1. 重做日志缓冲区写满1/3;
  2. 当客户端发起提交或回滚操作时;
  3. 数据库写出进程(DBWR)通知日志写出 – 通常是检查点发生时。

从上面的触发条件可以看到,日志写出的频率还是非常高的。即便如此,当日志生成量过多,或者日志文件的磁盘性能不够好时,仍然可能会出现日志缓冲区不够的导致应用等待的问题。

在线重做日志

当用户发起提交操作时,由日志写出进程将相应操作记录从日志缓冲区写入到在线日志。在线日志,通常由3~5个日志文件组成,集群环境中每个实例都有一组这样的文件。日志文件循环使用,每次只有一个文件在工作,该文件写满后自动切换到下一个,所有日志文件都写满后再循环到第一个,覆盖之前记录的内容进行新的写入,周而复始。
在这里插入图片描述

在线日志文件被覆盖之前需要满足两个条件,1) 日志文件对应的"脏数据块"需要全部写出到数据文件;2) 如果数据库是归档模式,需要完成在线日志文件的归档。

这两个条件都涉及到其他组件的操作,容易受到干扰而导致日志文件不够用,因此通常建议每个实例分配3-5组重做日志文件,根据应用修改日志的生成量,每个日志分配500MB~1GB的大小。

归档日志文件

为了满足数据库备份和恢复的需求,需要将数据库设置为归档模式。在归档模式下,在线日志切换到下一个日志后,Oracle会启动新的进程将日志内容保存到文件系统或ASM等存储介质上,这就是我们所说的归档日志文件。由在线到归档,Oracle通过这种方式将源源不断生成的在线日志归档到数据库之外,避免数据库空间的无限增长。

对于生产库,强烈建议设置为归档模式,并建立相应的备份策略,定期进行归档日志文件和数据库的备份,以备不时之需,关于异常恢复有太多的悲剧故事,都是因为没做好备份!

逻辑读还是物理读

磁盘和内存的读写速度存在显著差异,‌内存的读写速度远超磁盘。为了提升数据访问性能,绝大多数数据库系统都设计了数据缓存机制,Oracle数据库中称之为数据缓冲区 (Buffer Cache)。

初始状态下,数据保存在数据文件中,当一条更新操作提交给数据库之后,会先检索数据库缓存中是否存在相应的数据,如果数据不在缓存中,则将需要的数据从数据文件读取到缓存,这个过程我们称为物理读;如果要访问的数据在缓存中已经存在,则直接对其进行后续的操作,这个过程称为逻辑读。显然,逻辑读的性能要优于物理读,为此Oracle还专门引入了一个指标(Buffer Hit) 来计算缓存访问在所有访问中的占比,以此来判断数据库的性能是否良好。对于OLTP类型的数据库来说,通常建议保持该指标在98%以上。

数据的提交或回滚

Oracle在数据缓冲区中找到对应的数据后,会进行以下几个操作:

  1. 将更新前的数据拷贝一份到UNDO表空间,这份数据我们称之为前镜像;
  2. 更新数据缓冲区中的相应的数据为目标数据,同时将变更操作写入日志缓冲区;
  3. 接下来会有两种可能:
  • 当客户端发出提交操作后,Oracle会在日志中写入一个提交标记,表示事务的结束,并把日志缓冲区中的内容写出到在线日志文件,当在线日志文件写出完成后,会反馈"Commit Complete"信息给客户端。客户端接收到这个信息,提交才算是完成,可以继续后续的操作;
  • 如果这个时候用户发现执行了一条错误的操作,或者因为某种情况产生了异常需要回退本次操作,Oracle会进行回滚操作。这时会在日志中写入回滚标记,表示本次事务作废,并把之前的数据从前镜像中拷贝会原来的地址。

写到这里,对于客户端来说一次更新操作已经完成。细心的同学可能会发现,这个时候数据库并没有处在一致性的状态上,变更操作只是写到了在线日志文件,并没有最终落盘到数据文件。

事实上,只要成功将操作日志写出到在线日志文件,客户端收到"Commit Completed."的反馈,Oracle就能够确保事务所做的变更不会丢失。因为在线日志中包含了数据库事务所有的操作记录,当实例异常宕机时,会有专门的进程负责将需要的日志从在线日志文件中读取出来,还原到对应的数据块,将数据恢复到崩溃前的状态。

但其实还有一个问题,如果这个时候在线日志文件丢失了怎么办,还能正常恢复吗?如果数据没有写出到数据文件而在线日志文件又丢失了,这个时候就会导致数据的丢失,更极端的情况还会导致数据库无法正常打开。所以在线日志文件是Oracle体系架构中的“软肋”之一,生产系统通常建议为每个日志组分配两个成员,其中一个成员异常,不会影响数据库的正常使用。

总结

在这篇文章中,我们以一条更新SQL的视角,给大家介绍了Oracle数据库是如何处理DML语句的。通过这个过程我们可以看到,所有变更操作都是在内存中完成的,这里的内存操作包括Redo Log Buffer中记录的数据操作日志和Buffer Cache中对数据块的变更。当用户确认提交后,会将Redo Log Buffer记录同步写出到在线重做日志文件,而Buffer Cache中被修改的数据块,则会放入到“脏块”链表,基于一定的机制异步写出到数据文件中。

只要客户端收到"Commit Completed."的反馈,数据就不会丢失。那么Oracle是如何做到这一点的呢?大家又经历过哪些数据丢失的故障,欢迎留言讨论!

  • 33
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值