ZetCode

Python os.fdatasync 函数

上次修改时间:2025 年 4 月 11 日

本综合指南探讨了 Python 的 os.fdatasync 函数,该函数强制将文件数据写入磁盘。我们将介绍同步方法、性能影响和实际的文件 I/O 示例。

基本定义

os.fdatasync 函数强制将文件数据写入磁盘,但不一定写入元数据。它类似于 fsync,但效率更高。

主要区别:fdatasync 仅刷新数据,不刷新元数据(如文件大小)。这使得它比 fsync 更快,适用于元数据不更改的情况。

基本文件同步

此示例演示了 fdatasync 的基本用法,以确保在文件操作后将数据写入磁盘。这对关键数据完整性至关重要。

basic_sync.py
import os

# Open file for writing
with open("important.log", "w") as f:
    f.write("Critical transaction data\n")
    # Force data to disk
    os.fdatasync(f.fileno())
    print("Data safely written to disk")

# Verify the write
with open("important.log", "r") as f:
    print(f.read())

该示例将数据写入文件,并立即使用 fdatasync 将其强制写入磁盘。 这确保了即使系统崩溃,数据也会持续存在。

请注意,我们使用 fileno() 从文件对象获取文件描述符,然后将其传递给 fdatasync。

比较 fsync 和 fdatasync

此示例通过定时使用每种同步方法执行的多次写入操作,演示了 fsync 和 fdatasync 之间的性能差异。

sync_comparison.py
import os
import time

def test_sync(method, filename):
    start = time.time()
    with open(filename, "w") as f:
        for i in range(1000):
            f.write(f"Line {i}\n")
            if method == "fsync":
                os.fsync(f.fileno())
            else:
                os.fdatasync(f.fileno())
    return time.time() - start

# Test both methods
fsync_time = test_sync("fsync", "fsync_test.log")
fdatasync_time = test_sync("fdatasync", "fdatasync_test.log")

print(f"fsync time: {fsync_time:.3f} seconds")
print(f"fdatasync time: {fdatasync_time:.3f} seconds")
print(f"Difference: {(fsync_time-fdatasync_time):.3f} seconds")

该测试使用每种同步方法将 1000 行写入文件。 fdatasync 通常更快,因为它不会将元数据刷新到磁盘。

性能差异取决于文件系统和硬件,但 fdatasync 通常比 fsync 的开销更少。

数据库事务安全

此示例演示了如何使用 fdatasync 来确保数据库事务的持久性,而无需 fsync 的全部开销。

db_transaction.py
import os
import sqlite3

def safe_transaction(db_file):
    conn = sqlite3.connect(db_file)
    cursor = conn.cursor()
    
    # Create table if not exists
    cursor.execute("CREATE TABLE IF NOT EXISTS data (id INTEGER PRIMARY KEY, value TEXT)")
    
    # Perform transaction
    cursor.execute("INSERT INTO data (value) VALUES ('important')")
    conn.commit()
    
    # Force data to disk
    db_fd = os.open(db_file, os.O_RDWR)
    os.fdatasync(db_fd)
    os.close(db_fd)
    
    print("Transaction committed and synced")
    conn.close()

safe_transaction("test.db")

该示例执行 SQLite 事务,并确保使用 fdatasync 将数据刷新到磁盘。 这提供了持久性,并具有更好的性能。

请注意,我们直接打开数据库文件的文件描述符以进行同步,因为 SQLite 的 Python 接口未公开文件描述符。

带同步的日志文件轮换

此示例演示了正确的日志文件轮换以及数据同步,以防止轮换过程中出现数据丢失。

log_rotation.py
import os
import time
from datetime import datetime

LOG_FILE = "app.log"
MAX_SIZE = 1024  # 1KB for demonstration

def write_log(message):
    with open(LOG_FILE, "a") as f:
        f.write(f"{datetime.now()}: {message}\n")
        os.fdatasync(f.fileno())

def rotate_logs():
    if os.path.exists(LOG_FILE) and os.path.getsize(LOG_FILE) > MAX_SIZE:
        timestamp = int(time.time())
        os.rename(LOG_FILE, f"app_{timestamp}.log")
        # Create new empty log file
        with open(LOG_FILE, "w") as f:
            os.fdatasync(f.fileno())

# Test log writing and rotation
for i in range(50):
    write_log(f"Test message {i}")
    rotate_logs()
    time.sleep(0.1)

该示例写入日志消息并在日志文件超出大小时轮换日志文件。 每次写入都与 fdatasync 同步,以确保不会丢失数据。

轮换过程还会同步新的空文件,以确保轮换后文件系统元数据保持一致。

配置文件缓冲

此示例演示了如何将文件缓冲设置与 fdatasync 结合使用,以优化性能,同时保持数据完整性。

buffering.py
import os

def write_with_buffering(filename, buffer_size):
    # Open with specific buffer size
    with open(filename, "w", buffering=buffer_size) as f:
        for i in range(100):
            f.write(f"Data chunk {i}\n")
            # Sync at specific intervals
            if i % 10 == 0:
                os.fdatasync(f.fileno())
        # Final sync
        os.fdatasync(f.fileno())

# Test different buffer sizes
for size in [0, 1, 1024, 4096]:
    write_with_buffering(f"output_{size}.txt", size)
    print(f"Written with buffer size {size}")

该示例使用定期同步测试不同的缓冲区大小。 较小的缓冲区会降低数据丢失风险,但可能会影响性能。

最佳缓冲区大小取决于您的具体用例以及性能要求与数据完整性需求。

错误处理

此示例演示了使用 fdatasync 时的正确错误处理,因为该操作可能会由于各种原因(例如磁盘已满或 I/O 错误)而失败。

error_handling.py
import os
import errno

def safe_write(filename, data):
    try:
        with open(filename, "w") as f:
            f.write(data)
            try:
                os.fdatasync(f.fileno())
                print("Data successfully written and synced")
            except OSError as e:
                if e.errno == errno.EIO:
                    print("I/O error during sync - data may be corrupted")
                elif e.errno == errno.ENOSPC:
                    print("No space left on device")
                else:
                    print(f"Sync failed: {e}")
    except IOError as e:
        print(f"File operation failed: {e}")

# Test with good and bad scenarios
safe_write("good.txt", "This should work")
safe_write("/full/device.txt", "This should fail if device is full")

该示例显示了针对文件操作和同步的全面错误处理。 不同类型的错误得到专门处理。

在处理磁盘同步时,正确的错误处理至关重要,因为失败可能表明存在严重的存储问题。

平台注意事项

此示例演示了 fdatasync 的平台特定行为,并提供了一个跨平台包装函数。

cross_platform.py
import os
import sys

def safe_sync(fd):
    """Cross-platform file synchronization"""
    if hasattr(os, 'fdatasync'):
        # Unix-like systems
        os.fdatasync(fd)
    else:
        # Windows or other platforms
        os.fsync(fd)

def write_data(filename):
    with open(filename, "w") as f:
        f.write("Cross-platform data\n")
        safe_sync(f.fileno())
        print("Data written and synced")

write_data("data.txt")
print(f"Running on: {sys.platform}")

该示例提供了一个跨平台解决方案,该解决方案在可用时使用 fdatasync,否则回退到 fsync。

在 Windows 上,fdatasync 不可用,因此改用 fsync。 该行为类似,但并不完全相同。

性能影响

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程