MySQL源码(3)异步IO核心逻辑简化调试(对比PG)

0 总结速查

  1. mysql innodb支持使用LIBAIO和O_DIRECT完成刷脏。刷脏是异步的,io_prep_pwrite+io_submit调用完就返回了,后面**io_getevents**拿结果。相对于pg略微复杂,pg是双缓冲,使用sharedbufferpage cache,写盘比较简单,直接write配合fsync即可。

  2. 虽然做了10年pg,但从性能角度看mysql的方案确实更平缓一些,例如pg如果脏页太多集中checkpoint时刷一堆经常会抖,mysql平时一直刷checkpoint只需要推lsn会比较平缓。pg双缓存严重依赖pagec ache,这东西又不在pg控制范围内,增加了一些复杂度,例如预热等场景还需要插件去间接影响page cache,如果单缓存会更直接。

  3. 关于io_getevents,这个函数不是阻塞的,可以配置最少等几个好了就返回。MySQL 的策略是至少等1个完成,但最多等500ms。如果500ms内没有任何请求完成,返回0,外层for(;;) 循环重新调用。os0file.cc:2334

    假设同时提交了 5 个异步写请求 (A B C D E)

    时间线:
    t0: io_submit(A B C D E)
    t1: A 完成, C 完成
    t2: io_getevents(min=1, max=5, timeout=500ms)
    → 立即返回 2(收到 A 和 C 的 io_event)
    → B D E 还在飞行中,不等它们
    t3: D 完成
    t4: io_getevents(min=1, max=5, timeout=500ms)
    → 立即返回 1(收到 D)
    t5: 500ms 超时,B E 还没完成
    t6: io_getevents(min=1, max=5, timeout=500ms)
    → 返回 0(超时,一个也没完成)
    → 外层 for(;;) 循环重新调用
    t7: B E 完成
    t8: io_getevents → 返回 2

    所以 io_getevents 是半同步的:它会阻塞等待,但不要求所有请求都完成,凑够 min_nr 个(或超时)就返回。MySQL 把它放在 io_handler_thread
    的死循环里,不断收割已完成的事件,一次收几个就处理几个。

Page Cleaner 线程

复制代码
    │
    │  扫描 flush_list,挑出脏页
    ▼
buf_flush_page()                         ← 决定刷哪些页
    │
    ▼
dblwr::write()                           ← 先写 doublewrite buffer(防止 partial write)
    │
    ▼
fil_io(type=WRITE, sync=false, ...)      ← sync=false 走异步路径
    │
    ▼
os_aio() → AIO::linux_dispatch()
    │
    ▼
io_submit(ctx, 1, &iocb)                ← 提交给内核,Page Cleaner 立即返回
                                            继续提交下一个脏页
------- 内核异步执行 DMA 写磁盘 -------

io_handler_thread (后台收割线程)
    │
    ▼
io_getevents()                           ← 等待完成事件
    │
    ▼
fil_aio_wait() → buf_page_io_complete() ← 标记该页为 clean

1 Mysql Innodb与Postgresql刷脏对比

  • Innodb Buffer Pool脏页不等 checkpoint ,由后台Page Cleaner线程持续、异步地刷盘。
  • PostgreSQL shared buffer脏页的落盘主要靠两个机制,两个机制使用的是标准write() + fsync(),所有IO都要走系统的page cache。相对Innodb看起来就是平时少、集中批量刷
    1. clock sweep(Postgresql源码(5)缓冲区管理),挑选脏页刷盘。
    2. checkpoint检查点刷脏。

数据路径对比

复制代码
  模式 A: O_DIRECT (MySQL InnoDB 默认)
  ═══════════════════════════════════
  用户态 buffer ────── DMA ──────► 磁盘控制器 write cache ──► 盘片
      (posix_memalign                 (绕过 page cache)
       地址必须 512 对齐)

  ✓ 没有 page cache 拷贝开销
  ✓ InnoDB Buffer Pool 本身就是缓存,不需要 OS 再缓存一份
  ✗ buffer 地址和 I/O 大小都必须 512 字节对齐


  模式 B: Buffered I/O (PostgreSQL 默认)
  ═══════════════════════════════════════
  用户态 buffer ── memcpy ──► OS page cache ── writeback ──► 磁盘
      (malloc 即可                (内核 RAM)        (内核择机写出)
       无对齐要求)

  ✓ API 简单,无对齐限制
  ✗ 双重缓存(shared_buffers + page cache 各存一份)
  ✗ write() 返回时数据只在 RAM,掉电会丢
  ✗ 必须 fsync() 才保证落盘

