跟游族面试官有谈论到InnoDB Flush Redo Log Buffer的配置与i/o操作。MySql Innodb引擎支持事务,实现方式是WAL方式(先写事务日记,后写数据)。每次提交事务,为了提高性能,innodb先将信息保存在Innodb Log Buffer Pool中(Log Buffer Pool存在于用户态mysqld进程内存空间)。当Log Buffer Pool写满(其大小由innodb_log_buffer_size参数设定)或者满足参数innodb_flush_log_at_trx_commit设置条件时,innodb都会把log buffer pool写到日志文件,并同步到磁盘设备上(ps:同步数据受参数innodb_flush_method影响)。我们可以把写日志文件用write()来表示,把同步到磁盘设备用fsync()来表示。
innodb_flush_log_at_trx_commit
参数innodb_flush_log_at_trx_commit决了什么时候write(),什么时候fsync()。write()意味着把用户态进程mysqld内存空间log buffer pool的数据复制到内核态线程内存空间中(即page cache),此时,日志数据并没有写至磁盘设备,直到fsync()才会把页面缓存中的日志数据同步到磁盘设备上(这里忽略硬盘可能欺骗系统内核缓冲区已经写回磁盘的情况)。参数innodb_flush_log_at_trx_commit,可以取三个值,分别是1,0,2,默认值1。不同的取值,会有不同的flush行为
0, 每次提交事务,不进行任何操作,即不write(),也不fsync()。但是每隔1s时间,会write(),同时会fsync()。
1, 每次提交事务,都会write(),也会fsync()。这样保证完整的ACID。
2, 每次提交事务,只会write(),但不fsync()清理内核缓冲区。InnoDB安排每1s进行一次fsync()。也就是说,即使mysqld守护进程挂了,也不丢失任何事务。
注意:
a.值1,容易产生大量同步I/O操作。每次同步I/O操作,都会阻止I/O调用,直到数据被真正写回磁盘(写磁盘较写内存慢很多),这样就会显著地降低InnoDB每秒可以提交的事务数。
b.值2或值0,意味着更少的调用fsync(),但有可能丢失1s的事务。如果愿意对事务安全折衷,对事务要求不高。可以设置值2,甚至值0来减少事务引起的磁盘I/O。
c.上文提到的每秒fsync()一次,也不能确保百分之百准确,必竟这是mysqld的机制,还有赖于系统进程调度等因素。
innodb_flush_method
通常使用fsync()调用flush数据与日志文件,但也可以通过innodb_flush_method参数来改变innodb执行I/O的方法。在linux下,可以取值fdatasync、O_DSYNC、O_DIRECT。
1.fdatasync, 调用fdatasync()跟fsync()调用一样,同步系统缓冲区的数据至磁盘设备(内存到硬盘)。不一样的是fdatasync(),它只会写回数据,而无法保证元数据与磁盘是同步的。因此,速度可能会更快。尽管这样,InnoDB开发人员非常保守,他们发现fdatasync()在某些情况下会崩溃,转而使用fsync()调用。当然,innodb会根据编译配置以及运行时配置,决定使用安全又最快的方法。
2.O_DSYNC,它使调用open()打开日志文件(不包括数据文件)时,会使用O_SYNC标志。该O_SYNC标志确保write()调用会执行I/O的同步化。至于标志O_SYNC,你可以把它想象为,每次调用write()之后以及从调用返回之前隐式地调用了fsync()。尽管这样,它们在操作系统和硬件层面的实现是不一样的,Linux内核所实现的O_SYNC的效率更高一点。注意:虽然是O_DSYNC,但open()调用使用的标志是O_SYNC,linux没有定义O_DSYNC。
3.O_DIRECT, InnoDB根据操作系统决定是否使用open()的O_DIRECT标志,linux支持该标志。但是仅作用于数据文件,而不影响日记文件。即便是这样,InnoDB还是会调用fsync()来确保数据写回至磁盘。只不过,数据文件的open()调用使用了O_DIRECT标志,意味着write()调用,直接把数据从用户空间缓存区写至磁盘,而不是写入系统缓冲区。也就是说,它希望系统内核不要提前读取,也不要延后写入,以此避免了双缓冲(mysql缓冲与page cache)。
注意:
1.仅值O_DSYNC,会影响到日志文件的I/O同步化操作,设置该值flush日志文件的时候,不会调用fsync()或fdatasync()。
2.改变InnoDB如何执行I/O操作会极大的影响性能,改变后需要仔细评测它们。
3.尽管写数据文件时没有将数据写至内核缓冲区,但innodb还是会调用fsync()确定数据安全写至磁盘。(^_^是不是觉得有点浪费)
补充知识
1.Linux2.6提供了标准访问文件、同步访问文件、直接I/O、内存映射、异步访问文件等方式。
2.Linux 文件I/O相关的系统内核调用
int open(const char *name, int flags);
ssize_t read(int fd, void *buf, size_t len);
ssize_t write(int fd, const void *buf, size_t count);
int fsync(int fd);
int fdatasync(int fd);
void sync();
int close(int fd);
通过open()调用打开一个文件,根据flags不同,read()/write()的行为会有所不同。比如O_SYNC表示同步I/O,O_DIRECT表示直接I/O。一般read()或者write()只是从内核缓冲区读写数据,这让写入调用快如闪电,数据会被延后至内核唤醒内核线程pdflush才将数据写回磁盘。当然,有些应用 程序可能需要自己确保将内核缓冲的数据写回磁盘, 这时可以调用fsync(),fdatasync(),当然也可以打开open()文件的时候设置O_SYNC标志。类似数据库这样的守护进程应用,自己实现了缓存机制,不希望系统内核重复缓存数据,可以提供O_DIRECT标志给open(),绕过系统内核提供的页面缓存,write()的时候,可以直接将用户空间缓冲区的数据写至磁盘。
3.标准I/O(标准c链接库提供的标准I/O链接库)
FILE *fopen(const char *path, const char *mode);
FILE *fdopen(int fd, const char *mode);
size_t fread(void *buf, size_t size, size_t num, FILE *stream);
size_t fwrite(void *buf, size_t size, size_t num, FILE *stream);
int fclose(FILE *stream);
int fflush(FILE *stream);
标准I/O,即c链接库提供了用户空间缓冲区,fflush()只是将用户空间缓冲区数据复制到系统内核缓冲区。要想及时将内核缓冲区数据写入到磁盘,还需要调用fflush(),接着调用fsync()。通常将这种用户态进程或者链接库缓冲机制称为缓冲式I/O。与 write()调用不同的是,fwrite()只是将数据写至用户空间缓存区,这样减少了系统内核调用(write())降低开销,提高效率。
————————–
〈〈linux system programming〉〉
〈〈高性能MySQL〉〉
Linux 中直接 I/O 机制的介绍
Linux 内核的文件 Cache 管理机制介绍
转载请注明:爱开源 » innodb flush redo log