MySQL 中与文件 I/O(输入/输出)相关的核心抽象层

28 阅读8分钟

我们来详细介绍一下 MySQL 中与文件 I/O(输入/输出)相关的核心抽象层,重点关注 my_openmy_dir 函数。这些函数是 MySQL 用来封装和抽象底层操作系统文件操作的关键部分,旨在提供跨平台一致性、增强错误处理、简化资源管理

核心目标:为什么需要文件 I/O 抽象?

MySQL 需要在多种操作系统(Linux, Windows, macOS, Solaris 等)上运行。不同操作系统在文件操作 API 上存在显著差异:

  1. 路径分隔符: Windows 使用 \,Unix-like 使用 /
  2. 文件名大小写敏感性: 文件系统行为不同(如 NTFS 通常保留大小写但不敏感,EXT4 通常敏感)。
  3. 文件描述符限制: 系统对进程可打开文件数 (ulimit -n) 有上限,MySQL 需要管理这个限制。
  4. 文件打开模式标志: open() vs CreateFile(),标志位定义不同(O_RDWR, O_CREAT, O_EXCL, O_SHORT_LIVED, O_TEMPORARY 等)。
  5. 错误代码: errno (Unix) vs GetLastError() (Windows),错误号含义不同。
  6. 目录遍历: opendir()/readdir() (Unix) vs FindFirstFile()/FindNextFile() (Windows)。
  7. 文件属性和元数据: 获取文件大小、修改时间、权限等的方式不同。
  8. 原子性和并发性: 某些平台特定 API 或标志可能提供更好的原子操作或并发控制。

直接处理这些差异会使 MySQL 的文件操作代码变得复杂、脆弱且难以维护。 my_openmy_dir 等函数就是为了解决这些问题而设计的。

解决方案:MySQL 文件 I/O 抽象层

MySQL 在 mysysmysys_ssl 目录下实现了一套文件操作函数库(如 my_sys.h, my_file.h),提供了一套统一的、跨平台的接口。