2 异步IO也需要Fsync?

innodb_flush_method = O_DIRECT 时,会执行fsync。

mysql8.0

复制代码
buf_flush_fsync()                      ← buf0flu.cc:3623  一轮刷脏结束后调用
  └► fil_flush_file_spaces()           ← fil0fil.cc:8195  遍历所有需 flush 的表空间
       └► Fil_shard::space_flush()     ← fil0fil.cc:8021
            ├─ fil_disable_space_flushing() ?
            │    是 O_DIRECT_NO_FSYNC → 跳过 (除非文件大小变了)
            │    否 → 继续
            └► os_file_flush()         ← os0file.cc:3003
                 └► os_file_fsync_posix()  ← os0file.cc:2816
                      └► fsync(fd)  或  fdatasync(fd)

3 libaio + directlyio实例

aio_demo.c在文档末尾

bash 复制代码
gcc -g -O0 -o aio_demo aio_demo.c -laio
./aio_demo

执行结果

bash 复制代码
╔══════════════════════════════════════════════════╗
║  Linux AIO + O_DIRECT vs Buffered I/O 对比演示  ║
╚══════════════════════════════════════════════════╝

====== 模式 A: O_DIRECT + libaio (MySQL 方式) ======

[A-1] io_setup OK    ctx=140575005413376
[A-2] io_submit PWRITE  => 1  (非阻塞,立即返回)
[A-3] io_getevents    => 1 event, tag="direct-write", bytes=4096
[A-4] fsync           => 0  (数据从控制器 cache 刷到盘片)
[A-5] io_submit PREAD => 1
[A-6] io_getevents    => 1 event, bytes=4096
[A-7] VERIFY OK: "[Direct I/O] data bypasses page cache, goes straight to disk"

====== 模式 B: Buffered I/O + fsync (PG 方式) ======

[B-1] write()  => 4096 bytes (数据在 page cache,未落盘)
[B-2] fsync()  => 0  (page cache → 磁盘,同步阻塞)
[B-3] VERIFY OK: "[Buffered I/O] data goes to page cache first, fsync later"

====== 总结 ======
O_DIRECT + AIO : io_submit 非阻塞,数据绕过 page cache
                 配合 fsync 确保从控制器 cache 落盘
Buffered + fsync: write 进 page cache,fsync 同步阻塞落盘
                  checkpoint 时大量 fsync = I/O 毛刺

gdb一些关键变量

bash 复制代码
gdb ./aio_demo

io_setup 前后

复制代码
(gdb) break aio_demo.c:107
(gdb) break aio_demo.c:109
(gdb) run

Breakpoint 1, demo_direct_aio () at aio_demo.c:107
107     ret = io_setup(MAX_EVENTS, &ctx);

(gdb) print ctx
$1 = (io_context_t) 0x0              ← 调用前为 NULL

(gdb) continue

Breakpoint 2, demo_direct_aio () at aio_demo.c:109

(gdb) print ctx
$2 = (io_context_t) 0x7ffff7eeb000   ← 内核分配了 AIO 上下文
(gdb) print ret
$3 = 0                                ← 0 = 成功

io_submit(PWRITE):iocb 内容

复制代码
(gdb) break aio_demo.c:152
(gdb) continue

Breakpoint 3, demo_direct_aio () at aio_demo.c:152

(gdb) print cb
$5 = {data = 0x40132e, key = 0, aio_rw_flags = 0,
      aio_lio_opcode = 1,       ← IOCB_CMD_PWRITE
      aio_reqprio = 0,
      aio_fildes = 3,           ← fd
      u = {c = {
        buf = 0x605000,          ← write_buf (对齐地址)
        nbytes = 4096,           ← 一个页
        offset = 0}}}            ← 文件偏移

