2

据我所知,当我们发出一条 SQL 语句时,数据库中的默认行为(当然在 MySQL 中)是自动提交。
但结构通常如下:

String sqlInsertOrUpdateStatement = "....";  
sqlStatement.execute(sqlInsertOrUpdateStatement);  
//rest of code  

现在execute是一个阻塞函数,当它返回时,我们知道数据是否被保存(根据执行的结果)。
但我无法理解的是:
所有数据库实现都使用最低级别的文件。例如,anINSERT基本上是对文件的深入写入操作。但是当我们写入文件时,我相信内容实际上并没有立即刷新。当操作系统决定时,它们会被刷新。即使应用程序代码(例如 C 库)发出刷新,操作系统也会很快刷新数据。
那么数据库如何保证持久性。我忽略/误解的文件 I/O 和操作系统中是否有一些基本的东西?

4

2 回答 2

3

数据库不保证任何东西,它们不控制从数据库到存储中的比特的整个堆栈,那么它们怎么能呢?

他们能做的是确保他们调用适当的函数,如果假设成立,将保持持久性。

它的工作方式是程序写入数据库。数据库(通常)将该写入操作转换为日志事件,然后调用 fsync 将日志缓冲区刷新到磁盘。

但是请看,这里涉及到几个抽象。第一个是数据库级别的缓冲。DB 使用适当的信息更新日志缓冲区,然后将这些缓冲区写入某种 I/O 流。

如果它们是使用 stdio 编写的,它再次缓冲数据(不太可能,但谁知道),那么他们将不得不在该流上调用 flush 以强制系统调用将缓冲区“写入磁盘”。

但是,正如您所观察到的,写入“磁盘”意味着写入更多缓冲区。因此,fsync 调用相当于 stdio 的刷新调用。它告诉操作系统将进程数据写入磁盘。但这究竟意味着什么?这意味着操作系统调用适当的设备驱动程序将底层块写入物理设备。

好吧,设备驱动程序可以说“Okey dokey”而不做任何事情。他们通常不会,但他们可以。司机可以为所欲为。我们假设发生的是驱动程序获取数据缓冲区,然后开始通过适当的接口(SATA、SCSI、iSCSI、NFS、USB 等)与硬件通信。

这里的坚果是,到底司机到底在说什么?好吧,今天,使用现代 SATA 驱动器,它正在与另一台计算机通信。这台计算机是磁盘驱动器的控制器。

你知道磁盘驱动器上的控制器是做什么的吗?它们将数据缓冲到 RAM 中。所以,有可能你可以做“一切正确”(stdio flush、fsync、驱动程序写入块设备)并且仍然有数据,在 RAM 中,等待失败,坐在大约 50 美元的硬盘上,上面写着“Aok boss!” .

一些驱动器默认说“okey dokey”并将数据简单地存储在缓冲区中,并且需要另外配置。其他驱动器默认开箱即用。如果您将前一种驱动器(配置不正确)用于数据库或 RAID 系统,那么,如果您断电,您会感到很痛苦。

或者设备将正确配置并愉快地将块写入坏扇区,而您从一开始就注定要失败。这就是生活。

另外,考虑一下您是否正在与驱动程序交谈,与网络交谈,与 SAN 控制器交谈,在写入阵列之前使用电池备份 RAM 缓存......希望如此。

因此,数据库不保证任何事情。相反,他们进行尽职调查,应该将数据安全地传送到实际的物理设备,以及数据最终将实际保存的位置。

任何称其执行 ACID 的数据库都可以做到这一点,并且所有操作系统都支持这一点——当然,任何人都会实际使用的所有操作系统。

底线,如果你想确保你的数据在磁盘上,在 Unix 中,你调用 fsync。在 Windows 中,你称之为“无论谷歌说你在 Windows 上调用 fsync 的东西”。那时实际发生的事情不在你的掌控之中,所以最好向那些了解你的硬件和接口的人提问。然而,程序已经完成。

附加物:

现代数据库通常的工作方式是将数据组织在磁盘上的“表”中,并且通常单独作为“索引”进行组织。因此,您可以想象,如果您有一个包含 1000 行的表,它以 100 个“页”存储在磁盘上,您可以看到如果更新了第 500 行,您将需要更新第 50 页。您还可以看到,如果您更新了第 700、500、600 行,则需要更新第 70、50 和 60 页。

好吧,事实证明,磁盘驱动器作为流媒体设备工作得非常快。当您必须移动磁盘磁头(在驱动器上来回移动的手臂)时,它会显着减慢速度。所以你可以在上面的例子中看到,磁盘磁头在写操作期间来回弹跳。

现在,具有事务的数据库也需要某种日志。日志是记录所有操作的地方。日志通常也仅附加到,因此如果您要更新上面的那些行,它们将作为 3 个连续事件在日志中被捕获。

所以DB所做的是,当你改变一行时,它会更新内部缓冲区,即第50页的内部副本。当数据库想要去获取第 500 行的另一个副本时,它会看到它已经在 RAM 中有一个副本,并且不需要去磁盘。一旦它更新了内部缓冲区,它就会将操作正确地写入日志。由于这些是顺序的,因此它们要快得多。最后,它将使用 fsync 等提交写入。每次提交事务时,它都会立即执行此操作。在您“确定”数据记录在某处之前,您无法提交事务。

此时,内存中有行,日志中捕获的行(持久),但磁盘上的实际页面已过期。对于正在运行的数据库,这不是问题。最终,系统将“检查点”。这需要所有过期的日志条目,并将它们复制到磁盘上的最终目的地,同步日志、缓冲区和磁盘。当它这样做时,它将对磁盘的写入进行排序。所以上面不是写第 60 页,然后是第 50 页,然后是第 70 页,而是按顺序写它们:50、60、70。更少的寻头,更好的性能。

现在,如果系统在此之前崩溃,那没问题。我们已经将数据安全地保存在日志中。因此,当系统恢复正常时,它会进入“恢复”状态。恢复基本上是对日志中已提交的所有数据(即它们的事务已完成)运行检查点过程,并像以前一样将它们刷新到磁盘页面。它会在让数据库出现并可以访问之前执行此操作。

至于配置驱动器,据我了解,有一些实用程序可以将参数配置写入驱动器。取决于操作系统,我真的不知道细节。你必须用谷歌搜索它。

于 2013-11-10T03:53:25.430 回答
3

好吧,在 Linux 数据库管理系统上使用fsync调用,它实际上是这样做的:保证,数据被写入磁盘。一些像 Oracle 这样的大型系统也会跳过文件系统(它执行您所说的由操作系统完成的事情)并自己写入驱动器上的扇区(当然会提高速度)。

而且,如果您想质疑耐用性...硬盘驱动器也可能会出现故障,即使是散装;-)

于 2013-11-09T21:35:48.100 回答