本文我们来简单过一下InnoDB的IO子系统相关模块的代码逻辑。主要包括IO读写线程、预读逻辑、InnoDB读写Page以及社区的一些改进。

前言

InnoDB对page的磁盘操作分为读操作和写操作。
对于读操作,在将数据读入磁盘前,总是为其先预先分配好一个block,然后再去磁盘读取一个新的page,在使用这个page之前,还需要检查是否有change buffer项,并根据change buffer,进行数据变更。
读操作分为两种场景:普通的读page及预读操作,前者为同步读,后者为异步读
Page写操作也分为两种,一种是batch write,一种是single page write。写page默认受double write buffer保护,因此对double write buffer的写磁盘为同步写,而对数据文件的写入为异步写。
同步读写操作通常由用户线程来完成,而异步读写操作则需要后台线程的协同。
举个简单的例子,假设我们向磁盘批量写数据,首先先写到double write buffer,当dblwr满了之后,一次性将dblwr中的数据同步刷到Ibdata,在确保sync到dblwr后,再将这些page分别异步写到各自的文件中。注意这时候dblwr依旧未被清空,新的写Page请求会进入等待。
当异步写page完成后,io helper线程会调用buf_flush_write_complete,将写入的Page从flush list上移除。当dblwr中的page完全写完后,在函数buf_dblwr_update里将dblwr清空。这时候才允许新的写请求进dblwr。
同样的,对于异步写操作,也需要IO Helper线程来检查page是否完好、merge change buffer等一系列操作。
除了page的写入,还包括日志异步写入线程、及ibuf后台线程。

后台线程

* IO READ 线程 —- 后台读线程数,线程数目通过参数innodb_read_io_threads配置
主要处理INNODB 数据文件异步读请求,任务队列为os_aio_read_array,任务队列包含slot数为线程数 * 256(linux 平台),也就是说,每个read线程最多可以pend 256个任务;
* IO WRITE 线程 —- 后台写线程数,线程数目通过参数innodb_write_io_threads配置
主要处理INNODB 数据文件异步写请求,任务队列为os_aio_write_array,任务队列包含slot数为线程数 * 256(linux 平台),也就是说,每个read线程最多可以pend 256个任务;
* LOG 线程 — 写日志线程
只有在写checkpoint信息时才会发出一次异步写请求。任务队列为os_aio_log_array,共1个segment,包含256个slot
* IBUF 线程 — 负责读入change buffer页的后台线程,任务队列为os_aio_ibuf_array,共1个segment,包含256个slot
所有的同步写操作都是由用户线程或其他后台线程执行。上述IO线程只负责异步操作。

发起请求

入口函数:os_aio_func
a.首先对于同步读写请求(OS_AIO_SYNC),发起请求的线程直接调用os_file_read_func 或者os_file_write_func 去读写文件 ,然后返回
b.对于异步请求,用户线程从对应操作类型的任务队列中选取一个slot,将需要读写的信息存储于其中(os_aio_array_reserve_slot):
##首先在任务队列数组中选择一个segment
local_seg = (offset >> (UNIV_PAGE_SIZE_SHIFT + 6))
% array->n_segments;
这里根据偏移量来算segment,因此可以尽可能的将相邻的读写请求放到一起,这有利于在IO层的合并操作。
##然后加mutex,遍历该segement,选择空闲的slot,如果没有则等待。
##将对应的文件读写请求信息赋值到slot中,例如写入的目标文件,偏移量,数据等
     slot->is_reserved = true;slot->reservation_time = ut_time();slot->message1 = message1;slot->message2 = message2;slot->file     = file;slot->name     = name;slot->len      = len;slot->type     = type;slot->buf      = static_cast<byte*>(buf);slot->offset   = offset;slot->io_already_done = false;……//对于Native AIO 还需要调用如下逻辑aio_offset = (off_t) offset;ut_a(sizeof(aio_offset) >= sizeof(offset)|| ((os_offset_t) aio_offset) == offset);iocb = &slot->control;if (type == OS_FILE_READ) {io_prep_pread(iocb, file, buf, len, aio_offset);} else {ut_a(type == OS_FILE_WRITE);io_prep_pwrite(iocb, file, buf, len, aio_offset);}iocb->data = (void*) slot;slot->n_bytes = 0;slot->ret = 0;