(gdb) print (char*)write_buf
$6 = 0x605000 "[Direct I/O] data bypasses page cache, goes straight to disk"

io_submit 返回后立即可以继续做其他事(非阻塞),磁盘 DMA 在后台进行:

复制代码
(gdb) break aio_demo.c:153
(gdb) continue

(gdb) print ret
$7 = 1                ← 成功提交 1 个请求,此时磁盘可能还没写完

io_getevents 等写完成

复制代码
(gdb) break aio_demo.c:171
(gdb) continue

(gdb) print events[0]
$9 = {data = 0x40132e, obj = 0x7fffffffcc90, res = 4096, res2 = 0}

(gdb) print (char*)events[0].data
$10 = 0x40132e "direct-write"   ← 我们设的 tag,用于识别是哪个请求

(gdb) print events[0].res
$11 = 4096                      ← 写入 4096 字节

io_event 结构解读

字段 含义 本例值
data 用户设的 iocb.data tag "direct-write"
obj 指向原 struct iocb 0x7fff...cc90
res 正数=字节数,负数=-errno 4096
res2 应恒为 0 0

fsync 确保落盘

复制代码
(gdb) break aio_demo.c:206
(gdb) break aio_demo.c:207
(gdb) continue

Breakpoint, demo_direct_aio () at aio_demo.c:206
206     ret = fsync(fd);     ← 把磁盘控制器 write cache 刷到盘片

(gdb) continue

(gdb) print ret
$12 = 0                      ← 0 = 成功,数据已在盘片上

Buffered write vs fsync

复制代码
(gdb) break aio_demo.c:270
(gdb) break aio_demo.c:271
(gdb) continue

Breakpoint, demo_buffered_write () at aio_demo.c:270
270     ssize_t n = write(fd, buf, PAGE_SIZE_);

(gdb) print (char*)buf
$13 = 0x7fffffffbcd0 "[Buffered I/O] data goes to page cache first, fsync later"

(gdb) continue
(gdb) print n
$14 = 4096              ← 4096 字节到了 page cache,但不在磁盘上!

(gdb) break aio_demo.c:289
(gdb) continue

(gdb) print ret
$15 = 0                 ← fsync 完成,page cache 中的数据现在在磁盘上了

核心数据结构

复制代码
 io_context_t ctx                     struct iocb (提交请求)
 ┌───────────────┐                    ┌──────────────────────┐
 │ 内核 AIO 上下文 │◄── io_setup()      │ aio_lio_opcode = 1   │ (PWRITE)
 │ 指针           │                    │ aio_fildes   = 3     │ (fd)
 └───────────────┘                    │ aio_buf    = 0x605000│ (write_buf, 对齐)
        │                             │ aio_nbytes = 4096    │
        │  io_submit(ctx, 1, &cb)     │ aio_offset = 0       │
        │◄────────────────────────────│ data = "direct-write"│ (user tag)
        │    [非阻塞返回]              └──────────────────────┘
        │
        │  io_getevents(ctx, ...)     struct io_event (完成事件)
        │────────────────────────────► ┌──────────────────────┐
        │    [阻塞等完成]               │ data = "direct-write"│
                                       │ obj  = &cb           │
        fsync(fd)                      │ res  = 4096          │
        │                              │ res2 = 0             │
        │ [控制器 cache → 盘片]         └──────────────────────┘
        ▼
     数据持久化完成

4 用例源码

复制代码
/*
 * aio_demo.c  ------ Linux Native AIO + O_DIRECT 完整演示
 *               仿 MySQL/InnoDB 的 I/O 模式
 *
 * 演示两种模式的对比:
 *   模式 A: O_DIRECT + libaio  (MySQL InnoDB 默认方式)
 *   模式 B: 普通 buffered I/O  (PostgreSQL 方式)
 *
 * Build:  gcc -g -O0 -o aio_demo aio_demo.c -laio
 * Run  :  ./aio_demo
 */
