InnoDB I/O

InnoDB 事务日志

InnoDB使用日志减少提交事务时候的开销,日志中记录了事务后就无须在每个事务提交的时候把缓冲池的脏块刷新到磁盘
InnoDB采用日志将随机I/O变为顺序I/O,日志可用来恢复已经提交的事务
日志采用环形方式写入:写到日志的尾部后重新跳转到开头继续写,不会覆盖还没应用到数据文件的日志记录
后台线程控制刷新变更到数据文件,可以批量写入
多个文件作为一组循环日志,通常不修改文件数量而修改文件大小。完全关闭MySQL,将旧的日志文件移到其他地方保存,然后重启
确定理想的日志文件大小,需要权衡正常数据变更的开销和崩溃恢复所需要的时间。

  • 日志太小:InnoDB将必须做更多检查点导致更多日志写。极端情况下必须等待变更应用到数据文件。
  • 日志太大:崩溃恢复时间极大增加。现在得到一定改善

数据大小和访问模式影响恢复时间。
例子:
均匀分布在1TB数据中的缓存池脏页和几百MB频繁变更的数据
恢复时间也依赖于普通修改操作大小。较短的行使得更多修改可以放在同一日志中,恢复时就必须重放更多操作。
在:

  • 缓冲满
  • 事务提交
  • 每一秒钟

三条件满足任意之一时InnoDB刷写缓冲区内容到磁盘日志文件
调大innodb_log_buffer_size增加日志缓冲区大小以帮助在大事务的情况下减少I/O
推荐日志缓冲区大小为1~8MB
大的日志缓冲区可帮助减少缓冲区空间分配的争用,32~128MB的日志缓冲区也可以考虑
通过检查SHOW INNODB STATUS输出中LOG部分监控InnoDB日志和日志缓冲区的I/O性能,通过观察Innodb_os_log_written变量来查看InnoDB对日志文件写出了多少数据。
经验法则:

  • 可以通过观察10~100秒间隔的数字然后记录峰值决定日志缓存的设置大小
  • 日志文件的全部大小应该足够容纳服务器一个小时的活动内容

当InnoDB把日志缓冲刷新到磁盘日志文件时,先会使用一个Mutex锁住缓冲区,刷新到所需要的位置,然后移动剩下的条目到缓冲区的前面。
Mutex释放的时候可能有多个事务准备好刷新日志记录
Group Commit 可以在一个I/O操作内提交多个事务,但是在MySQL 5.0后开启二进制日志时这个功能就不能使用
innodb_flush_log_at_trx_commit变量控制日志缓冲刷新的频繁程度

0

把日志缓冲写到日志文件并每秒钟刷新一次,事务提交时不做任何操作

1

默认。将日志缓冲写到日志文件并且每次事务提交都刷新到持久化存储。最安全的设置,保证不会丢失已提交的事务除非磁盘或者操作系统采用“伪”刷新

2

每次提交时把日志缓冲写到日志文件,并不刷新,InnoDB每秒钟做一次刷新。比0更适合(当MySQL进程挂了的时候2不会丢失任何事务)

大部分操作系统内,把缓存写到日志只是简单的把数据从InnoDB的内存缓冲移到操作系统的内存缓存。
MySQL崩溃/电源断电时,设置0或2通常导致最多一秒的数据丢失,除非刷新被推迟
把日志刷新到持久化存储是阻塞I/O的调用,因此设置1时会明显降低InnoDB每秒提交的事务数量。
磁盘缓存更快但是十分危险,不止可能丢失事务,更可能损坏数据,不推荐
高性能事务处理的最佳配置是设置1并把日志文件放到一个有电池保护的写缓存的RAID卷中
Percona Server将innodb_flush_log_at_trx_commit变量从全局变量扩展为会话级变量

InnoDB打开和刷新日志以及数据文件

innodb_flush_method选项配置InnoDB与文件系统交互
windows和非windows操作系统的选项值互斥
windows默认unbuffered,其他操作系统都是fdatasync

fdatasync

非windows系统默认值
InnoDB使用fsync()刷新数据和日志文件
缺点:操作系统至少会在自己的缓存中缓冲一些数据,理论上是浪费的。实际上有时有帮助,有时没用
文件系统可能会做更智能的I/O调度和批量操作,例如积累写操作后合并执行,重排I/O来提升效率,并发写入多个设备,预读优化(例如连续请求几个顺序块时通知磁盘预读下一个块)
innodb_file_per_table选项导致每个文件独立的fsync(),写多个表不能合并到一个I/O操作,可能导致InnoDB执行更多的fsync()操作

