Oracle,SQL Server等数据库非常擅长数据完整性。如果我想写一个我知道会存储一些数据或失败的数据存储(即ACID),那么我会使用像它下面的MySQL这样的数据库作为实际的存储,因为这些问题已经解决了
然而,作为一名非综合科学毕业生,我不知道ACID 实际上是如何工作的处于非常低的水平。我知道Oracle就是这样,一直将数据写入“在线重做日志”,然后在应用程序发出信号提交应该提交的某个时刻执行“提交”。
正是这个“提交”阶段,我想直接放大并理解。是仅仅将“再多一个字节”写入磁盘,还是将0
翻转到1
以表示已成功存储给定行?
答案 0 :(得分:12)
我记得曾经发现BerkeleyDB文档实际上对于了解这些实现如何工作非常有用,因为这是一个非常低级别的数据库,它实现了没有整个关系/查询规划基础结构的事务。
并非所有数据库(甚至只是您提到的数据库)都以完全相同的方式工作。 PostgreSQL的底层实现与Oracle和SQL Server的快照实现完全不同,即使它们都基于相同的方法(MVCC:多版本并发控制)。
实现ACID属性的一种方法是将您所做的所有更改("您和#34;这里是一些进行更改的事务)对数据库写入"事务日志",以及锁定每一行(原子单位)以确保在您提交或回滚之前没有其他事务可以改变它。在事务结束时,如果提交,您只需将记录写入日志中,表示您已提交并释放锁。如果您回滚,则需要返回事务日志,撤消所有更改 - 因此写入日志文件的每个更改都包含一个"之前的图像"数据最初的样子(在实践中,它还将包含"在图像之后"因为事务日志也会被重放以进行崩溃恢复)。通过锁定要更改的每一行,在结束事务后释放锁之前,并发事务不会看到您的更改。
MVCC是一种方法,通过该方法,想要读取行而不是被更新阻止的并发事务可以访问"之前的图像"代替。每个交易都有一个身份,并有一种方法来确定哪些交易'它可以"看"并且它不能:产生这个集合的不同规则用于实现不同的隔离级别。因此,要获得"可重复读取"语义,交易必须找到"之前的图像"例如,对于由其后启动的事务更新的任何行。您可以通过让事务通过事务日志查看之前的映像来天真地实现这一点,但实际上它们存储在其他地方:因此Oracle具有单独的重做和撤消空间 - 重做是事务日志,撤消是在块的映像之前要使用的并发事务; SQL Server将之前的映像存储在tempdb中。相比之下,PostgreSQL总是在更新时创建一个行的新副本,因此之前的图像存在于数据块本身中:这有一些优点(提交和回滚都是非常简单的操作,没有额外的空间来管理),需要权衡(那些过时的行版本必须在后台进行抽真空。)
在PostgreSQL的情况下(这是我最熟悉的内部数据库)磁盘上的每个行版本都有一些额外的属性,事务必须检查这些属性以确定该行版本是否为&# 34;可见"给他们。为简单起见,请考虑他们有" xmin"和" xmax" - " xmin"指定创建行版本的事务ID," xmax"删除它的(可选)事务ID(可能包括创建新行版本以表示对行的更新)。所以你从txn#20创建的行开始:
xmin xmax id value
20 - 1 FOO
然后txn#25执行update t set value = 'BAR' where id = 1
20 25 1 FOO
25 - 1 BAR
在txn#25完成之前,新交易将知道将其更改视为不可见。因此,扫描此表的交易将采用" FOO"版本,因为它的xmax是一个不可见的事务。
如果回滚txn#25,新事务将不会立即跳过它,但会考虑txn#25是否已提交或回滚。 (PostgreSQL管理"提交状态"查找表来提供此服务,pg_clog
)由于txn#25回滚,其更改不可见,因此" FOO"版本被采取。 (并且" BAR"版本被跳过,因为它的xmin事务是不可见的)
如果提交了txn#25,那么" FOO"现在没有采用行版本,因为它的xmax事务是可见的(也就是说,该事务所做的更改现在可见)。相比之下," BAR"行版是,因为它的xmin事务是可见的(它没有xmax)
虽然txn#25仍在进行中(同样可以从pg_clog
读取),任何其他想要更新行的事务都会等到txn#25完成后,尝试对其进行共享锁定交易ID 。我强调了这一点,这就是为什么PostgreSQL通常没有"行锁定"因此,只有事务锁:每行更改都没有内存中的锁。 (使用select ... for update
锁定是通过设置xmax和一个标志来表示xmax只是表示锁定而不是删除)
Oracle ......确实有些类似,但我对细节的了解更加模糊。在Oracle中,每个事务都会发出一个系统更改编号,并记录在每个块的顶部。当一个块改变时,它的原始内容被放入撤销空间,新块指向旧块:所以你基本上有一个块的版本块的链接列表 - 数据文件中的最新版本,逐步旧版本在撤消表空间中。在块的顶部是一个"感兴趣的交易列表"以某种方式实现锁定(再次没有更改每行的内存锁),我无法记住除此之外的细节。
SQL Server的快照隔离机制我认为与Oracle有很大的相似之处,使用tempdb存储正在更改的块而不是专用文件。
希望这个漫无边际的回答很有用。它全部来自内存,因此大量的错误信息是可能的,特别是对于非postgresql实现。
答案 1 :(得分:2)
Oracle的高级概述:
每个Oracle会话都是唯一的,每个会话可以有1 *活动事务。当事务开始时,Oracle会为其分配一个单调递增的系统变更编号(SCN)。当Oracle更新/插入/删除行时,Oracle会通过更新正在写入的块中的标头以及将“原始”块保存到oracle的回滚(撤消)空间来锁定表中感兴趣的行并支持索引。 Oracle还将重做日志条目写入内存缓冲区,描述对表和索引块以及撤消块进行的更改。请注意,正在进行的更改是在内存中进行的,而不是直接在磁盘上进行的。
提交时,Oracle确保在将事务控制权返回给客户端之前,整个日志缓冲区(包括事务的SCN)已写入磁盘。
回滚时,Oracle使用回滚(撤消)中的信息来消除所做的更改。
那么如何实现ACID:
原子性:我的会话,我的交易,一切都进行,或者都没有。当我提交时,在提交完成之前我不能做任何事情。
一致性:Oracle检查日期是日期,字符数据是字符数据,数字是否有效。检查约束也是如此。外键约束依赖于检查以确保被引用的父键是有效的 - 并且尚未被飞行中的事务更新或删除。如果父键已被更新或删除,则您的语句会挂起 - 它处于不确定状态,真的 - 等待影响父记录的语句提交或回滚。
独立:还记得那些系统更改号吗?如果您没有进行更改,Oracle会在您启动语句或声明游标时知道SCN是什么。因此,如果您有一个长期运行的语句,数据从您的下方更改,Oracle会在您的语句开始运行时检查以获取数据。这是多版本的一致性控制,而且非常复杂。 Oracle没有实现各种SQL标准所要求的所有隔离级别 - 例如,Oracle从不允许脏读或幻像读取。
Durablility:刷新到磁盘的重做日志缓冲区是持久性的根级别。当重做日志文件已满时,Oracle强制检查点。此过程使Oracle将所有已修改的表和索引块从内存写入磁盘,无论它们是否已提交。如果实例崩溃并且数据文件中的数据包含未提交的更改,则Oracle会使用重做日志回滚这些更改,因为撤消信息也包含在重做日志中。
* 暂时忽略自主交易,因为它们是严重的并发症。
答案 2 :(得分:0)