#define _GNU_SOURCE
#include <fcntl.h>
#include <libaio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/*
 * ============================================================
 *  关键常量
 * ============================================================
 *
 *  PAGE_SIZE = 4096
 *    InnoDB 默认页大小是 16KB,这里简化为 4KB(一个 OS page)。
 *    O_DIRECT 要求:buf 地址必须 512 对齐,I/O 大小必须 512 倍数。
 *
 *  MAX_EVENTS = 10
 *    io_setup 时告诉内核:这个 AIO context 最多同时有多少个
 *    飞行中(in-flight)的请求。MySQL 中这个值是
 *    8 * OS_AIO_N_PENDING_IOS_PER_THREAD(通常几百个)。
 */
#define PAGE_SIZE_  4096
#define MAX_EVENTS  10
#define FILE_DIRECT "/tmp/aio_direct.dat"
#define FILE_BUFFER "/tmp/aio_buffer.dat"

/* ============================================================
 *  辅助函数:分配 O_DIRECT 要求的对齐内存
 * ============================================================
 *
 * 为什么需要对齐?
 * ─────────────────
 * O_DIRECT 绕过 OS page cache,数据从用户态 buffer 直接 DMA 到磁盘。
 * DMA 引擎要求源地址按 512 字节(磁盘扇区大小)对齐。
 * 如果用 malloc 分配的内存,地址可能是 0x...1a3 这样的奇数地址,
 * DMA 传输会失败,write 返回 -EINVAL。
 *
 * MySQL 源码中 (ut/ut0mem.cc) 也用 posix_memalign / _aligned_malloc。
 */
static void *alloc_aligned(size_t size)
{
    void *p = NULL;
    if (posix_memalign(&p, PAGE_SIZE_, size) != 0) {
        perror("posix_memalign");
        exit(1);
    }
    memset(p, 0, size);
    return p;
}

/* ============================================================
 *  演示 A:O_DIRECT + libaio  ------ MySQL/InnoDB 的方式
 * ============================================================
 *
 *  数据路径(O_DIRECT):
 *
 *   用户态 buffer ────DMA────► 磁盘控制器 ────► 磁盘盘片
 *                  (绕过 page cache)
 *
 *   - 优点:没有 page cache 这层中间缓存
 *           1) 避免双重缓存(InnoDB Buffer Pool 已经是缓存了)
 *           2) io_submit 返回后数据已经到达磁盘控制器
 *              (注意:是控制器的 write cache,不一定落盘片)
 *   - 代价:buffer 必须对齐,I/O 大小必须对齐
 *
 *  MySQL 源码对应:
 *   - 打开文件时设置 O_DIRECT:  os0file.cc:3214
 *   - 条件: srv_unix_file_flush_method == SRV_UNIX_O_DIRECT
 *           或 SRV_UNIX_O_DIRECT_NO_FSYNC
 */
