Oracle之数据库一致性读的原理

news/2024/10/12 22:33:23

1.概述

在Oracle数据库中,undo主要有三大作用:提供一致性读(Consistent Read)、回滚事务(Rollback
Transaction)以及实例恢复(Instance Recovery)

2.原理

一致性读是相对于脏读(Dirty Read)而言的。假设某个表T中有10000条记录,获取所有记录需要15分钟时间。当前时间为9点整,某用户A发出一条查询语句:select * from T,该语句在9点15分时执行完毕。当用户A执行该SQL语句到9点10分的时候,另外一个用户B发出了一条delete命令,将T表中的最后一条记录删除并提交了。那么到9点15分时,A用户将返回多少条记录?

如果返回9999条记录,则说明发生了脏读;如果仍然返回10000条记录,则说明发生了一致性读。很明显,在
9点钟那个时间点发出查询语句时,表T中确实有10000条记录,只不过由于I/O的相对较慢,所以才会花15分钟完成所有记录的检索。对于Oracle数据库来说,没有办法实现脏读,必须提供一致性读,并且该一致性读是在没有阻塞用户的DML的前提下实现的。

那么undo数据是如何实现一致性读的呢?还是针对上面的例子。用户A在9点发出查询语句时,服务器进程会将9
点那个时间点上的SCN号记录下来,假设该SCN号为SCN9.00。那么9点整的时刻的SCN9.00一定大于等于记录在所有数据块头部的ITL槽中的 SCN号(如果有多个ITL[1]槽,则为其中最大的那个SCN号)。

服务器进程在扫描表T的数据块时,会把扫描到的数据块头部的ITL槽中的SCN号与SCN9:00之间进行比较,哪个更大。如果数据块头部的SCN号比SCN9.00要小,则说明该数据块在9点以后没有被更新,可以直接读取其中的数据;否则,如果数据块ITL槽的SCN号比SCN9.00要大,则说明该数据块在9点以后被更新了,该块里的数据已经不是9点那个时间点的数据了,于是要借助undo块。

9点10分,B用户更新了表T的最后一条记录并提交(注意,在这里,提交或者不提交并不是关键,只要用户B更新了表T,用户A就会去读undo数据块)。假设被更新记录属于N号数据块。那么这个时候N号数据块头部的ITL槽的SCN号就被改为SCN9.10。当服务器进程扫描到被更新的数据块(也就是N号块)时,发现其ITL槽中的SCN9.10大于发出查询时的SCN9.00,说明该数据块在9点以后被更新了。于是服务器进程到N号块的头部,找到SCN9.10所在的ITL槽。由于ITL槽中记录了对应的undo块的地址,于是根据该地址找到undo块,将 undo块中的被修改前的数据取出,再结合N号块里的数据行,从而构建出9点10分被更新之前的那个时间点的数据块内容,这样的数据块叫做CR块(Consistent Read)。对于delete来说,其undo信息就是insert,也就是说该构建出来的CR块中就插入了被删除的那条记录。随后,服务器进程扫描该 CR块,从而返回正确的10000条记录。

让我们继续把问题复杂化。假设在9点10分B用户删除了最后一条记录并提交以后,紧跟着9点11分,C用户在同一个数据块里(也就是N号块)插入了2条记录。这个时候Oracle又是如何实现一致性读的呢(假设表T的initrans为1,也就是只有一个ITL 槽)?因为我们已经知道,事务需要使用ITL槽,只要该事务提交或回滚,该ITL槽就能够被重用。换句话说,该ITL槽里记录的已经是SCN9.11,而不是SCN9.10了。这时,ITL槽被覆盖了,Oracle的服务器进程又怎能找回最初的数据呢?

其中的秘密就在于,Oracle在记录undo数据的时候,不仅记录了改变前的数据,还记录了改变前的数据所在的数据块头部的ITL信息。因此,9点10分B用户删除记录时(位于N号块里,并假设该N号块的ITL信息为[Undo_block0
SCN8.50]),则Oracle会将改变前的数据(也就是insert)放到undo块(假设该undo块地址为Undo_block1)里,同时在该undo块里记录删除前ITL槽的信息(也就是[Undo_block0 SCN8.50])。删除记录以后,该N号块的ITL信息变为 [Undo_block1 SCN9.10];到了9点11分,C用户又在N号块里插入了两条记录,则Oracle将插入前的数据(也就是delete两条记录)放到undo块(假设该undo块的地址为Undo_block2)里,并将9点11分时的ITL槽的信息(也就是[Undo_block1 SCN9.10])也记录到该undo块里。插入两条记录以后,该N号块的ITL槽的信息改为 [Undo_block2 SCN9.11]。