c.对于Native AIO (使用linux自带的LIBAIO库),调用函数os_aio_linux_dispatch,将IO请求分发给kernel层。
d.如果没有开启Native AIO,且没有设置wakeup later 标记,则会去唤醒io线程(os_aio_simulated_wake_handler_thread),这是早期libaio还不成熟时,InnoDB在内部模拟aio实现的逻辑。
Tips:编译Native AIO需要安装Libaio-dev包,并打开选项srv_use_native_aio

处理异步AIO请求

IO线程入口函数为io_handler_thread –> fil_aio_wait
a. 对于Native AIO,调用函数os_aio_linux_handle 获取读写请求
IO线程会反复以500ms的超时时间通过io_getevents确认是否有任务已经完成了(函数os_aio_linux_collect),如果有读写任务完成,则返回上层函数
逻辑中还处理了AIO部分读写的场景,这里会再次提交aio请求。(什么场景会这样 ??)
找到已完成任务的slot后,释放对应的槽位。(os_aio_array_free_slot)
b.对于simulated aio,调用函数os_aio_simulated_handle 获取读写请求,这里相比NATIVE AIO要复杂些
##首先,如果这是异步读队列,并且os_aio_recommend_sleep_for_read_threads被设置,则暂时不处理,而是等待一会,让其他线程有机会将更过的IO请求发送过来。目前linear readhaed 会使用到该功能。这样可以得到更好的IO合并效果。
##如果有超过2秒未被调度的请求,则选择最老的slot,防止饿死,否则,找一个文件读写偏移量最小的位置的slot.
##根据上一步找到的slot,遍历其他操作,找到与其连续的IO请求,加入数组consecutive_ios中。直到遍历完成,后者数组中slot个数超过64
## 根据连续IO的slot数,分配新的内存块,并进行一次IO读或写。
c. 调用函数fil_node_complete_io, 递减node->n_pending, 对于文件写操作,需要加入到fil_system->unflushed_spaces链表上,表示这个文件修改过了,后续需要被sync到磁盘。
如果设置为O_DIRECT_NO_FSYNC,对于数据文件,无需加入到unflushed_spaces链表上。这在某些文件系统上是可行的。(fil_buffering_disabled)
d. 对于数据文件读写或IMPORT操作,调用buf_page_io_complete,做page corruption检查、change buffer merge等操作;对于LRU FLUSH产生的写操作,还会将其对应的block释放到free list上;对于日志文件操作,调用log_io_complete执行一次fil_flush,并更新内存内的checkpoint信息(log_complete_checkpoint)

并发控制

a. 由于文件底层使用pwrite/pread来进行文件I/O,因此用户线程对文件普通的并发I/O操作无需加锁。但在windows平台下,则需要加锁进行读写。
b. 当文件处于扩展阶段时(fil_space_extend),将fil_node的being_extended设置为true,避免产生并发extend,或其他关闭文件或者rename操作等
c. 当正在删除一个表时,会检查是否有pending的操作(fil_check_pending_operations)
将fil_space_t::stop_new_ops设置为true;
检查是否有Pending的change buffer merge (space->n_pending_ops);有则等待
检查是否有pending的IO(fil_node_t::n_pending) 或者pending的flush操作(fil_node_t::n_pending_flushes);有则等待
d. 当truncate一张表时,和drop table类似,也会调用函数fil_check_pending_operations,检查表上是否有pending的操作,并将space->is_being_truncated设置为true
e. 当rename一张表时(fil_rename_tablespace),将文件的stop_ios标记设置为true,阻止其他线程所有的I/O操作
=====
当进行文件读写操作时,如果是读操作,发现stop_new_ops或者被设置了但is_being_truncated未被设置,会返回报错;但依然允许写操作(why ? 函数fil_io)
当进行文件flush操作时,如果发现stop_new_ops 或者is_being_truncated被设置了,则忽略文件flush操作 (fil_flush_file_spaces)。

