解析 ‘Write-Ahead Logging’ (WAL) 在内核层的体现:为什么数据库需要调用 `fsync` 才能保证数据安全?

各位同学,各位同仁,欢迎来到今天的技术讲座。今天我们将深入探讨一个在数据库领域至关重要,却又常被误解的核心机制:Write-Ahead Logging(WAL),以及它在内核层的体现,特别是为什么数据库系统需要依赖一个看似简单却蕴含深奥哲理的系统调用——fsync,来确保我们数据的绝对安全。

作为一名编程专家,我深知理论与实践的结合才能构建真正的理解。因此,今天的讲座将不仅仅是概念的堆砌,更会辅以代码示例,带大家从应用程序层面一路下探到操作系统内核,乃至物理存储介质,揭示数据持久化的真实旅程。

1. 数据的基石:持久性与WAL的登场

在数据库的世界里,ACID特性是衡量一个事务处理系统可靠性的黄金标准。其中,“D”代表着Durability(持久性)。它要求一旦事务被提交,即使系统发生崩溃(例如,断电、操作系统崩溃),该事务所做的修改也必须是永久性的,不会丢失。这并非易事,因为现代计算机系统为了性能,大量使用了缓存,而缓存往往是易失的。

要实现这种承诺,数据库系统引入了一项革命性的技术:Write-Ahead Logging (WAL)。WAL的核心思想非常简单,却极其强大:在对实际数据进行任何修改之前,必须先将这些修改的意图(即日志记录)写入一个持久化的日志文件。

WAL的工作原理可以概括为以下几点:

  1. 日志先行: 每次事务修改数据时,并不会立即将修改后的数据页写回磁盘。相反,它会生成一条描述该修改的日志记录(WAL record)。这条记录包含了足够的信息,以便在需要时重做(REDO)或撤销(UNDO)该操作。
  2. 日志写入: 这些WAL记录首先被写入内存中的WAL缓冲区,然后批量或单个地被写入磁盘上的WAL文件。
  3. 事务提交: 只有当事务的所有WAL记录都被成功地写入到稳定存储(通常是磁盘)之后,该事务才被认为是“提交”了。
  4. 数据页修改: 实际的数据页可以在内存中被修改(成为“脏页”),并最终由后台进程异步地写入磁盘,或者在检查点(checkpoint)时被强制写入。

WAL带来的好处是显而易见的:

  • 原子性与持久性保证: 即使系统在数据页被写入磁盘之前崩溃,只要WAL记录已经持久化,数据库在重启后就可以通过重放WAL日志来恢复数据,确保已提交事务的持久性。
  • 性能提升: 相比于随机地修改散落在磁盘各处的数据页,WAL日志是顺序写入的,这大大提高了磁盘I/O的效率。
  • 并发性优化: 事务不必等待数据页写入磁盘就能提交,减少了锁的持有时间,提高了系统的并发处理能力。

然而,这里的关键词是“成功地写入到稳定存储”。这并非简单地调用一个write()系统调用就能完成的,其背后隐藏着操作系统I/O栈的复杂性。

2. 存储栈:数据从应用到磁盘的旅程

为了理解fsync的重要性,我们首先需要了解数据从应用程序到物理磁盘的完整旅程。这个过程涉及多个层次的抽象和缓存。

表 1: 存储I/O栈的层次结构

层次 描述 缓存机制 数据状态(易失/持久)
应用程序层 数据库系统(PostgreSQL, MySQL等)的用户进程。 数据库内部缓存 易失
用户空间 应用程序运行的内存区域。 易失
系统调用接口 write(), read(), open(), fsync()
内核空间 操作系统内核运行的内存区域。
文件系统层 VFS (Virtual File System) 抽象层,具体文件系统驱动(Ext4, XFS等)。 页缓存 (Page Cache) 易失
块设备层 驱动程序与存储设备交互的接口,管理I/O请求队列。
设备控制器 硬盘控制器、RAID卡等,负责与物理存储设备通信。 设备写缓存 (DRAM) 易失(通常有电池保护)
物理存储介质 HDD (硬盘), SSD (固态硬盘) 的实际存储单元。 持久

2.1 用户空间与内核空间

操作系统将内存划分为用户空间和内核空间。应用程序代码运行在用户空间,无法直接访问硬件或操作系统的核心数据结构。当应用程序需要进行I/O操作(例如写入文件)时,它必须通过系统调用(如write(), open())进入内核空间,由内核代为完成。