关键函数详解:

  1. my_open

    • 功能: 打开或创建文件,返回一个 MySQL 内部管理的文件描述符(File 类型)。
    • 原型 (简化):
      File my_open(const char *pathname, int flags, myf MyFlags);
      
    • 参数:
      • pathname: 文件路径字符串。
      • flags: 文件打开模式标志。MySQL 重新定义了一套统一的标志(在 my_sys.h 中),映射到不同平台的底层标志。常用标志包括:
        • O_RDONLY: 只读
        • O_WRONLY: 只写
        • O_RDWR: 读写
        • O_APPEND: 追加模式
        • O_CREAT: 如果文件不存在则创建
        • O_EXCL: 与 O_CREAT 一起使用,确保文件必须被创建(如果已存在则失败)
        • O_TRUNC: 如果文件存在且可写,则将其长度截断为 0
        • O_TEMPORARY: (内部常用) 提示系统该文件是临时的,可能优先使用内存或更积极的缓存/删除策略。
        • O_SHORT_LIVED: (内部常用) 类似 O_TEMPORARY,表示文件生命周期短。
      • MyFlags: MySQL 特定的控制标志(位掩码)。极其重要! 用于控制错误处理和文件行为:
        • MYF(MY_WME | MY_WAIT_IF_FULL): 最常见组合。
          • MY_WME (Write Message on Error): 如果打开失败,将错误信息写入 MySQL 的错误日志(error log)或 stderr
          • MY_WAIT_IF_FULL: 如果因为文件描述符耗尽 (EMFILE) 而打开失败,则等待一段时间并重试。这是 MySQL 管理文件描述符限制的关键机制!避免在高并发打开文件时直接失败。
        • MY_DONT_CHECK_FILESIZE: 打开时不检查文件大小(可能用于特殊设备)。
        • MY_DONT_WAIT: 即使设置了 MY_WAIT_IF_FULL,也不等待,立即返回错误(用于非关键文件)。
        • MY_REDEL_NO_COPY_STAT: (特定用途,如重做日志) 不尝试复制文件属性。
    • 返回值:
      • 成功:返回一个非负整数 File 描述符(MySQL 内部定义的类型,通常等同于或映射到系统 int/HANDLE)。
      • 失败:返回 -1 (通常是 (File)-1)。
    • 内部工作:
      • 处理路径分隔符转换(Windows <-> Unix)。
      • 将 MySQL 统一的 flags 转换为底层操作系统特定的标志(open()CreateFile())。
      • 调用底层 open() (Unix) 或 CreateFile() (Windows)。
      • 文件描述符管理: MySQL 维护一个内部文件描述符表,用于:
        • 跟踪已打开的文件。
        • 实现 MY_WAIT_IF_FULL 语义:当 open()EMFILE 失败时,短暂休眠 (my_sleep()),并可能尝试关闭一些空闲文件描述符或等待其他线程释放,然后重试。这极大地提高了系统在达到 ulimit 时的健壮性。
      • 错误处理: 根据 MyFlags (尤其是 MY_WME) 记录错误日志。将系统错误码 (errno / GetLastError()) 映射到 MySQL 内部错误码(通过 my_errno 变量访问)。
    • 优势:
      • 跨平台一致性: 开发者无需关心底层是 open() 还是 CreateFile()
      • 文件描述符耗尽处理: MY_WAIT_IF_FULL 机制是 MySQL 稳定性的重要保障。
      • 统一错误日志: MY_WME 简化了错误报告。
      • 统一标志: 使用一套易于理解的标志。
  2. my_dir

    • 功能: 读取目录内容,返回一个包含文件/子目录信息的结构体 (MY_DIR)。注意:my_dir 是一个较老、较底层的接口。在高版本 MySQL 或某些上下文中,可能更推荐使用 my_dir_iterator 或封装程度更高的函数。
    • 原型 (简化):
      MY_DIR *my_dir(const char *path, myf MyFlags);
      
    • 参数:
      • path: 要读取的目录路径。
      • MyFlags: 类似于 my_open 的控制标志。常用 MYF(MY_WME) 在出错时写日志。
    • 返回值:
      • 成功:返回指向 MY_DIR 结构体的指针。
      • 失败:返回 NULL
    • 结构体 MY_DIR (简化):
      typedef struct my_dir {
        char **dir_entry;      // 指向 MY_FILEINFO 结构体数组的指针
        uint number_off_files; // 目录中文件/子目录的数量
        ...                   // 可能包含平台特定的内部状态信息
      } MY_DIR;
      
    • 结构体 MY_FILEINFO (通常内部使用或定义在 my_dir.h):
      typedef struct fileinfo {
        char *name;           // 文件名/子目录名
        my_off_t size;        // 文件大小 (字节)
        time_t mtime;         // 最后修改时间 (Unix 时间戳)
        ...                   // 可能包含其他属性 (如权限 mode_t attrib)
      } MY_FILEINFO;
      
    • 内部工作:
      • 调用底层 opendir()/readdir() (Unix) 或 FindFirstFile()/FindNextFile() (Windows)。
      • 遍历目录中的每一项。
      • 为每一项创建一个 MY_FILEINFO 结构体,填充 namesize (通过 stat()GetFileAttributesEx() 获取)、mtime 等信息。
      • 分配内存构建 MY_DIR 结构体,并将 MY_FILEINFO 数组的指针赋给 dir_entry,设置 number_off_files
      • 处理错误 (MyFlags 控制日志)。
      • 注意:. (当前目录) 和 .. (上级目录) 通常会被过滤掉不包含在结果中。
    • 使用和清理:
      MY_DIR *dirp = my_dir("/path/to/dir", MYF(MY_WME));
      if (dirp != NULL) {
        for (uint i = 0; i < dirp->number_off_files; i++) {
          MY_FILEINFO *finfo = &(dirp->dir_entry[i]);
          printf("File: %s, Size: %lld\n", finfo->name, (long long)finfo->size);
        }
        my_dirend(dirp); // 必须调用此函数释放资源!
      }
      
    • my_dirend: 释放 my_dir 分配的内存 (MY_DIR 结构体本身和 MY_FILEINFO 数组)。
    • 优势:
      • 跨平台: 统一了目录遍历接口。
      • 提供基本信息: 返回文件名、大小、修改时间等常用元数据。
      • 简化遍历: 返回一个包含所有项信息的数组。

