0 总结速查
-
mysql innodb支持使用LIBAIO和O_DIRECT完成刷脏。刷脏是异步的,
io_prep_pwrite+io_submit调用完就返回了,后面**io_getevents**拿结果。相对于pg略微复杂,pg是双缓冲,使用sharedbuffer和page cache,写盘比较简单,直接write配合fsync即可。 -
虽然做了10年pg,但从性能角度看mysql的方案确实更平缓一些,例如pg如果脏页太多集中checkpoint时刷一堆经常会抖,mysql平时一直刷checkpoint只需要推lsn会比较平缓。pg双缓存严重依赖
pagec ache,这东西又不在pg控制范围内,增加了一些复杂度,例如预热等场景还需要插件去间接影响page cache,如果单缓存会更直接。 -
关于
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看起来就是平时少、集中批量刷 。- clock sweep(Postgresql源码(5)缓冲区管理),挑选脏页刷盘。
- 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的代码。