0_DIRECT

InnoDB对数据文件使用0_DIRECT标记或directio()函数
不影响日志文件且不是所有类unix系统都有效
GNU/Linux、FreeBSD、Solaris(5.0后新版本)支持
同时影响读写
大部分系统用fcntl()调用来设置文件描述符的0_DIRECT标记,Solaris采用directio()
只能关闭操作系统和文件系统的预读
通常需要带有写缓存的RAID卡并且设置为Write-Back策略。否则可能导致严重的性能下降。多个写线程可能缓解一定问题,MySQL5.5提供原生异步I/O,但通常无法解决
可能导致服务器预热时间变长,特别是操作系统缓存较大时。
也可能导致小容量的缓存池比缓冲I/O方式操作慢的多。如果需要的数据不在缓冲池时不得不直接从磁盘读取。
innodb_file_per_table不会产生任何额外性能损失。相反如果不用,可能由于顺序I/O遭受性能损失,因为某些文件系统(Linux所有的ext文件系统)每个inode都有一个Mutex。这些文件系统上使用0_DIRECT时推荐打开innodb_file_per_table

ALL_0_DIRECT

在Percona Server和MariaDB可用,让服务器在打开日志文件时采用0_DIRECT

0_DSYNC

日志文件调用open()函数时设置0_SYNC标记,使得所有写同步(数据写到磁盘后写操作才返回)
不影响数据文件
和0_DIRECT标记的区别:没有禁用操作系统层的缓存。因此并没有避免双重缓冲,也没使写操作直接操作到硬盘。0_SYNC标记在缓存中写数据然后发送到磁盘
和fsync()区别:用0_SYNC标记时,操作系统可能把“使用同步I/O”标记下传给硬件层,告诉设备不要使用缓存。fsync()告诉操作系统把修改过的缓冲数据刷写到设备商。如果设备支持,紧接着传递一个指令给设备刷新设备自身的缓存。另外,使用0_SYNC标记时,每个write()或者pwrite()操作都会在函数完成之前 把数据同步到磁盘,完成前函数调用是阻塞的。而fsync()允许写操作累积在缓存使得每个写更快,然后一次性刷新所有的数据

async_unbuffered

windows默认值
InnoDB大部分写使用没有缓存的I/O
例外是当innodb_flush_log_at_trx_commit设置为2的时候对日志文件使用缓冲I/O
windows2000以上使用操作系统的原生异步I/O,更老的版本InnoDB使用自己用多线程模拟的异步I/O

unbuffered

只对windows有效
类似于async_unbuffered,但不使用原生异步I/O

normal

只对windows有效
InnoDB不使用原生异步I/O或者无缓冲I/O

Nosync和littlesync

只为开发使用
无文档,对生产环境不安全
不应该使用

建议

使用类UNIX操作系统并且RAID控制器带有电池保护的写缓存,建议使用0_DIRECT。其他情况默认值或者0_DIRECT都可能是最好的选择

InnoDB表空间

InnoDB把数据保存在表空间内,本质是一个由一个或多个磁盘文件组成的虚拟文件系统
保存表、索引、插入缓冲、双写缓冲以及其他内部数据结构
innodb_data_file_path配置表空间
例:innodb_data_file_path = ibdata1:1G;ibdata2:1G;ibdata3:1G:autoextend:max:2G
上述配置在三个文件中一共创建了3GB的表空间,分散了驱动器的负载,然而通常并不能获得太多收益。InnoDB先填满第一个文件,第一个满了以后填满第二个。推荐使用RAID控制器
最后一个文件设置了自动扩展。默认的行为是创建单个10MB的自动扩展文件。设置了限制,自动扩展文件最多到2GB
建议关闭自动扩展功能,或者至少设置一个合理的空间范围
唯一的回收空间方式是导出数据,关闭MySQL,删除所有文件,修改配置,重启,让InnoDB创建新的数据文件,然后导入数据
不能简单的删除文件或者改变大小。如果表空间损坏了,InnoDB会拒绝启动
设置innodb_file_per_table选项为每张表使用一个文件,在MySQL4.1和之后的版本都支持
在数据字典存储为“表名.ibd”的数据
优点:删除一张表时回收空间简单,容易分散表到不同的磁盘上
缺点:

  • 数据放到多个文件导致更多的空间浪费,对于非常小的表问题更大(InnoDB的页大小是16KB)
  • 更差的DROP TABLE性能,可能导致显而易见的服务器端阻塞
    • 删除表需要从文件系统层删除文件,这可能在某些文件系统(重点批评ext3)上很慢。可以欺骗文件系统来缩短过程:把.ibd文件链接到一个0字节的文件然后手动删除文件
    • 每张表都在InnoDB中使用自己的表空间,移除表空间需要InnoDB锁定和扫描缓冲池,查找属于这个表空间的页面,对于大缓冲池的服务器十分缓慢。可以通过Percona Server的innodb_lazy_drop_table选项让服务器慢慢清理掉属于被删除表的页面

