数据库如何执行原子I/O?

Nei*_*ell 18 database-design acid

Oracle,SQL Server等数据库非常擅长数据完整性.如果我想编写一个我知道会存储一些数据或失败的数据存储(即ACID),那么我会在它下面使用像MySQL这样的数据库作为实际商店,因为这些问题已经解决了.

然而,作为一名非综合性学生,我不知道ACID 究竟是如何在非常低的水平上运作的.我知道Oracle就是这样,一直将数据写入"在线重做日志",然后在应用程序发出应该提交事务的信号时执行"提交".

正是这个"提交"阶段,我想直接放大并理解.是仅仅将"一个字节"写入磁盘,还是将a翻转0到a 1表示已成功存储给定行的情况?

ara*_*nid 12

我记得曾经发现BerkeleyDB文档实际上对于了解这些实现如何工作非常有用,因为这是一个非常低级别的数据库,它实现了没有整个关系/查询规划基础结构的事务.

并非所有数据库(甚至只是您提到的数据库)都以完全相同的方式工作.PostgreSQL的底层实现与Oracle和SQL Server的快照实现完全不同,即使它们都基于相同的方法(MVCC:多版本并发控制).

实现ACID属性的一种方法是将您("您"这里是一些进行更改的事务)所做的所有更改写入"事务日志",以及锁定每一行(单位为原子性)以确保在您提交或回滚之前,没有其他事务可以改变它.在事务结束时,如果提交,您只需将记录写入日志中,表示您已提交并释放锁定.如果您回滚,则需要返回事务日志,撤消所有更改 - 因此写入日志文件的每个更改都包含数据最初显示方式的"前映像".(实际上它还将包含"后映像",因为事务日志也会被重放以进行崩溃恢复).通过锁定要更改的每一行,在结束事务后释放锁之前,并发事务不会看到您的更改.

MVCC是一种方法,通过该方法,想要读取行而不是被更新阻止的并发事务可以访问"之前的映像".每个事务都有一个标识,并且有一种方法可以确定它可以"看到"哪些事务的数据以及哪些事务不能:生成此组的不同规则用于实现不同的隔离级别.因此,要获得"可重复读取"语义,事务必须找到由其后启动的事务更新的任何行的"前映像".您可以通过让事务通过事务日志查看之前的映像来天真地实现这一点,但实际上它们存储在其他地方:因此Oracle具有单独的重做和撤消空间 - 重做是事务日志,撤消是在块的映像之前要使用的并发事务; SQL Server将之前的映像存储在tempdb中.相比之下,PostgreSQL总是在更新时创建行的新副本,因此之前的图像存在于数据块本身中:这具有一些优势(提交和回滚都是非常简单的操作,没有额外的空间来管理)与权衡(那些过时的行版本必须在后台吸尘).

在PostgreSQL的情况下(这是我最熟悉的内部数据库)磁盘上的每个行版本都有一些额外的属性,事务必须检查这些属性以确定该行版本是否对它们"可见".为简单起见,请考虑它们具有"xmin"和"xmax" - "xmin"指定创建行版本的事务ID,"xmax"指定删除它的(可选)事务ID(可能包括创建新行版本到表示对行的更新).所以你从txn#20创建的行开始:

xmin xmax id value
20   -    1  FOO
Run Code Online (Sandbox Code Playgroud)

然后txn#25执行 update t set value = 'BAR' where id = 1

20   25   1  FOO
25   -    1  BAR
Run Code Online (Sandbox Code Playgroud)

在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实现.