文件预读

文件预读是一项在SSD普及前普通磁盘上比较常见的技术,通过预读的方式进行连续IO而非带价高昂的随机IO
InnoDB有两种预读方式:随机预读及线性预读; Facebook另外还实现了一种逻辑预读的方式

a.随机预读

入口函数:buf_read_ahead_random
以64个Page为单位(这也是一个extend的大小),当前读入的page no所在的64个pagno 区域[ (page_no/64)*64, (page_no/64) *64 + 64],如果最近被访问的Page数超过BUF_READ_AHEAD_RANDOM_THRESHOLD(通常值为13),则将其他Page也读进内存。这里采取异步读。
随机预读受参数innodb_random_read_ahead控制

b.线性预读

入口函数:buf_read_ahead_linear
所谓线性预读,就是在读入一个新的page时,和随机预读类似的64个连续page范围内,默认从低到高Page no,如果最近连续被访问的page数超过innodb_read_ahead_threshold,则将该extend之后的其他page也读取进来。

c.逻辑预读

由于表可能存在碎片空间,因此很可能对于诸如全表扫描这样的场景,连续读取的page并不是物理连续的,线性预读不能解决这样的问题,另外一次读取一个extend对于需要全表扫描的负载并不足够。因此facebook引入了逻辑预读。
其大致思路为,扫描聚集索引,搜集叶子节点号,然后根据叶子节点的page no (可以从非叶子节点获取)顺序异步读入一定量的page。
由于Innodb aio一次只支持体检一个page读请求,虽然Kernel层本身会做读请求合并,但那显然效率不够高。他们对此做了修改,使INNODB可以支持一次提交(io_submit)多个aio请求。
入口函数:row_search_for_mysql –> row_read_ahead_logical
具体参阅这篇博文:http://planet.mysql.com/entry/?id=516236
或者webscalesql上的几个commit:
git show 2d61329446a08f85c89a4119317ae85baacf2bbb   // 合并多个AIO请求,对所有的预读逻辑(上述三种)采用这种方式
git show 9f52bfd2222403f841fe5fcbedd1333f78a70a4b     //  逻辑预读的主要代码逻辑
git show 64b68e07430b50f6bff5ed67374b336623db24b6   // 防止事务在多个表上读取操作时预读带来的影响

日志填充写入

由于现代磁盘通常的block size都是大于512字节的,例如一般是4096字节,为了避免 “read-on-write” 问题,在5.7版本里添加了一个参数innodb_log_write_ahead_size,你可以通过配置该参数,在写入redo log时,将写入区域配置到block size对齐的字节数。
在代码里的实现,就是在写入redo log 文件之前,为尾部字节填充0,(参考函数log_write_up_to)
Tips:所谓READ-ON-WRITE问题,就是当修改的字节不足一个block时,需要将整个block读进内存,修改对应的位置,然后再写进去;如果我们以block为单位来写入的话,直接完整覆盖写入即可。

