buffer pool
(缓冲池
)是主内存
中的一个区域,在InnoDB访问表数据
和索引数据
的时候,会顺便把对应的数据页缓存到缓冲池中。如果直接从缓冲池中直接读取数据将会加快处理速度。在专用服务器上,通常将80%左右的物理内存分配给缓冲池。
为了提高缓存管理效率,缓冲池把页面链接为列表,使用改进版的LRU算法
将很少使用的数据从缓存中老化淘汰掉。
1、缓冲池LRU算法
通过使用改进版的LRU算法来管理缓冲池列表。
当需要把新页面存储到缓冲池中的时候,将淘汰最近最少使用的页面,并将新页面添加到旧子列表的头部。
其中:
- 旧子列表:也可以成为Old区,存储冷数据;
- 新子列表:也成为Young区,存储热数据。
该算法运行方式:
- 默认 3/8缓冲池用于旧子列表;
- 当新页面加入缓冲池时,首先将其插入旧子列表头部;
- 重复访问旧子列表的页面,将使其移动至新子列表的头部;
- 随着数据库的运行,页面逐步移至列表尾部,缓冲池中未被访问的页面最终将被老化淘汰。
相关优化参数:
innodb_old_blocks_pct
:控制LRU列表中旧子列表的百分比,默认是37,也就是3/8,可选范围为5~95;innodb_old_blocks_time
:指定第一次访问页面后的时间窗口,该时间窗口内访问页面不会使其移动到LRU列表的最前面。默认是1000,也就是1秒。
innodb_old_blocks_time很重要,有了这1秒,对于全表扫描,由于是顺序扫描的,一般同一个数据页的数据都是在一秒内访问完成的,不会升级到新子列表中,一直在旧子列表淘汰数据,所以不会影响到新子列表的缓存。
2、关于磁盘IO的方式
O_DIRECT
是innodb_flush_method
参数的一个可选值。
这里先介绍下和数据库性能密切相关的文件IO操作方法
2.1、文件IO操作方法
数据库系统是基于文件系统的,其性能和设备读写的机制有密切的关系。
open:打开文件[1]
1 | int open(const char *pathname, int flags); |
系统调用Open会为该进程一个文件描述符fd,常用的flags如下:
O_WRONLY
:表示我们以"写"的方式打开,告诉内核我们需要向文件中写入数据;O_DSYNC
:每次write都等待物理I/O完成,但是如果写操作不影响读取刚写入的数据,则不等待文件属性更新;O_SYNC
:每次write都等到物理I/O完成,包括write引起的文件属性的更新;O_DIRECT
:执行磁盘IO时绕过缓冲区高速缓存(内核缓冲区),从用户空间直接将数据传递到文件或磁盘设备,称为直接IO(direct IO)。因为没有了OS cache,所以会O_DIRECT降低文件的顺序读写的效率。
write:写文件[2]
1 | ssize_t write(int fd, const void *buf, size_t count); |
使用open打开文件获取到文件描述符之后,可以调用write函数来写文件,具体表现根据open函数参数的不同而不同弄。
fsync & fdatasync:刷新文件[3]
1 |
|
fdatasync
:操作完write之后,我们可以调用fdatasync将文件数据块flush到磁盘,只要fdatasync返回成功,则可以认为数据已经写到磁盘了;fsync
:与O_SYNC参数类似,fsync还会更新文件metadata到磁盘;sync
:sync只是将修改过的块缓冲区写入队列,然后就返回,不等实际写磁盘操作完成;
为了保证文件更新成功持久化到硬盘,除了调用write方法,还需要调用fsync。
大致交互流程如下图:
更多关于磁盘IO的相关内容,可以阅读:On Disk IO, Part 1: Flavors of IO[4]
**fsync性能问题:**除了刷脏页到磁盘,fsync还会同步文件metadata,而文件数据和metadata通常存放在磁盘不同地方,所以fsync至少需要两次IO操作。
对fsync性能的优化建议:由于以上性能问题,如果能够减少metadata的更新,那么就可以使用fdatasync了。因此需要确保文件的尺寸在write前后没有发生变化。为此,可以创建固定大小的文件进行写,写完则开启新的文件继续写。
2.2、innodb_flush_method
innodb_flush_method
定义用于将数据刷新到InnoDB
数据文件和日志文件的方法,这可能会影响I/O吞吐量。
以下是具体参数说明:
属性 | 值 |
---|---|
命令行格式 | –innodb-flush-method=value |
系统变量 | innodb_flush_method |
范围 | 全局 |
默认值(Windows) | unbuffered |
默认值(Unix) | fsync |
有效值(Windows) | unbuffered, normal |
有效值(Unix) | fsync, O_DSYNC, littlesync, nosync, O_DIRECT, O_DIRECT_NO_FSYNC |
比较常用的是这三种:
fsync
默认值,使用fsync()
系统调用来flush数据文件和日志文件到磁盘;
O_DSYNC
由于open函数的O_DSYNC参数在许多Unix系统上都存中问题,因此InnoDB不直接使用O_DSYNC。
InnoDB
用于O_SYNC
打开和刷新日志文件,fsync()
刷新数据文件。
表现为:写日志操作是在write函数完成,数据文件写入是通过fsync()
系统调用来完成;
O_DIRECT
使用O_DIRECT
(在Solaris上对应为directio()
)打开数据文件,并用于fsync()
刷新数据文件和日志文件。此选项在某些GNU/Linux版本,FreeBSD和Solaris上可用。
表现为:数据文件写入直接从buffer pool到磁盘,不经过操作系统缓冲,日志还是需要经过操作系统缓存;
O_DIRECT_NO_FSYNC
在刷新I/O期间InnoDB
使用O_DIRECT
,并且每次write操作后跳过fsync()
系统调用。
此设置适用于某些类型的文件系统,但不适用于其他类型的文件系统。例如,它不适用于XFS。如果不确定所使用的文件系统是否需要fsync()(例如保留所有文件元数据),请改用O_DIRECT。
如下图所示:
为什么使用了O_DIRECT配置后还需要调用fsync()?
参考MySQL的这个bug:Innodb calls fsync for writes with innodb_flush_method=O_DIRECT[5]
Domas进行的一些测试表明,如果没有fsync,某些文件系统(XFS)不会同步元数据。如果元数据会更改,那么您仍然需要使用fsync(或O_SYNC来打开文件)。
例如,如果在启用O_DIRECT的情况下增大文件大小,它仍将写入文件的新部分,但是由于元数据不能反映文件的新大小,因此如果此刻系统发生崩溃,文件尾部可能会丢失。
为此:当重要的元数据发生更改时,请继续使用fsync或除O_DIRECT之外,也可以选择使用O_SYNC。
MySQL从v5.6.7起提供了O_DIRECT_NO_FSYNC
选项来解决此类问题。
References
Linux Programmer’s Manual - OPEN(2). (2020-02-09). Retrieved from http://man7.org/linux/man-pages/man2/open.2.html ↩︎
man-pages.write. (2019-10-10). Retrieved from http://man7.org/linux/man-pages/man2/write.2.html ↩︎
man-pages.fdatasync. (2019-03-06). Retrieved from http://man7.org/linux/man-pages/man2/fdatasync.2.html ↩︎
On Disk IO, Part 1: Flavors of IO. medium.com. Retrieved from https://medium.com/databasss/on-disk-io-part-1-flavours-of-io-8e1ace1de017 ↩︎
Innodb calls fsync for writes with innodb_flush_method=O_DIRECT. Retrieved from https://bugs.mysql.com/bug.php?id=45892 ↩︎