打开innodb_file_per_table选项时依然需要为回滚日志和其他系统数据创建共享表空间,最好关闭自动增长
可以通过查看文件的大小来确认表的大小,远远快于SHOW TABLE STATUS
建议:使用innodb_file_per_table并设置共享表空间的大小
不推荐:使用裸设备
例:一个没有格式化的分区。
可能可以提升几个百分点的性能,但是不能直接用文件管理数据

行的旧版本和表空间

写压力大的环境下表空间迅速增长。如果事务保持打开很久,并且使用默认的REPEATABLE READ事务隔离级别,InnoDB将不能删除旧的行版本
InnoDB把旧版本存在共享表空间,如果有很多数据在更新,共享表空间会持续增长
清理线程只有一个线程处理,直到最近的MySQL版本才改进
可以使用SHOW INNODB STATUS来帮助定位问题。历史链表的长度会显示了回滚日志的大小,以页为单位
TRANSACTIONS部分的第一行和第二行展示当前事务号和清理线程完成到了哪个点,差距很大时可能有大量没有清理的事务
例子:

------------
TRANSATIONS
------------
Trx id conter 0 80157601
Purge done for trx's n:o <0 80154573 undo n:o <0 0

事务标识是一个64bit的数字,由两个32bit的数字组成,两个事务标识做差计算未清理的事务数量
很多事务可能不改变数据,一个事务可能修改很多行,不能精确反应增长情况

如果有很大的回滚日志并且因此表空间增长的很快,可以强制MySQL减速使MySQL清理线程跟上。否则InnoDB将保持数据写入,填充磁盘直到磁盘空间爆满或大于表空间定义上限
设置innodb_max_purge_lag变量为大于0的值。这个值表示InnoDB开始延迟后面的语句更新数据之前,可以等待被清除的最大事务数量
innodb_max_purge_lag会降低性能,但是伤害会小于清理线程跟不上造成的性能下降

双写缓冲

避免页没写完整所导致的数据损坏,保证数据完整性
双写缓冲是表空间一个特殊的保留区域,在一个连续的块中保存100个页,本质上是一个最近写回的页面的备份拷贝
InnoDB从缓冲池刷新页面到磁盘时,首先把他们写/刷新到双写缓冲,然后再把他们写到所属的数据区域中。保证每个页面的写入都是原子且持久化
InnoDB写页面到双写缓冲是顺序的,并且只调用一次fsync()刷新到磁盘,所以写两次对性能的冲击很小
双写缓冲策略允许日志文件更高效,保证数据页不会损坏,InnoDB日志记录的时候就没必要包含整个页
InnoDB通过校验值判断页面是否损坏,从双写缓冲或者原始页面读取正确的内容
备库或者使用zfs等做了相同处理的文件系统时可以不用双写缓冲,可以配置innodb_doublewrite为0来关闭双写缓冲
Percona Server中,可以配置双写缓冲存到独立的文件中

其他配置项

sync_binlog控制MySQL刷新二进制日志到磁盘方式
默认值是0,即MySQL并不刷新,由操作系统决定什么时候刷新缓存到持久化设备
大于0的值指定两次刷新到磁盘的动作之间间隔多少次二进制写操作(若autocommit被设置,则每个独立语句是一次写,否则一个事务记一次写),0和1以外的值罕见
若未设置为1,则崩溃以后可能导致二进制文件没有同步事务数据,会导致复制中断并且无法及时恢复
设置为1时每次写二进制日志都会增加的二进制日志大小,需要更新元信息,性能损失极大
放到带电池保护的写缓存的RAID卷能极大提高性能
通过expire_logs_days选项清理旧的二进制文件
rm造成损坏后手动同步二进制日志

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据