2.2 操作系统文件系统缓存 (Page Cache)

这是理解fsync的关键点之一。为了提高I/O性能,操作系统在内核空间维护了一个庞大的内存区域,称为页缓存(Page Cache)。当应用程序通过write()系统调用写入数据时,数据并不会立即被写入物理磁盘,而是首先被复制到页缓存中

  • 脏页(Dirty Pages): 当页缓存中的数据被修改后,它就成了“脏页”。这些脏页最终会被内核的后台进程(如pdflushkswapd)异步地写入磁盘。
  • 惰性写入(Lazy Write): 这种异步写入策略是为了聚合小写操作、优化磁盘寻道,从而提高整体I/O吞吐量。
  • 易失性: 然而,页缓存是内存的一部分,在系统断电或崩溃时,所有尚未写入磁盘的脏页都会丢失

2.3 设备写缓存

在更低的层次,许多存储设备本身(如硬盘、SSD控制器、RAID卡)也拥有自己的易失性写缓存(通常是DRAM)。这些缓存能够进一步提升设备的写入性能,它们可以在数据真正写入非易失性存储介质之前,就向操作系统返回“写入成功”的信号。与页缓存类似,如果设备或控制器断电,而缓存中的数据尚未刷新到持久介质,数据同样会丢失。

由此可见,从应用程序的角度,一个“写入成功”的信号,在数据真正到达安全、持久的存储介质之前,需要跨越重重缓存的鸿沟。

3. write()系统调用:表面下的陷阱

让我们从一个简单的C语言程序开始,模拟一个数据库应用程序向文件写入数据。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main() {
    int fd;
    char *data = "This is a log record for transaction X. ";
    ssize_t bytes_written;

    // 1. 打开文件:O_CREAT 如果文件不存在就创建,O_WRONLY 只写,0644 文件权限
    fd = open("wal_segment.log", O_CREAT | O_WRONLY, 0644);
    if (fd == -1) {
        perror("Error opening file");
        return 1;
    }

    printf("File 'wal_segment.log' opened successfully. FD: %dn", fd);

    // 2. 写入数据:将数据从用户空间缓冲区复制到内核页缓存
    bytes_written = write(fd, data, strlen(data));
    if (bytes_written == -1) {
        perror("Error writing data");
        close(fd);
        return 1;
    }
    printf("Successfully wrote %zd bytes to file. Data is in page cache.n", bytes_written);

    // 假设这里发生了系统崩溃 (例如:断电)
    // 问:这些数据是否安全?

    // 3. 关闭文件:通常会触发内核将脏页异步写入磁盘,但不是立即的,也不是同步的。
    // 在 close() 返回时,数据可能仍在页缓存中。
    close(fd); 
    printf("File closed. Data might still be in page cache or device cache.n");

    // 为了演示目的,我们可以在这里删除文件,或者模拟后续的崩溃,
    // 然后尝试读取,你会发现如果没有 fsync,数据可能根本就没有写入磁盘。
    // system("rm wal_segment.log"); // 谨慎使用,这会删除文件

    return 0;
}

运行这段代码,你会看到write()系统调用成功返回,并且程序打印出“Successfully wrote … bytes”。然而,这仅仅意味着数据已经从应用程序的用户空间缓冲区复制到了内核的页缓存中。它不保证数据已经到达物理磁盘。

如果此时发生断电,或者操作系统崩溃,那么页缓存中的这些数据将永久丢失。对于数据库的WAL文件来说,这意味着一个已提交的事务可能会因为其WAL记录未能持久化而“消失”,这直接违反了持久性(Durability)的承诺。

因此,仅仅依赖write()是不够的。我们需要一种机制来强制数据从易失的页缓存写入到非易失的物理存储。

4. fsync():数据持久化的守护神

fsync()系统调用正是为了解决上述问题而生。

4.1 fsync() 的目的与保证

fsync()(或fdatasync())的目的是强制将一个文件所有已修改的、内存中的数据和/或元数据刷新到稳定存储设备

