文章中所有图片均来自网络
一、double write
第一个二次是mysql一个崩溃恢复很重要的特性-重复写入。
doublewrite缓冲区是位于系统表空间中的存储区域,在该区域中,InnoDB会在将页面写入数据文件中的适当位置之前,从InnoDB缓冲池中刷新这些页面。仅在刷新页面并将其写入doublewrite缓冲区后,InnoDB才会将页面写入其适当位置。如果在页面写入过程中发生操作系统,存储子系统或mysqld进程崩溃,InnoDB稍后可以在崩溃恢复期间从doublewrite缓冲区中找到该页面的良好副本。
部分页面写
InnoDB的页面大小通常是16KB,其数据校验也是针对这16KB来计算的,将数据写入到磁盘并以页面为单位进行操作的。而计算机硬件和操作系统,在极端情况下(有时断电) )通常并不能保证这一步的原子性,16K的数据,写入4K时,发生了系统断电/ os崩溃,只有一部分写是成功的,这种情况下就是局部页面写问题。
很多DBA会想到系统恢复后,MySQL可以根据redolog进行恢复,而mysql在恢复的过程中是检查页面的校验和,checksum就是pgae的最后事务号,发生部分页面写问题,页面已经损坏,找不到该页面中的事务号,就无法恢复。
double write原理
Double write是InnoDB在表空间上的128个页(2个区)是2MB;
为了解决部分页写问题,当mysql将脏数据刷新到数据文件的时候,先使用内存复制将脏数据复制到内存中的double write buffer,之后通过double write buffer再分2次,每次写入1MB到共享表空间,然后立即调用fsync函数,同步到磁盘上,避免缓冲带来的问题,在这个过程中,doublewrite是顺序写,不会大小写大,在完成doublewrite写入后,在将double write buffer写入各个表空间文件,这时是离散写入。
如果发生了极端情况(断电),InnoDB再次启动后,发现了一个页面数据已经损坏,那么此时就可以从doublewrite buffer中进行数据恢复了。
double对性能的影响
在共享表空间上的双重写缓冲区实际上也是一个文件,写DWB会导致系统有更多的fsync操作,而硬盘的fsync性能,所以它会降低mysql的整体性能。但是并不会降低到原来的50%。这主要是因为:
1)double write是一个连接的存储空间,所以硬盘在写数据的时候是顺序写,而不是随机写,这样性能更高。
2)将数据从双写缓冲区写入到真正的segment中的时候,系统会自动合并连接空间刷新的方式,每次可以刷新多个页面;
如果页面大小是16k,那么就有128个页面(1M)需要写,但是128个页面写入到共享表空间是1次IO完成,则doublewrite写入是1 + 128次。其中128次是写数据文件表空间。
doublewrite写入是顺序的,性能开销转化为量,通常5%-25%的性能影响。
double在恢复的时候是如何工作的?
如果部分页面写入doublewrite缓冲区本身,则原始页面仍将保留在磁盘上的实际位置。
如果是写双写缓冲区本身失败,那么这些数据不会被写入磁盘,InnoDB此时会从磁盘加载原始数据,然后通过InnoDB的事务日志来计算出正确的数据,重新写入到双写缓冲区。
当InnoDB恢复时,它将使用原始页面而不是doublewrite缓冲区中的损坏副本。但是,如果双写缓冲区成功并且对页面实际位置的写入失败,则InnoDB将在恢复期间使用双写缓冲区中的副本。
如果doublewrite buffer写成功的话,但是写磁盘失败,InnoDB就不用通过事务日志来计算了,或者直接用buffer的数据再写一遍。
InnoDB知道页面何时损坏,因为每个页面的末尾都有一个校验和。校验和是最后要写入的内容,因此,如果页面的内容与校验和不匹配,则页面已损坏。因此,恢复后,InnoDB只会读取doublewrite缓冲区中的每个页面并验证校验和。如果页面的校验和不正确,它将从其原始位置读取页面。
在恢复的时候,InnoDB直接比较页面的校验和,如果不对的话,就从硬盘加载原始数据,再由事务日志开始推演正确的数据。所以InnoDB的恢复通常需要花费时间。
重复写相关参数
InnoDB_doublewrite = 1表示启动双写,显示状态为’InnoDB_dblwr%‘可以查询双写的使用情况;
#是否开启double write
mysql>显示类似’%double%write%'的变量;
#Double write的使用情况
mysql>显示状态,例如“%InnoDB_dblwr%”;
InnoDB_dblwr_pages_write#从bp刷新到DBWB的个数
InnoDB_dblwr_writes#写文件的次数
每次写操作合并page的个数= InnoDB_dblwr_pages_write / InnoDB_dblwr_writes
图片源自wang’l
是否一定需要重复写
在某些情况下,确实没有必要使用doublewrite缓冲区-例如,您可能想在从属服务器上禁用它。另外,某些文件系统(例如ZFS)本身也会执行相同的操作,因此InnoDB这样做是多余的。您可以通过将InnoDB_doublewrite设置为0来禁用双写缓冲区。
1,Fursion-io原子写,如果每次写16k就是16k,每次写都是16k不会出现部分写partial write写4k的情况。
2,特定的文件系统(b-tree文件系统),支持原子写。
为了解决部分页写问题,当mysql将脏数据刷新到数据文件的时候,先使用内存复制将脏数据复制到内存中的doublewritebuffer,之后通过doublewritebuffer再分2次,每次写入1MB到共享…
二、两阶段提交
第二个两次就是两阶段提交
InnoDB引擎更新一条指定数据的过程如下:
可以看到,InnoDB在写redo log时,并不是一次性写完的,而有两个阶段,Prepare与Commit阶段,这就是"两阶段提交"的含义。
为什么要写redo log,不写redo log的话,根本就不会出现“两阶段提交”的麻烦事啊?
先说结论:在于崩溃恢复。
MySQL为了提升性能,引入了BufferPool缓冲池。查询数据时,先从BufferPool中查询,查询不到则从磁盘加载在BufferPool。
每次对数据的更新,也不总是实时刷新到磁盘,而是先同步到BufferPool中,涉及到的数据页就会变成脏页。同时会启动后台线程,异步地将脏页刷新到磁盘中,来完成BufferPool与磁盘的数据同步。如果在某个时间,MySQL突然崩溃,则内存中的BufferPool就会丢失,剩余未同步的数据就会直接消失。
虽然在更新BufferPool后,也写入了binlog中,但binlog并不具备crash-safe的能力。因为崩溃可能发生在写binlog后,刷脏前。在主从同步的情况下,从节点会拿到多出来的一条binlog。所以server层的binlog是不支持崩溃恢复的,只是支持误删数据恢复。InnoDB考虑到这一点,自己实现了redo log。
为什么写两次redo
为什么要写两次redo log,写一次不行吗?
redo log与binlog都写一次的话,也就是存在以下两种情况:
先写binlog,再写redo log:当前事务提交后,写入binlog成功,之后主节点崩溃。在主节点重启后,由于没有写入redo log,因此不会恢复该条数据。而从节点依据binlog在本地回放后,会相对于主节点多出来一条数据,从而产生主从不一致。
先写redo log,再写binlog:当前事务提交后,写入redo log成功,之后主节点崩溃。在主节点重启后,主节点利用redo log进行恢复,就会相对于从节点多出来一条数据,造成主从数据不一致。
因此,只写一次redo log与binlog,无法保证主节点崩溃恢复与从节点本地回放数据的一致性。
如何实现奔溃恢复
在两阶段提交的情况下,是怎么实现崩溃恢复的呢?
首先比较重要的一点是,在写入redo log时,会顺便记录XID,即当前事务id。在写入binlog时,也会写入XID。因此存在以下三种情况:
如果在写入redo log之前崩溃,那么此时redo log与binlog中都没有,是一致的情况,崩溃也无所谓。
如果在写入redo log prepare阶段后立马崩溃,之后会在崩恢复时,由于redo log没有被标记为commit。于是拿着redo log中的XID去bin log中查找,此时肯定是找不到的,那么执行回滚操作。
如果在写入bin log后立马崩溃,在恢复时,由redo log中的XID可以找到对应的bin log,这个时候直接提交即可。
总的来说,在崩溃恢复后,只要redo log不是处于commit阶段,那么就拿着redo log中的XID去binlog中寻找,找得到就提交,否则就回滚。在这样的机制下,两阶段提交能在崩溃恢复时,能够对提交中断的事务进行补偿,来确保redo log与binlog的数据一致性。