那么当执行查询的服务器进程扫描到N号块时,发现SCN9.11大于SCN9.00,于是到ITL槽中指定的
Undo_block2处找到该undo块。发现该undo块里记录的ITL信息为[Undo_block1 SCN9.10],其中的SCN9.10仍然大于SCN9.00,于是服务器进程继续根据ITL中记录的Undo_block1,找到该undo块。发现该undo块里记录的ITL信息为[Undo_block0 SCN8.50],这时ITL里的SCN8.50小于发出查询时的SCN9.00,说明这时undo块包含合适的undo信息,于是服务器进程不再找下去,而是将N号块、Undo_block2以及Undo_block1的数据结合起来,构建CR块。将当前N号的数据复制到CR块里,然后在CR块里先回退9点11分的事务,也就是在CR块里删除两条记录,然后再回退9点10分的事务,也就是在CR块里插入被删除的记录,从而构建出9点钟时的数据。Oracle就是这样,以层层嵌套的方式,查找整个undo块的链表,直到发现ITL槽里的SCN号小于等于发出查询时的那个SCN号为止。正常来说,当前undo块里记录的SCN号要比上一个undo块里记录的SCN号要小。

但是在查找的过程中,可能会发现当前undo块里记录的ITL槽的SCN号比上一个undo块里记录的SCN号还要大。这种情况说明由于事务被提交或回滚,导致当前找到的undo块里的数据已经被其他事务覆盖了,于是我们无法再找出小于等于发出查询时的那个时间点的SCN号,这时Oracle就会抛出一个非常经典的错误——ORA-1555,也就是snapshot too old的错误。

以上的描述可以用图来描述:

3.其他

回滚事务则是在执行DML以后,发出rollback命令撤销DML所作的变化。Oracle利用记录在ITL槽里记录的undo
块的地址找到该undo块,然后从中取出变化前的值,并放入数据块中,从而对事务所作的变化进行回滚。

实例恢复则是在SMON进程完成前滚并打开数据库以后发生。SMON进程会去查看undo segment头部(所谓头部就是undo segment里的第一个数据块)记录的事务表(每个事务在使用undo块时,首先要在该undo块所在的undo segment的头部记录一个条目,该条目里记录了该事务相关的信息,其中包括是否提交等),将其中既没有提交也没有回滚,而是在实例崩溃时被异常终止的事务全部回滚。

本文为转载,原文为:Oracle之数据库一致性读的原理


  1. ITL(Interested Transaction List)是Oracle数据块内部的一个组成部分,用来记录该块所有发生的事务,一个itl可以看作是一个记录,在一个时间,可以记录一个事务(包括提交或者未提交事务)。当然,如果这个事务已经提交,那么这个itl的位置就可以被反复使用了,因为itl类似记录,所以,有的时候也叫itl槽位。 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/27172.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

Codeforces Round 943 (Div. 3)

小孩不懂事,随便写点无伤但没速通,然后被hack两个题,直接从rk90掉到rk114514+了,我是真他妈的服了。 特此纪念 A 暴力枚举秒了。 B 二分答案秒了 C 他妈脑子抽了,差一步完全整解。我们发现只要确定 \(a_{\mathbf{1}}\) 那么你直接不断加 \(x_i\) 就能求出 \(a_i\),但是直…

5.3 居家养老web端控制台

基于vue3+ElementPlus做的一个居家养老静态界面,内容准备后期实现 以下是效果图 特别声明用到了百度地图JS API进行位置展示与iV Cam模拟实时监控 以下是代码部分<script setup> import { onMounted, onBeforeUnmount } from vue import { ref } from vue const videoRe…

Python深入理解*和**含义和应用

本文源代码:https://gitee.com/obullxl/PythonCS/tree/master/CS-CY2405 Python中*和**很常见 禅师在阅读Python代码过程中,经常看到一个函数或方法的入参是*args和**kwargs(如:def func(*args, **kwargs)等)形式,或者在调用函数或方法时,在入参的元组和列表前面增加1个…

设置border后,子元素和父容器存在间隙

下方也出现了间隙网上找了一圈,全是说inline-block的,但是我调整a标签为block还是有间隙。 问题代码 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=…

python教程3.1:数据类型:字符串+列表list

一、字符串 字符串是⼀个有序的字符的集合,⽤于在计算机⾥存储和表示⽂本信息 常用操作二、列表list []内以逗号分隔,按照索引,存放各种数据类型,每个位置代表⼀个元素 特征 1、增加操作追加,数据会追加到尾部 2、删除操作3、修改操作 4、查找操作 如果不知道元素在列表哪…

WPF上位机 - 轴运动控制

最近学习WPF,写了一个WPF上位机使用S7.NETPlus 库与西门子1500TPLC,控制西门子伺服的通用上位机界面。分享在写上位机过程中踩的一些坑和使用体验。 上位机介绍可以看到上位机分为3个区域轴选择,使能区域 控制参数设置区域 诊断区域选择使能区域 选择区域读取TIA中组态的轴工…

系统管理之Linux启动流程

1.系统初始化进程SysV风格: init centos5 配置文件:/etc/inittabUpstart风格: init centos6 配置文件:/etc/init.d/,/etc/inittabSystemd风格: systemd centos7 配置文件:/usr/lib/systemd/system/,/etc/systemd/system/ systemctl是一个systemd管理工具2.Systemd 服务…

提高安全性,优雅实现拷贝与交换:C++中的Copy-and-Swap惯用法

概述:拷贝并交换(Copy-and-Swap)是C++编程中的惯用法,用于实现赋值操作符和确保异常安全的拷贝构造函数。其核心思想是通过拷贝构造函数创建临时副本,再通过交换确保操作的异常安全性。这种方法在C++11之前和之后都适用,但在C++11中,移动语义和右值引用的引入使得实现可…