fsync(fd)被调用时,操作系统内核会执行以下操作:

  1. 刷新文件数据: 遍历与文件描述符fd关联的所有脏页,并将它们强制写入到磁盘。
  2. 刷新文件元数据: 更新并写入文件的元数据,例如文件大小、修改时间、访问时间、文件权限等。
  3. 等待I/O完成: fsync()阻塞调用进程,直到所有相关数据和元数据都被写入到物理磁盘,并且磁盘控制器也确认写入操作已完成(即数据已从设备写缓存刷新到持久介质)。

只有当fsync()成功返回时,我们才能确信:该文件在调用fsync()之前的所有写入操作,其数据都已经安全地存储在非易失性存储介质上,即使系统立即崩溃也不会丢失。

4.2 fdatasync():性能与持久性的权衡

除了fsync(),还有一个相关的系统调用是fdatasync()

  • fsync() 保证所有数据和所有元数据(包括不影响数据可见性的元数据,如访问时间)都写入磁盘。
  • fdatasync() 保证所有数据以及必要的元数据(例如文件大小,文件块分配等)都写入磁盘。它不会刷新那些不影响后续数据读取的元数据(例如修改时间),因此通常比fsync()更快。

对于数据库的WAL日志文件,我们最关心的是日志记录本身的数据以及文件末尾的增长(即文件大小的更新)。因此,许多数据库系统(如PostgreSQL)会优先使用fdatasync()来提高性能,因为它提供了足够的持久性保证,同时避免了不必要的元数据刷新开销。

4.3 fsync()的代码示例

现在,让我们修改之前的代码,加入fsync(),看看它如何改变数据的命运。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main() {
    int fd;
    char *data = "This is a critical log record, MUST be durable! ";
    ssize_t bytes_written;

    fd = open("wal_segment_durable.log", O_CREAT | O_WRONLY, 0644);
    if (fd == -1) {
        perror("Error opening file");
        return 1;
    }

    printf("File 'wal_segment_durable.log' opened successfully. FD: %dn", fd);

    bytes_written = write(fd, data, strlen(data));
    if (bytes_written == -1) {
        perror("Error writing data");
        close(fd);
        return 1;
    }
    printf("Successfully wrote %zd bytes to page cache.n", bytes_written);

    // 关键一步:调用 fsync() 强制数据写入稳定存储
    printf("Calling fsync() to ensure data durability...n");
    if (fsync(fd) == -1) {
        perror("Error calling fsync");
        close(fd);
        return 1;
    }
    printf("fsync() completed successfully. Data is now on stable storage.n");

    // 此时,即使系统立即崩溃,这条日志记录也已安全。

    close(fd); 
    printf("File closed.n");

    // 我们可以尝试读取文件以验证 (需要重新打开文件以读模式)
    // char read_buf[100];
    // int read_fd = open("wal_segment_durable.log", O_RDONLY);
    // if (read_fd != -1) {
    //     ssize_t read_bytes = read(read_fd, read_buf, sizeof(read_buf) - 1);
    //     if (read_bytes > 0) {
    //         read_buf[read_bytes] = '';
    //         printf("Verified: Read back from disk: '%s'n", read_buf);
    //     }
    //     close(read_fd);
    // }

    return 0;
}

在这个例子中,fsync()的调用是确保数据持久性的关键。它强制了从页缓存到物理磁盘的写入,并且等待这个写入操作的完成。对于数据库系统而言,这正是WAL机制的核心所在:在事务被声明为“提交”之前,其对应的WAL记录必须经过fsync()的洗礼,确保其安全抵达稳定存储。

5. fsync()与WAL:持久性保证的完整链条

现在,我们将fsync()整合到WAL协议中,勾勒出数据库如何使用它来提供强大的持久性保证。