static void demo_direct_aio(void)
{
    printf("====== 模式 A: O_DIRECT + libaio (MySQL 方式) ======\n\n");

    io_context_t    ctx = 0;
    struct iocb     cb;
    struct iocb    *cbs[1] = { &cb };
    struct io_event events[MAX_EVENTS];
    int             ret;

    void *write_buf = alloc_aligned(PAGE_SIZE_);
    void *read_buf  = alloc_aligned(PAGE_SIZE_);
    snprintf((char *)write_buf, PAGE_SIZE_,
             "[Direct I/O] data bypasses page cache, goes straight to disk");

    /* ── Step 1: io_setup ── 创建内核 AIO 上下文 ──
     *
     * 内核分配一块 mmap 区域,用于用户态和内核态共享 AIO 状态。
     * ctx 是一个指向该区域的 "句柄"。
     *
     * MySQL: AIO::linux_create_io_ctx()  os0file.cc:2572
     *        memset(io_ctx, 0); io_setup(max_events, io_ctx);
     */
    ret = io_setup(MAX_EVENTS, &ctx);
    if (ret < 0) { perror("io_setup"); return; }
    printf("[A-1] io_setup OK    ctx=%lu\n", (unsigned long)ctx);

    /* ── 打开文件:O_DIRECT 是关键 ──
     *
     * O_DIRECT 告诉内核:
     *   1) 不要把数据拷贝到 page cache
     *   2) 直接从我的 buffer DMA 到磁盘
     *   3) 读的时候也直接从磁盘 DMA 到我的 buffer
     *
     * 对比普通 open():
     *   普通 write → 拷贝到 page cache → 内核随后 writeback → 磁盘
     *   O_DIRECT  → DMA 直达磁盘控制器
     *
     * MySQL: os_file_create_func()  os0file.cc:3214
     *   if (srv_unix_file_flush_method == SRV_UNIX_O_DIRECT)
     *       use_odirect = true;  // 然后 fcntl(fd, F_SETFL, O_DIRECT)
     */
    int fd = open(FILE_DIRECT, O_RDWR | O_CREAT | O_DIRECT | O_TRUNC, 0644);
    if (fd < 0) { perror("open O_DIRECT"); return; }
    ftruncate(fd, PAGE_SIZE_);  /* 预分配空间 */

    /* ── Step 2: io_submit(PWRITE) ── 异步写 ──
     *
     * io_prep_pwrite 填充 struct iocb:
     *   cb.aio_lio_opcode = IOCB_CMD_PWRITE  (写操作)
     *   cb.aio_fildes     = fd
     *   cb.aio_buf        = write_buf         (必须对齐!)
     *   cb.aio_nbytes     = PAGE_SIZE_        (必须 512 倍数!)
     *   cb.aio_offset     = 0
     *
     * io_submit 把这个 iocb 提交给内核。因为 O_DIRECT,
     * 内核会直接安排 DMA,不经过 page cache。
     *
     * 重要:io_submit 是【非阻塞】的!
     * 提交后立刻返回,不等磁盘写完。
     * MySQL 的 Page Cleaner 就是靠这个特性实现
     * "提交一批脏页后继续干别的"。
     *
     * MySQL: AIO::linux_dispatch()  os0file.cc:2543-2556
     *        io_submit(m_aio_ctx[io_ctx_index], 1, &iocb);
     */
    io_prep_pwrite(&cb, fd, write_buf, PAGE_SIZE_, 0);
    cb.data = (void *)"direct-write";  /* 自定义 tag,用于收割时识别请求 */
    ret = io_submit(ctx, 1, cbs);
    printf("[A-2] io_submit PWRITE  => %d  (非阻塞,立即返回)\n", ret);

    /* ── Step 3: io_getevents ── 等待写完成 ──
     *
     * 阻塞等待至少 1 个 AIO 请求完成。
     * 返回值 = 完成的事件数。
     *
     * events[i].data = 之前设置的 cb.data(用户 tag)
     * events[i].obj  = 指向原 iocb
     * events[i].res  = 正数表示字节数,负数表示 -errno
     * events[i].res2 = 应恒为 0
     *
     * MySQL: LinuxAIOHandler::collect()  os0file.cc:2334
     *   io_getevents(io_ctx, 1, m_n_slots, events, &timeout);
     *   然后遍历 events,通过 iocb->data 找回 Slot*
     */
    ret = io_getevents(ctx, 1, MAX_EVENTS, events, NULL);
    printf("[A-3] io_getevents    => %d event, tag=\"%s\", bytes=%lld\n",
           ret, (char *)events[0].data, (long long)events[0].res);

    /* ── Step 4: fsync ── 确保数据从磁盘控制器落到盘片 ──
     *
     * 关键问题:O_DIRECT 写完后,数据真的在磁盘上了吗?
     *
     *          不一定!
     *
     * O_DIRECT 只保证数据绕过了 OS page cache,到达了磁盘控制器。
     * 但磁盘控制器有自己的 write cache(volatile cache)。
     * 如果此时掉电,write cache 中的数据会丢失。
     *
     * 所以 MySQL 仍然需要 fsync!
     *
     * MySQL 的三种刷盘策略 (innodb_flush_method):
     *
     *   ┌──────────────────────┬────────────┬──────────┐
     *   │  flush_method        │  O_DIRECT  │  fsync   │
     *   ├──────────────────────┼────────────┼──────────┤
     *   │  fsync (默认旧版)     │    否      │  每轮刷  │
     *   │  O_DIRECT            │    是      │  每轮刷  │
     *   │  O_DIRECT_NO_FSYNC   │    是      │  不刷*   │
     *   └──────────────────────┴────────────┴──────────┘
     *   * O_DIRECT_NO_FSYNC:8.0.14+ 的默认值。
     *     假设硬件有电池保护 write cache (BBU/FBU),
     *     数据到达控制器就等于落盘,跳过 fsync 获得更高性能。
     *     如果文件大小变化(如扩表空间),仍然会 fsync 元数据。
     *
     * MySQL 源码:
     *   - os_file_fsync_posix() os0file.cc:2816 调用 fsync/fdatasync
     *   - fil_flush() fil0fil.cc:8153 对单个表空间 flush
     *   - buf_flush_fsync() buf0flu.cc:3623 一轮刷脏后统一 flush
     *   - fil_disable_space_flushing() fil0fil.cc:641
     *     当 O_DIRECT_NO_FSYNC 时跳过 fsync(除非文件大小变了)
     */
    ret = fsync(fd);
    printf("[A-4] fsync           => %d  (数据从控制器 cache 刷到盘片)\n", ret);

    /* ── Step 5: 异步读回验证 ── */
    io_prep_pread(&cb, fd, read_buf, PAGE_SIZE_, 0);
    cb.data = (void *)"direct-read";
    ret = io_submit(ctx, 1, cbs);
    printf("[A-5] io_submit PREAD => %d\n", ret);

    ret = io_getevents(ctx, 1, MAX_EVENTS, events, NULL);
    printf("[A-6] io_getevents    => %d event, bytes=%lld\n",
           ret, (long long)events[0].res);

    /* ── 校验 ── */
    if (memcmp(write_buf, read_buf, PAGE_SIZE_) == 0)
        printf("[A-7] VERIFY OK: \"%s\"\n", (char *)read_buf);
    else
        printf("[A-7] VERIFY FAILED!\n");

    io_destroy(ctx);
    close(fd);
    unlink(FILE_DIRECT);
    free(write_buf);
    free(read_buf);
    printf("\n");
}

