【问题标题】:How do databases perform atomic I/O?数据库如何执行原子 I/O?
【发布时间】:2012-03-01 21:55:32
【问题描述】:

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

但是,作为一名非计算机专业的毕业生,我想知道 ACID 实际上是如何工作的 在非常低的水平上。例如,我知道 Oracle 一直将数据写入“在线重做日志”,然后在应用程序发出信号应该提交事务时执行“提交”。

我想放大并理解正是这个“提交”阶段。是只是将“多一个字节”写入磁盘,还是将0 翻转为1 以表示给定行已成功存储?

【问题讨论】:

  • 更适合dba.stackexchange.com - 因为这不是编程问题,因此。
  • 我不同意。如果我是“管理数据库”,那很好,但如果我是“编写”数据库,那么这是一个编程问题,可以由拥有软件工程或计算机科学学位等的人回答。
  • 很公平。这个问题似乎询问了当前的实现(即 Oracle 如何做到这一点),因此我发表了评论。
  • 您可以查看 PostgreSQL 源代码以了解他们是如何做到的。显然,代码非常干净并且相对“容易”理解。
  • 是的,我实际上是在考虑研究 RavenDB。

标签: database-design acid


【解决方案1】:

我记得有一次发现 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

然后 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 中,每个事务都会发出一个系统更改编号,并记录在每个块的顶部。当一个块发生变化时,它的原始内容被放入撤消空间,新块指向旧块:所以你基本上有一个块版本的链表N-数据文件中的最新版本,逐渐变旧的版本在撤消表空间中。在块的顶部是一个“感兴趣的事务”列表,它以某种方式实现了锁定(同样没有为每一行更改的内存锁),我不记得除此之外的细节。

我认为 SQL Server 的快照隔离机制与 Oracle 的很相似,使用 tempdb 来存储正在更改的块而不是专用文件。

希望这个漫无边际的答案有用。全部来自内存,因此可能存在大量错误信息,尤其是对于非 postgresql 实现。

【讨论】:

  • 实际上,我认为 MS SQL Server、PostgreSQL 和 Firebird/Interbase 在如何实现 MVCC 方面是表兄弟。基本技术是由 Jim Starkey 开创的,最终将作为“Interbase”发布,并且从根本上是面向行的。 Oracle 使用了一种不同的(更“面向页面”)算法,该算法已获得 IIRC 的专利,从而阻止了竞争。
【解决方案2】:

Oracle 的高级概述:

每个 Oracle 会话都是唯一的,每个会话可以有 1* 个活动事务。当事务开始时,Oracle 为其分配一个单调递增的系统更改编号 (SCN)。当 Oracle 更新/插入/删除行时,Oracle 通过更新正在写入的块中的标题以及将“原始”块保存到 oracle 的回滚(撤消)空间来锁定表中感兴趣的行和支持索引。 Oracle 还将重做日志条目写入内存缓冲区,描述对表和索引块以及撤消块所做的更改。请注意,所做的更改是在内存中进行的,而不是直接在磁盘中进行的。

提交时,Oracle 确保在将事务控制权返回给客户端之前,已将整个日志缓冲区(包括事务的 SCN)写入磁盘。

回滚时,Oracle 使用回滚(撤消)中的信息来消除所做的更改。

那么这是如何实现 ACID 的:

原子性:我的会话,我的交易,要么全部完成,要么都不完成。当我提交时,在提交完成之前我什么都做不了。

一致性:Oracle 检查日期是否为日期,字符数据是否为字符数据,数字是否有效。与检查约束相同。外键约束依赖于检查以确保被引用的父键是有效的——并且没有被正在运行的事务更新或删除。如果父键已更新或删除,您的语句将挂起 - 它实际上处于不确定状态 - 等待影响父记录的语句提交或回滚。

独立性:还记得那些系统变更数字吗?如果您不进行更改,Oracle 会在您启动语句或声明游标时知道 SCN 是什么。因此,如果您有一个长时间运行的语句,其中数据正在从您下面更改,Oracle 会在您的语句开始运行时检查以获取数据,因为它是提交的。它是多版本的一致性控制,而且相当复杂。 Oracle 并未实现各种 SQL 标准所要求的所有隔离级别 - 例如,Oracle 绝不允许脏读或幻读。

持久性:刷新到磁盘的重做日志缓冲区是持久性的根级别。当重做日志文件已满时,Oracle 会强制执行检查点。此过程导致 Oracle 将所有已修改的表和索引块从内存中写入磁盘,无论它们是否已提交。如果实例崩溃并且数据文件中的数据包含未提交的更改,Oracle 会使用重做日志回滚这些更改,因为撤消信息也包含在重做日志中。

*暂时忽略自治事务,因为它们很复杂。

【讨论】:

  • “提交时,Oracle 确保整个日志缓冲区(包括事务的 SCN 在内)都已写入磁盘……”它是如何做到的?这是我的问题的核心——它是否开始将日志缓冲区和 SCN 的内容写入磁盘,然后做最后一件小事,比如在磁盘上设置一个从 0 到 1 的位来表示一切正常?跨度>
  • 它使用fwritefflush 调用(我相信Oracle 内核仍在C 中)并依赖从这些调用返回的状态来了解它是否有效。在某些时候,Oracle 必须相信操作系统按照它所说的做了。
【解决方案3】:

Ayende 在 Twitter 上建议我查看Munin,这是他用于RavenDBRaven MQ 的实际数据存储机制。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-08
    • 1970-01-01
    • 2012-09-18
    • 1970-01-01
    • 2015-07-24
    • 1970-01-01
    • 1970-01-01
    • 2012-03-01
    相关资源
    最近更新 更多