WAL协议与fsync()的详细步骤:

  1. 事务开始与日志生成:

    • 应用程序中的事务开始执行,对数据库进行修改(INSERT, UPDATE, DELETE)。
    • 数据库系统为这些修改生成对应的WAL日志记录。这些记录包含了执行REDO操作所需的所有信息。
    • 这些WAL记录首先写入到数据库内部的内存WAL缓冲区(这是一个应用程序层的缓存)。
  2. WAL记录写入文件:

    • 当WAL缓冲区达到一定阈值,或者事务需要提交时,数据库系统会通过write()系统调用,将WAL缓冲区中的数据(WAL记录)写入到操作系统维护的WAL文件描述符中。
    • 此时,WAL记录仅仅是从数据库的内存缓冲区复制到了操作系统内核的页缓存中。
  3. 强制刷新到稳定存储:

    • 在事务被标记为“提交”之前,数据库系统会立即调用fsync()(或fdatasync()在该WAL文件描述符上。
    • fsync()强制内核将WAL文件相关的所有脏页从页缓存写入到物理磁盘。
    • fsync()还会等待磁盘控制器确认这些数据已经从其内部的易失性写缓存刷新到非易失性存储介质。
    • 只有当fsync()成功返回时,数据库才认为该事务的WAL记录已安全持久化。
  4. 事务提交确认:

    • 一旦fsync()成功返回,数据库系统就可以向客户端确认事务已成功提交。此时,即使系统立即崩溃,该事务的数据也不会丢失。
  5. 数据页的异步写入:

    • 实际的数据页(例如,表数据页或索引页)在内存中被修改后,成为“脏页”。
    • 这些脏页可以异步地、在后台由数据库的写进程(如PostgreSQL的bgwriter)或操作系统的后台进程(如pdflush)写入磁盘。它们不需要在事务提交时立即写入。
    • 这种异步写入大大提高了性能,因为它可以批量处理写入请求,优化磁盘I/O模式。
  6. 检查点(Checkpoint):

    • 数据库系统会定期执行检查点操作。在检查点期间,所有在检查点之前修改过的脏数据页都会被强制写入磁盘。
    • 检查点的目的是为了缩短系统崩溃后的恢复时间。如果所有数据页都已写入磁盘,那么在恢复时就不需要重放那么长的WAL日志。

为什么是对WAL文件调用fsync(),而不是对所有被修改的数据页?

这是一个核心问题。如果每次事务提交时都对所有被修改的数据页调用fsync(),那么性能将是灾难性的。数据页的修改通常是随机的,每次fsync()都会导致大量的随机I/O,并且需要等待其完成。

而WAL文件则不同:

  • 顺序写入: WAL文件通常是顺序追加写入的。对顺序写入的文件进行fsync(),虽然仍需等待I/O完成,但其性能远优于随机写入的fsync()
  • 集中管理: 所有的事务修改都通过WAL日志集中管理。我们只需要保证WAL日志的持久性,就可以通过重放日志来恢复数据。

因此,fsync()在WAL机制中的作用,就是为数据库系统提供了在性能与数据安全之间取得平衡的关键工具。它确保了日志的绝对持久性,进而保证了整个数据库系统的持久性。

6. 性能考量与权衡

fsync()虽然是数据持久化的守护神,但它并非没有代价。fsync()是一个昂贵的操作,因为它强制了物理I/O,并且会阻塞调用进程直到I/O完成。这在本质上是无法避免的,因为我们等待的是物理硬件的确认。

6.1 批量fsync()与延迟提交

为了缓解fsync()的性能瓶颈,数据库系统通常会采用批量提交延迟fsync()的策略。

  • 批量提交 (Group Commit): 数据库可以将多个并发事务的WAL记录收集起来,然后一次性地对这些记录进行fsync()。这意味着多个事务共享一次昂贵的fsync()操作。虽然单个事务的延迟可能略有增加,但系统的整体吞吐量会显著提高。
    • 例如,PostgreSQL的wal_sync_methodsynchronous_commit参数就提供了这种控制。当synchronous_commit设置为on时,每个事务都会等待其WAL记录被fsync。如果设置为off,事务提交时不会立即执行fsync,而是依赖于后台的WAL写入器或操作系统异步刷新,这会大大提高吞吐量,但牺牲了一定程度的崩溃安全性(最近提交的事务可能丢失)。

6.2 O_DIRECT:绕过页缓存

在某些高性能场景下,应用程序可能会选择使用O_DIRECT标志来打开文件。当文件以O_DIRECT模式打开时,数据将直接在用户空间缓冲区和块设备之间传输,完全绕过操作系统页缓存

  • 优点: 避免了页缓存的开销(如CPU复制),避免了双重缓冲(数据在应用缓冲区和页缓存各一份),对于大文件顺序I/O可能有性能优势。write()操作直接与物理磁盘交互,返回时数据通常已在磁盘上(或至少在设备缓存中)。
  • 缺点: 应用程序必须自己管理I/O缓冲区(通常需要对齐到文件系统块大小),小规模随机I/O性能可能下降,因为失去了页缓存的聚合和调度优势。
  • WAL中的应用: 对于WAL文件,O_DIRECT并不常用,因为WAL的顺序写入特性在页缓存的帮助下已经表现良好。但对于一些大型数据文件的存储(例如,某些NoSQL数据库或特定文件系统),O_DIRECT可能会被考虑。

6.3 硬件辅助:电池备份写缓存 (BBWC)

许多高端的RAID控制器或存储阵列都配备了电池备份写缓存 (Battery-Backed Write Cache, BBWC)

  • 工作原理: 当数据写入RAID控制器时,它首先存储在控制器的DRAM缓存中。控制器可以立即向操作系统返回“写入成功”的信号,而无需等待数据真正写入物理硬盘。如果此时发生断电,电池会为DRAM缓存供电,确保缓存中的数据在电力恢复后被安全地写入磁盘。
  • fsync()的影响: 对于操作系统而言,当RAID控制器报告fsync()完成时,数据实际上可能仍在控制器的BBWC中,而非物理磁盘。但由于BBWC的电池保护,这被认为是“稳定存储”的一种形式,因为它在主机电源故障的情况下提供了持久性。
  • 局限性: BBWC的持久性依赖于电池的健康状况。如果电池失效且未被发现,那么BBWC就变成了普通的易失性缓存,数据安全将面临威胁。因此,定期检查BBWC电池状态至关重要。

6.4 持久内存 (Persistent Memory, NVM)

这是存储领域的一个新兴技术,旨在从根本上改变数据持久化的方式。持久内存(如Intel Optane DC Persistent Memory)提供了一种内存级别的访问速度,但其数据在断电后依然保留。

  • 革命性: 如果数据直接写入持久内存,那么fsync()的概念将变得多余,因为写入操作本身就意味着持久化。这将彻底消除fsync()带来的性能瓶颈。
  • 现状: 虽然技术上可行,但持久内存的普及和在数据库系统中的广泛应用仍在发展中,需要操作系统、文件系统和数据库软件栈进行深度适配。

7. 深入探讨:文件系统日志与原子文件替换

7.1 文件系统日志 (Journaling File Systems)

现代文件系统(如Ext4, XFS, NTFS)大多是日志文件系统。这与数据库的WAL日志是两个不同层面的概念,但目的相似:在系统崩溃后,确保文件系统自身的一致性

  • 目的: 文件系统日志主要保护的是文件系统元数据(例如,文件和目录的结构、inode、文件分配表等),而不是用户数据本身。它确保文件系统在崩溃后能够快速恢复到一致状态,避免文件系统损坏。
  • 工作模式:
    • data=journal 所有数据和元数据都写入文件系统日志。最安全,但性能最差。
    • data=ordered (默认对Ext4): 元数据写入日志,而用户数据直接写入数据区。文件系统保证用户数据在元数据提交到日志之前写入。这是最常见的模式,它保护了数据与元数据的一致性,但仍需要fsync()来强制用户数据写入磁盘。
    • data=writeback 只有元数据写入日志。用户数据异步写入,不保证写入顺序。性能最好,但可能在崩溃后导致数据不一致(例如,文件内容更新了,但文件大小没有更新)。
  • fsync()的影响: 即使在日志文件系统上,fsync()仍然是必要的。日志文件系统保证了其自身的元数据不会损坏,以及在data=ordered模式下数据和元数据的相对顺序,但它不保证fsync()之前写入的用户数据块已经全部到达物理磁盘fsync()是应用程序层面,针对特定文件的数据持久化要求。

7.2 sync命令与fsync()系统调用

  • sync命令/系统调用: sync命令(或底层的sync()系统调用)会指示内核将所有内存中的脏页(针对所有文件和文件系统)都写入磁盘。这是一个全局性、粗粒度的操作。它通常由系统管理员定时执行,或在系统关机前执行。
  • fsync()系统调用: fsync()是针对特定文件描述符的细粒度操作。它只刷新与该文件描述符关联的脏页和元数据。

数据库系统使用fsync()而不是sync,因为它需要精确控制哪个文件(WAL文件)的数据已经持久化,而不是等待整个系统的所有I/O都完成。

7.3 原子文件替换

有些应用程序为了确保文件更新的原子性,会采用原子文件替换的策略:

  1. 将新内容写入一个临时文件。
  2. 对临时文件调用fsync(),确保新内容已持久化。
  3. 包含该临时文件和目标文件的目录调用fsync(),确保目录条目(即临时文件的创建)持久化。
  4. 使用rename()系统调用将临时文件重命名为目标文件。rename()操作在文件系统层面是原子的,它要么成功,要么失败,不会出现中间状态。
  5. 再次对包含该目录调用fsync(),以确保rename操作的元数据变更(例如,旧文件的删除,新文件的链接)也持久化。

这个过程之所以复杂,是因为即使rename()是原子的,其底层文件系统元数据的改变也需要被持久化。如果没有对目录进行fsync(),那么在rename()成功后立即崩溃,可能会导致目录结构不一致,从而使原子替换失败。

8. PostgreSQL中的WAL与fsync()实践

作为生产级数据库的典范,PostgreSQL的WAL实现是理解fsync()在实际系统中作用的绝佳案例。

PostgreSQL的WAL日志文件存储在pg_wal(旧版本为pg_xlog)目录下。每个WAL文件都是一个固定大小的段文件(通常是16MB)。

  1. XLogInsert 这是PostgreSQL内部用于创建和写入WAL记录的核心函数。当事务对数据进行修改时,会调用XLogInsert将相应的日志记录写入到内存中的WAL缓冲区。
  2. XLogFlush 当事务提交、WAL缓冲区满、或达到一定时间间隔时,XLogFlush函数会被调用。它的职责是将WAL缓冲区中的数据写入到当前的WAL段文件中,并调用fsync()fdatasync()来确保这些记录的持久性。
    • XLogFlush会首先调用write()将WAL数据从内存缓冲区写入到操作系统页缓存。
    • 然后,它会根据配置的wal_sync_method参数,调用fsync()fdatasync()在WAL文件描述符上。
    • 如果wal_sync_method设置为open_datasyncopen_sync,PostgreSQL会在打开WAL文件时使用O_DSYNCO_SYNC标志,这使得每次write()调用本身就具有fdatasync()fsync()的效果,从而避免了显式调用fsync()。然而,这种方式在某些文件系统上可能性能不佳,因此通常不推荐。
  3. synchronous_commit参数: 这是PostgreSQL中一个非常重要的配置参数,它直接控制了事务提交时fsync()的行为:
    • on (默认值): 最安全。当事务提交时,数据库会等待其所有WAL记录被fsync()到磁盘,然后才向客户端返回成功。保证了最高级别的数据持久性。
    • off 最不安全,但性能最高。事务提交时不会等待WAL记录被fsync()。WAL记录的持久化由后台进程异步完成。在系统崩溃时,最近提交的事务可能会丢失。
    • local 仅在主数据库上等待WAL记录被fsync(),不对异步复制的备用服务器做等待。
    • remote_write / remote_apply 与流复制相关,等待WAL记录被复制到备用服务器并写入其页缓存 (remote_write) 或被备用服务器应用 (remote_apply)。这提供了更强的灾备能力。

通过合理配置synchronous_commit,数据库管理员可以在性能和持久性之间进行权衡。对于大多数生产系统,on是确保数据安全的推荐设置。

9. 最终的真理:fsync()的不可或缺性

我们今天的旅程从数据库的持久性需求开始,深入探讨了操作系统I/O栈的层层缓存,最终聚焦于fsync()系统调用的核心作用。

数据在现代计算机系统中,从应用程序的内存到最终的物理存储介质,要经过多层易失性缓存的洗礼:数据库自身的缓冲区、操作系统页缓存、存储设备控制器缓存。这些缓存是为了性能而生,却也带来了数据丢失的风险。

fsync(),作为连接内存与物理世界的桥梁,它的存在正是为了弥合这一鸿沟。它强制了易失性缓存中的数据写入到非易失性存储,并等待硬件的确认。在Write-Ahead Logging的范式下,fsync()确保了WAL日志的绝对持久性,进而成为了数据库系统提供ACID事务中“持久性”承诺的基石。

尽管fsync()是一个相对昂贵的操作,并且在追求极致性能的道路上,我们总希望能找到绕过它的方法,但直到持久内存等颠覆性技术真正普及之前,fsync()在确保数据安全方面仍然是不可或缺的,也是我们目前最可靠的机制。理解并正确使用它,是构建任何可靠、持久化数据系统的基本要求。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注