/* ============================================================
 *  演示 B:普通 Buffered I/O ------ PostgreSQL 的方式
 * ============================================================
 *
 *  数据路径(Buffered I/O,无 O_DIRECT):
 *
 *   用户态 buffer ─── memcpy ──► OS page cache ─── writeback ──► 磁盘
 *                  (内核会攒着)         (内核择机写出)
 *
 *   write() 返回时,数据只是到了 page cache,不在磁盘上!
 *   必须 fsync() 才能保证落盘。
 *
 *   PostgreSQL 的策略:
 *     bgwriter: 定期 write() 少量脏页到 page cache(不 fsync)
 *     checkpoint: 遍历所有脏页 write(),然后逐文件 fsync()
 *     → checkpoint 时大量 fsync 导致 I/O 毛刺
 */
static void demo_buffered_write(void)
{
    printf("====== 模式 B: Buffered I/O + fsync (PG 方式) ======\n\n");

    char buf[PAGE_SIZE_];  /* 不需要对齐,因为没有 O_DIRECT */
    memset(buf, 0, sizeof(buf));
    snprintf(buf, sizeof(buf),
             "[Buffered I/O] data goes to page cache first, fsync later");

    /* 普通 open,没有 O_DIRECT */
    int fd = open(FILE_BUFFER, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) { perror("open buffered"); return; }

    /* ── write:数据只到 page cache ──
     *
     * 此时如果掉电,数据【会丢失】!
     * 因为 page cache 在 RAM 中,掉电即消失。
     * 这就是 PostgreSQL bgwriter 调 write() 后
     * 数据不安全的原因。
     */
    ssize_t n = write(fd, buf, PAGE_SIZE_);
    printf("[B-1] write()  => %zd bytes (数据在 page cache,未落盘)\n", n);

    /* ── fsync:强制 page cache 中的数据写入磁盘 ──
     *
     * fsync 做了什么?
     *   1) 遍历该 fd 对应文件在 page cache 中的所有脏页
     *   2) 对每个脏页发起 I/O 写磁盘
     *   3) 等待所有 I/O 完成
     *   4) 写入文件元数据(inode: mtime, size 等)
     *   5) 全部完成后才返回
     *
     * 注意:fsync 是 【同步阻塞】的!
     * 如果 page cache 中有 1GB 的脏数据,
     * fsync 会阻塞直到 1GB 全部落盘。
     *
     * PostgreSQL checkpoint 时的 I/O 毛刺就来源于此。
     */
    int ret = fsync(fd);
    printf("[B-2] fsync()  => %d  (page cache → 磁盘,同步阻塞)\n", ret);

    /* 读回验证 */
    char rbuf[PAGE_SIZE_];
    lseek(fd, 0, SEEK_SET);
    n = read(fd, rbuf, PAGE_SIZE_);
    if (memcmp(buf, rbuf, PAGE_SIZE_) == 0)
        printf("[B-3] VERIFY OK: \"%s\"\n", rbuf);
    else
        printf("[B-3] VERIFY FAILED!\n");

    close(fd);
    unlink(FILE_BUFFER);
    printf("\n");
}