InnoDB IO子系统介绍相关推荐

  1. MySQL · 引擎特性 · InnoDB 事务子系统介绍

    前言 在前面几期关于InnoDB Redo和Undo实现的铺垫后,本节我们从上层的角度来阐述InnoDB的事务子系统是如何实现的,涉及的内容包括:InnoDB的事务相关模块,如何实现MVCC及ACID ...

  2. mysql 5.1版本无innodb trx_MySQL 5.7: Innodb 事务子系统优化-阿里云开发者社区

    MySQL5.7 : Innodb 事务子系统优化 之前写了篇博客介绍了Percona Server对Read View的优化,顺带简单提到了MySQL5.7的事务子系统优化,详细见http://my ...

  3. Linux 性能优化之 IO 子系统

    本文介绍了对 Linux IO 子系统性能进行优化时需要考虑的因素,以及一些 IO 性能检测工具. 本文的大部分内容来自 IBM Redbook - Linux Performance and Tun ...

  4. IO流介绍、java常用的几个IO流类之间的区别,以及各自的用法、使用场景

    1. IO流介绍 IO流(Input Output Stream,输入输出流),表示数据在程序内存和磁盘之间的传输.按照数据流的流向不同分为输入.输出流,输入流表示程序从磁盘读入数据,输出流表示程序往 ...

  5. 5种网络IO模型介绍

    5种网络IO模型介绍 IO 模型分为以下几种: 阻塞IO 非阻塞IO 信号驱动IO IO多路复用 异步IO 前四个为同步IO 1 阻塞IO 一个IO操作需要两步: 等待数据和拷贝数据. blockin ...

  6. Linux优化之IO子系统监控与调优

    Linux优化之IO子系统 作为服务器主机来讲,最大的两个IO类型 : 1.磁盘IO 2.网络IO 这是我们调整最多的两个部分所在 磁盘IO是如何实现的 在内存调优中,一直在讲到为了加速性能,linu ...

  7. oracle dbra,资源供给:IO子系统之二

    案例描述: 某运营商的dbra备份系统,备份构建在vxvm和vxfs文件系统之上,串行更新的速度基本理想.由于无法达到更新目标,通过增加并行来增加IO写速度,结果并行度加大之后,iops快速下跌,io ...

  8. linux的每次IO大小控制,Linux优化之IO子系统监控与调优

    Linux优化之IO子系统 作为服务器主机来讲,最大的两个IO类型 : 1.磁盘IO 2.网络IO 这是我们调整最多的两个部分所在 磁盘IO是如何实现的 在内存调优中,一直在讲到为了加速性能,linu ...

  9. Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统介绍

    文章目录 一.Linux设备模型介绍 (1)设备驱动模型总体介绍 (2)设备驱动模型文件表现 (3)设备驱动模型工作原理 [1]总线 [2]设备 [3]驱动 [4]注册流程 二.平台设备驱动介绍 (1 ...

最新文章

  1. 三星自动驾驶汽车路测获批,进军无人驾驶领域
  2. CSS3之利用选择器和content属性在页面中插入内容
  3. 使用SWig出现调用异常的情况
  4. js页面上的excel导出
  5. 关于JPQL UPDATE 语句的 一点体会
  6. [WC2008]游览计划(斯坦纳树)
  7. mysql yog中文版下载_SQLyog 12
  8. NEO技术文章征集大赛
  9. OpenLayers3基础教程——OL3 介绍interaction
  10. 【Flutter 问题系列第 54 篇】Flutter 引入阿里矢量图标库的详细图文教程
  11. linux 查看网卡以及开启网卡
  12. 全国青少年机器人技术等级考试标准 (三四级/arduino/mixly)
  13. 【JavaScript】支持js代码的博客有…
  14. WordCloud 中英文词云图绘制,看这一篇就够了
  15. Liskov Substitution Principle
  16. 计算机条文式求职简历范文,个人简历自我描述#40;范本)(11页)-原创力文档
  17. 2023计算机毕业设计SSM最新选题之javaJava班级信息管理系统x0w9c
  18. 零知开源分享-L298N模块使用 电机驱动 TT马达
  19. Tomcat使用startup.bat启动闪退的原因
  20. AutoformR8 软件安装说明视频教程

热门文章

  1. wxpython中文手册 官方版_wxPython API参考手册.chm
  2. 照片打印预览正常打印空白_照片打印机怎么挑选 照片打印机的选购技巧分享 - 台式电脑...
  3. python pipline_python中sklearn的pipeline模块实例详解
  4. kill 进程_05516.1普通用户配置kill CDH集群进程权限
  5. idea中Terminal输入命令git log后如何退出
  6. 安卓第一阶段实训项目:基于存储卡音乐播放器(更新中……)
  7. Spring框架学习笔记02:初探Spring——利用组件注解符精简Spring配置文件
  8. 大数据学习笔记21:MR案例——分区全排序
  9. 【BZOJ1758】重建计划,点分治+单调队列
  10. 正式请求:Could you...? May I...? _52