相关的重要文件 I/O 函数:

MySQL 的文件 I/O 抽象层远不止这两个函数,还包括:

  • my_close(File fd, myf MyFlags) 关闭由 my_open 打开的文件。同样处理错误 (MY_WME) 并更新内部文件描述符表。
  • my_read(File fd, uchar *buf, size_t count, myf MyFlags) 从文件读取数据。处理错误 (MY_WME),支持 MY_FULL_IO (读不满 count 不返回) 等标志。
  • my_write(File fd, const uchar *buf, size_t count, myf MyFlags) 向文件写入数据。处理错误 (MY_WME),支持 MY_NABP (No-Abandon-on-Partial-write - 部分写入也视为错误)、MY_FULL_IO (必须写满 count) 等标志。
  • my_seek(File fd, my_off_t pos, int whence, myf MyFlags) 移动文件指针 (lseek / SetFilePointer)。whenceMY_SEEK_SET, MY_SEEK_CUR, MY_SEEK_END
  • my_tell(File fd, myf MyFlags) 获取当前文件指针位置。
  • my_sync(File fd, myf MyFlags) 将文件数据刷新到磁盘 (fsync / FlushFileBuffers)。
  • my_fstat(File fd, MY_STAT *stat_area, myf MyFlags) 获取文件状态信息(大小、修改时间、权限等)填充到 MY_STAT 结构体。
  • my_delete(const char *name, myf MyFlags) 删除文件。处理错误 (MY_WME)。
  • my_rename(const char *from, const char *to, myf MyFlags) 重命名/移动文件。处理错误 (MY_WME)。
  • my_mkdir(const char *path, int flags, myf MyFlags) 创建目录。flags 指定权限模式 (如 0777)。
  • my_rmdir(const char *path, myf MyFlags) 删除(空)目录。
  • my_chmod(const char *path, int flags, myf MyFlags) 修改文件权限。
  • my_stat(const char *path, MY_STAT *stat_area, myf MyFlags) 通过路径获取文件状态信息。

总结:MySQL 文件 I/O 抽象的价值

  1. 跨平台兼容性: 消除不同操作系统文件 API 的差异,使核心存储引擎(如 InnoDB 操作数据文件、redo log、undo log)、复制(binlog)、日志记录(error log, general log, slow log)、临时文件处理等代码能够无缝运行在任何支持的平台上。
  2. 健壮的错误处理: 统一的错误标志 (MyFlags) 控制错误日志输出 (MY_WME),提供一致的信息。特别是 MY_WAIT_IF_FULL 对文件描述符耗尽的管理是 MySQL 高可用性的关键基石。
  3. 资源管理: 内部文件描述符表的维护,为 MY_WAIT_IF_FULL 提供基础。
  4. 简化开发: 开发者使用一套统一的、文档化的接口,无需深入研究不同 OS 的 API 细节。
  5. 集中控制点: 便于在单个地方添加调试日志、性能统计、安全审计或自定义行为(如模拟磁盘故障)。
  6. 性能优化基础: 统一的接口允许在底层实现平台相关的优化(如 Windows 上的异步 I/O、特定标志位优化)。

结论:

my_openmy_dir 及其家族函数构成了 MySQL 与物理文件系统交互的统一、健壮、跨平台的接口层。它们封装了底层操作系统调用的复杂性,处理了平台差异、错误管理(尤其是文件描述符耗尽)和资源管理,为 MySQL 服务器核心功能的稳定高效运行提供了基础保障。理解这些函数是分析 MySQL 存储、日志、备份恢复等模块底层行为的关键。