/* ============================================================
 *  main
 * ============================================================ */
int main(void)
{
    printf("╔══════════════════════════════════════════════════╗\n");
    printf("║  Linux AIO + O_DIRECT vs Buffered I/O 对比演示  ║\n");
    printf("╚══════════════════════════════════════════════════╝\n\n");

    /*
     * I/O 路径对比图:
     *
     * 模式 A (MySQL/InnoDB):
     *   App buf ──DMA──► 磁盘控制器 ──► 盘片
     *            (O_DIRECT, 绕过 page cache)
     *            (io_submit 非阻塞)
     *            (fsync 刷控制器 cache 到盘片)
     *
     * 模式 B (PostgreSQL):
     *   App buf ──memcpy──► page cache ──writeback──► 磁盘
     *            (write 只到 cache)
     *            (fsync 时才保证落盘,同步阻塞)
     */

    demo_direct_aio();
    demo_buffered_write();

    printf("====== 总结 ======\n");
    printf("O_DIRECT + AIO : io_submit 非阻塞,数据绕过 page cache\n");
    printf("                 配合 fsync 确保从控制器 cache 落盘\n");
    printf("Buffered + fsync: write 进 page cache,fsync 同步阻塞落盘\n");
    printf("                  checkpoint 时大量 fsync = I/O 毛刺\n");
    return 0;
}

下一篇继续读mysql aio的代码。

相关推荐
yoothey7 小时前
MySQL 数据库连接池调优实战
数据库·mysql
Tian_Hang8 小时前
Eclipse Mosquitto 安装及介绍
java·运维·服务器·ide·sql·mysql·eclipse
南墙上的石头20 小时前
麒麟 V10 重装人大金仓 V8R6 踩坑实录(含 MySQL 兼容模式)
数据库·mysql
执子手 吹散苍茫茫烟波1 天前
RC 隔离级别下 MySQL InnoDB 死锁典型案例
数据库·mysql
峥无1 天前
深入理解MySQL事务与MVCC机制
数据库·mysql
要开心吖ZSH1 天前
MVCC 进阶:快照读 vs 当前读、幻读与 Next-Key Lock
java·数据库·sql·mysql·mvcc
万亿少女的梦1681 天前
基于Spring Boot的天空影院电影网站系统设计与实现
java·spring boot·mysql·vue·系统设计
万亿少女的梦1681 天前
基于Spring Boot的社区管理系统设计与实现
java·spring boot·mysql·vue·系统设计
翔云1234561 天前
简单概括主库上 Executed_Gtid_Set 是什么时候更新的
数据库·mysql
要开心吖ZSH1 天前
Java事务与MySQL事务的关系及MVCC通俗解析
java·开发语言·mysql·mvcc