innodb事务日志包括redo log和undo log。redo log是重作日志,提供前滚操做,undo log是回滚日志,提供回滚操做。数据库
undo log不是redo log的逆向过程,其实它们都算是用来恢复的日志:缓存
1.redo log一般是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改为怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。安全
2.undo用来回滚行记录到某个版本。undo log通常是逻辑日志,根据每行记录进行记录。markdown
redo log不是二进制日志。虽然二进制日志中也记录了innodb表的不少操做,也能实现重作的功能,可是它们之间有很大区别.并发
一、二进制日志是在存储引擎的上层产生的,无论是什么存储引擎,对数据库进行了修改都会产生二进制日志。而redo log是innodb层产生的,只记录该存储引擎中表的修改。而且二进制日志先于redo log被记录。具体的见后文group commit小结。app
二、二进制日志记录操做的方法是逻辑性的语句。即使它是基于行格式的记录方式,其本质也仍是逻辑的SQL设置,如该行记录的每列的值是多少。而redo log是在物理格式上的日志,它记录的是数据库中每一个页的修改。异步
三、二进制日志只在每次事务提交的时候一次性写入缓存中的日志"文件"(对于非事务表的操做,则是每次执行语句成功后就直接写入)。而redo log在数据准备修改前写入缓存中的redo log中,而后才对缓存中的数据执行修改操做;并且保证在发出事务提交指令时,先向缓存中的redo log写入日志,写入完成后才执行提交动做。async
四、由于二进制日志只在提交的时候一次性写入,因此二进制日志中的记录方式和提交顺序有关,且一次提交对应一次记录。而redo log中是记录的物理页的修改,redo log文件中同一个事务可能屡次记录,最后一个提交的事务记录会覆盖全部未提交的事务记录。例如事务T1,可能在redo log中记录了 T1-1,T1-2,T1-3,T1* 共4个操做,其中 T1* 表示最后提交时的日志记录,因此对应的数据页最终状态是 T1* 对应的操做结果。并且redo log是并发写入的,不一样事务之间的不一样版本的记录会穿插写入到redo log文件中,例如可能redo log的记录方式以下:T1-1,T1-2,T2-1,T2-2,T2*,T1-3,T1* 。函数
五、事务日志记录的是物理页的状况,它具备幂等性,所以记录日志的方式极其简练。幂等性的意思是屡次操做先后状态是同样的,例如新插入一行后又删除该行,先后状态没有变化。而二进制日志记录的是全部影响数据的操做,记录的内容较多。例如插入一行记录一次,删除该行又记录一次。oop
redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重作日志文件(redo log file),该部分日志是持久的。
在概念上,innodb经过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的全部事务日志写入到磁盘上的redo log file和undo log file中进行持久化。
为了确保每第二天志都能写入到事务日志文件中,在每次将log buffer中的日志写入日志文件的过程当中都会调用一次操做系统的fsync操做(即fsync()系统调用)。由于MariaDB/MySQL是工做在用户空间的,MariaDB/MySQL的log buffer处于用户空间的内存中。要写入到磁盘上的log file中(redo:ib_logfileN文件,undo:share tablespace或.ibd文件),中间还要通过操做系统内核空间的os buffer,调用fsync()的做用就是将OS buffer中的日志刷到磁盘上的log file中。
也就是说,从redo log buffer写日志到磁盘的redo log file中,过程以下:
在此处须要注意一点,通常所说的log file并非磁盘上的物理日志文件,而是操做系统缓存中的log file,官方手册上的意思也是如此(例如:With a value of 2, the contents of the InnoDB log buffer are written to the log file after each transaction commit and the log file is flushed to disk approximately once per second)。但说实话,这不太好理解,既然都称为file了,应该已经属于物理文件了。因此在本文后续内容中都以os buffer或者file system buffer来表示官方手册中所说的Log file,而后log file则表示磁盘上的物理日志文件,即log file on disk。
另外,之因此要通过一层os buffer,是由于open日志文件的时候,open没有使用O_DIRECT标志位,该标志位意味着绕过操做系统层的os buffer,IO直写到底层存储设备。不使用该标志位意味着将日志进行缓冲,缓冲到了必定容量,或者显式fsync()才会将缓冲中的刷到存储设备。使用该标志位意味着每次都要发起系统调用。好比写abcde,不使用o_direct将只发起一次系统调用,使用o_object将发起5次系统调用。
MySQL支持用户自定义在commit时如何将log buffer中的日志刷log file中。这种控制经过变量 innodb_flush_log_at_trx_commit 的值来决定。该变量有3种值:0、一、2,默认为1。但注意,这个变量只是控制commit动做是否刷新log buffer到磁盘。
当设置为1的时候,事务每次提交都会将log buffer中的日志写入os buffer并调用fsync()刷到log file on disk中。这种方式即便系统崩溃也不会丢失任何数据,可是由于每次提交都写入磁盘,IO的性能较差。
当设置为0的时候,事务提交时不会将log buffer中日志写入到os buffer,而是每秒写入os buffer并调用fsync()写入到log file on disk中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。
当设置为2的时候,每次提交都仅写入到os buffer,而后是每秒调用fsync()将os buffer中的日志写入到log file on disk。
注意,有一个变量 innodb_flush_log_at_timeout 的值为1秒,该变量表示的是刷日志的频率,不少人误觉得是控制 innodb_flush_log_at_trx_commit 值为0和2时的1秒频率,实际上并不是如此。测试时将频率设置为5和设置为1,当 innodb_flush_log_at_trx_commit 设置为0和2的时候性能基本都是不变的。关于这个频率是控制什么的,在后面的"刷日志到磁盘的规则"中会说。
在主从复制结构中,要保证事务的持久性和一致性,须要对日志相关变量设置为以下:
若是启用了二进制日志,则设置sync_binlog=1,即每提交一次事务同步写到磁盘中。
老是设置innodb_flush_log_at_trx_commit=1,即每提交一次事务都写到磁盘中。
上述两项变量的设置保证了:每次提交事务都写入二进制日志和事务日志,并在提交时将它们刷新到磁盘中。
选择刷日志的时间会严重影响数据修改时的性能,特别是刷到磁盘的过程。下例就测试了 innodb_flush_log_at_trx_commit 分别为0、一、2时的差距。
当前环境下, innodb_flush_log_at_trx_commit 的值为1,即每次提交都刷日志到磁盘。测试此时插入10W条记录的时间。
结果是15.48秒。
再测试值为2的时候,即每次提交都刷新到os buffer,但每秒才刷入磁盘中。
结果插入时间大减,只需3.41秒。
最后测试值为0的时候,即每秒才刷到os buffer和磁盘。
结果只有2.10秒。
最后能够发现,其实值为2和0的时候,它们的差距并不太大,但2却比0要安全的多。它们都是每秒从os buffer刷到磁盘,它们之间的时间差体如今log buffer刷到os buffer上。由于将log buffer中的日志刷新到os buffer只是内存数据的转移,并无太大的开销,因此每次提交和每秒刷入差距并不大。能够测试插入更多的数据来比较,如下是插入100W行数据的状况。从结果可见,值为2和0的时候差距并不大,但值为1的性能却差太多。
尽管设置为0和2能够大幅度提高插入性能,可是在故障的时候可能会丢失1秒钟数据,这1秒钟极可能有大量的数据,从上面的测试结果看,100W条记录也只消耗了20多秒,1秒钟大约有4W-5W条数据,尽管上述插入的数据简单,但却说明了数据丢失的大量性。更好的插入数据的作法是将值设置为1,而后修改存储过程,将每次循环都提交修改成只提交一次,这样既能保证数据的一致性,也能提高性能,修改以下:
测试值为1时的状况。
innodb存储引擎中,redo log以块为单位进行存储的,每一个块占512字节,这称为redo log block。因此无论是log buffer中仍是os buffer中以及redo log file on disk中,都是这样以512字节的块存储的。
每一个redo log block由3部分组成:日志块头、日志块尾和日志主体。其中日志块头占用12字节,日志块尾占用8字节,因此每一个redo log block的日志主体部分只有512-12-8=492字节。
由于redo log记录的是数据页的变化,当一个数据页产生的变化须要使用超过492字节()的redo log来记录,那么就会使用多个redo log block来记录该数据页的变化。
日志块头包含4部分:
log_block_hdr_no:(4字节)该日志块在redo log buffer中的位置ID。
log_block_hdr_data_len:(2字节)该log block中已记录的log大小。写满该log block时为0x200,表示512字节。
log_block_first_rec_group:(2字节)该log block中第一个log的开始偏移位置。
lock_block_checkpoint_no:(4字节)写入检查点信息的位置。
关于log block块头的第三部分 log_block_first_rec_group ,由于有时候一个数据页产生的日志量超出了一个日志块,这是须要用多个日志块来记录该页的相关日志。例如,某一数据页产生了552字节的日志量,那么须要占用两个日志块,第一个日志块占用492字节,第二个日志块须要占用60个字节,那么对于第二个日志块来讲,它的第一个log的开始位置就是73字节(60+12)。若是该部分的值和 log_block_hdr_data_len 相等,则说明该log block中没有新开始的日志块,即表示该日志块用来延续前一个日志块。
日志尾只有一个部分:log_block_trl_no ,该值和块头的 log_block_hdr_no 相等。
上面所说的是一个日志块的内容,在redo log buffer或者redo log file on disk中,由不少log block组成。以下图:
log group表示的是redo log group,一个组内由多个大小彻底相同的redo log file组成。组内redo log file的数量由变量 innodb_log_files_group 决定,默认值为2,即两个redo log file。这个组是一个逻辑的概念,并无真正的文件来表示这是一个组,可是能够经过变量 innodb_log_group_home_dir 来定义组的目录,redo log file都放在这个目录下,默认是在datadir下。
能够看到在默认的数据目录下,有两个ib_logfile开头的文件,它们就是log group中的redo log file,并且它们的大小彻底一致且等于变量 innodb_log_file_size 定义的值。第一个文件ibdata1是在没有开启 innodb_file_per_table 时的共享表空间文件,对应于开启 innodb_file_per_table 时的.ibd文件。
在innodb将log buffer中的redo log block刷到这些log file中时,会以追加写入的方式循环轮训写入。即先在第一个log file(即ib_logfile0)的尾部追加写,直到满了以后向第二个log file(即ib_logfile1)写。当第二个log file满了会清空一部分第一个log file继续写入。
因为是将log buffer中的日志刷到log file,因此在log file中记录日志的方式也是log block的方式。
在每一个组的第一个redo log file中,前2KB记录4个特定的部分,从2KB以后才开始记录log block。除了第一个redo log file中会记录,log group中的其余log file不会记录这2KB,可是却会腾出这2KB的空间。以下:
redo log file的大小对innodb的性能影响很是大,设置的太大,恢复的时候就会时间较长,设置的过小,就会致使在写redo log的时候循环切换redo log file。
由于innodb存储引擎存储数据的单元是页(和SQL Server中同样),因此redo log也是基于页的格式来记录的。默认状况下,innodb的页大小是16KB(由 innodb_page_size 变量控制),一个页内能够存放很是多的log block(每一个512字节),而log block中记录的又是数据页的变化。
其中log block中492字节的部分是log body,该log body的格式分为4部分:
redo_log_type:占用1个字节,表示redo log的日志类型。
space:表示表空间的ID,采用压缩的方式后,占用的空间可能小于4字节。
page_no:表示页的偏移量,一样是压缩过的。
redo_log_body表示每一个重作日志的数据部分,恢复时会调用相应的函数进行解析。例如insert语句和delete语句写入redo log的内容是不同的。
以下图,分别是insert和delete大体的记录方式。
log buffer中未刷到磁盘的日志称为脏日志(dirty log)。
在上面的说过,默认状况下事务每次提交的时候都会刷事务日志到磁盘中,这是由于变量 innodb_flush_log_at_trx_commit 的值为1。可是innodb不只仅只会在有commit动做后才会刷日志到磁盘,这只是innodb存储引擎刷日志的规则之一。
刷日志到磁盘有如下几种规则:
1.发出commit动做时。已经说明过,commit发出后是否刷日志由变量 innodb_flush_log_at_trx_commit 控制。
2.每秒刷一次。这个刷日志的频率由变量 innodb_flush_log_at_timeout 值决定,默认是1秒。要注意,这个刷日志频率和commit动做无关。
3.当log buffer中已经使用的内存超过一半时。
4.当有checkpoint时,checkpoint在必定程度上表明了刷到磁盘时日志所处的LSN位置。
内存中(buffer pool)未刷到磁盘的数据称为脏数据(dirty data)。因为数据和日志都以页的形式存在,因此脏页表示脏数据和脏日志。
上一节介绍了日志是什么时候刷到磁盘的,不只仅是日志须要刷盘,脏数据页也同样须要刷盘。
在innodb中,数据刷盘的规则只有一个:checkpoint。可是触发checkpoint的状况却有几种。无论怎样,checkpoint触发后,会将buffer中脏数据页和脏日志页都刷到磁盘。
innodb存储引擎中checkpoint分为两种:
sharp checkpoint:在重用redo log文件(例如切换日志文件)的时候,将全部已记录到redo log中对应的脏数据刷到磁盘。
fuzzy checkpoint:一次只刷一小部分的日志到磁盘,而非将全部脏日志刷盘。有如下几种状况会触发该检查点:
master thread checkpoint:由master线程控制,每秒或每10秒刷入必定比例的脏页到磁盘。
flush_lru_list checkpoint:从MySQL5.6开始可经过 innodb_page_cleaners 变量指定专门负责脏页刷盘的page cleaner线程的个数,该线程的目的是为了保证lru列表有可用的空闲页。
async/sync flush checkpoint:同步刷盘仍是异步刷盘。例如还有很是多的脏页没刷到磁盘(很是可能是多少,有比例控制),这时候会选择同步刷到磁盘,但这不多出现;若是脏页不是不少,能够选择异步刷到磁盘,若是脏页不多,能够暂时不刷脏页到磁盘
dirty page too much checkpoint:脏页太多时强制触发检查点,目的是为了保证缓存有足够的空闲空间。too much的比例由变量 innodb_max_dirty_pages_pct 控制,MySQL 5.6默认的值为75,即当脏页占缓冲池的百分之75后,就强制刷一部分脏页到磁盘。
因为刷脏页须要必定的时间来完成,因此记录检查点的位置是在每次刷盘结束以后才在redo log中标记的。
MySQL中止时是否将脏数据和脏日志刷入磁盘,由变量innodb_fast_shutdown=102控制,默认值为1,即中止时只作一部分purge,忽略大多数flush操做(但至少会刷日志),在下次启动的时候再flush剩余的内容,实现fast shutdown。
LSN称为日志的逻辑序列号(log sequence number),在innodb存储引擎中,lsn占用8个字节。LSN的值会随着日志的写入而逐渐增大。
根据LSN,能够获取到几个有用的信息:
1.数据页的版本信息。
2.写入的日志总量,经过LSN开始号码和结束号码能够计算出写入的日志量。
3.可知道检查点的位置。
实际上还能够得到不少隐式的信息。
LSN不只存在于redo log中,还存在于数据页中,在每一个数据页的头部,有一个
fil_page_lsn
记录了当前页最终的LSN值是多少。经过数据页中的LSN值和redo log中的LSN值比较,若是页中的LSN值小于redo log中LSN值,则表示数据丢失了一部分,这时候能够经过redo log的记录来恢复到redo log中记录的LSN值时的状态。
redo log的lsn信息能够经过 show engine innodb status 来查看。MySQL 5.5版本的show结果中只有3条记录,没有pages flushed up to。
其中:
log sequence number就是当前的redo log(in buffer)中的lsn;
log flushed up to是刷到redo log file on disk中的lsn;
pages flushed up to是已经刷到磁盘数据页上的LSN;
last checkpoint at是上一次检查点所在位置的LSN。
innodb从执行修改语句开始:
(1).首先修改内存中的数据页,并在数据页中记录LSN,暂且称之为data_in_buffer_lsn;
(2).而且在修改数据页的同时(几乎是同时)向redo log in buffer中写入redo log,并记录下对应的LSN,暂且称之为redo_log_in_buffer_lsn;
(3).写完buffer中的日志后,当触发了日志刷盘的几种规则时,会向redo log file on disk刷入重作日志,并在该文件中记下对应的LSN,暂且称之为redo_log_on_disk_lsn;
(4).数据页不可能永远只停留在内存中,在某些状况下,会触发checkpoint来将内存中的脏页(数据脏页和日志脏页)刷到磁盘,因此会在本次checkpoint脏页刷盘结束时,在redo log中记录checkpoint的LSN位置,暂且称之为checkpoint_lsn。
(5).要记录checkpoint所在位置很快,只需简单的设置一个标志便可,可是刷数据页并不必定很快,例如这一次checkpoint要刷入的数据页很是多。也就是说要刷入全部的数据页须要必定的时间来完成,中途刷入的每一个数据页都会记下当前页所在的LSN,暂且称之为data_page_on_disk_lsn。
详细说明以下图:
上图中,从上到下的横线分别表明:时间轴、buffer中数据页中记录的LSN(data_in_buffer_lsn)、磁盘中数据页中记录的LSN(data_page_on_disk_lsn)、buffer中重作日志记录的LSN(redo_log_in_buffer_lsn)、磁盘中重作日志文件中记录的LSN(redo_log_on_disk_lsn)以及检查点记录的LSN(checkpoint_lsn)。
假设在最初时(12:0:00)全部的日志页和数据页都完成了刷盘,也记录好了检查点的LSN,这时它们的LSN都是彻底一致的。
假设此时开启了一个事务,并马上执行了一个update操做,执行完成后,buffer中的数据页和redo log都记录好了更新后的LSN值,假设为110。这时候若是执行 show engine innodb status 查看各LSN的值,即图中①处的位置状态,结果会是:
logsequence number(110) >logflushed up to(100) = pages flushed up to = last checkpoint at
以后又执行了一个delete语句,LSN增加到150。等到12:00:01时,触发redo log刷盘的规则(其中有一个规则是 innodb_flush_log_at_timeout 控制的默认日志刷盘频率为1秒),这时redo log file on disk中的LSN会更新到和redo log in buffer的LSN同样,因此都等于150,这时 show engine innodb status ,即图中②的位置,结果将会是:
logsequence number(150) =logflushed up to > pages flushed up to(100) = last checkpoint at
再以后,执行了一个update语句,缓存中的LSN将增加到300,即图中③的位置。
假设随后检查点出现,即图中④的位置,正如前面所说,检查点会触发数据页和日志页刷盘,但须要必定的时间来完成,因此在数据页刷盘还未完成时,检查点的LSN仍是上一次检查点的LSN,但此时磁盘上数据页和日志页的LSN已经增加了,即:
logsequence number >logflushed up to 和 pages flushed up to > last checkpoint at
可是log flushed up to和pages flushed up to的大小没法肯定,由于日志刷盘可能快于数据刷盘,也可能等于,还多是慢于。可是checkpoint机制有保护数据刷盘速度是慢于日志刷盘的:当数据刷盘速度超过日志刷盘时,将会暂时中止数据刷盘,等待日志刷盘进度超过数据刷盘。
等到数据页和日志页刷盘完毕,即到了位置⑤的时候,全部的LSN都等于300。
随着时间的推移到了12:00:02,即图中位置⑥,又触发了日志刷盘的规则,但此时buffer中的日志LSN和磁盘中的日志LSN是一致的,因此不执行日志刷盘,即此时 show engine innodb status 时各类lsn都相等。
随后执行了一个insert语句,假设buffer中的LSN增加到了800,即图中位置⑦。此时各类LSN的大小和位置①时同样。
随后执行了提交动做,即位置⑧。默认状况下,提交动做会触发日志刷盘,但不会触发数据刷盘,因此 show engine innodb status 的结果是:
logsequence number =logflushed up to > pages flushed up to = last checkpoint at
最后随着时间的推移,检查点再次出现,即图中位置⑨。可是此次检查点不会触发日志刷盘,由于日志的LSN在检查点出现以前已经同步了。假设此次数据刷盘速度极快,快到一瞬间内完成而没法捕捉到状态的变化,这时 show engine innodb status 的结果将是各类LSN相等。
在启动innodb的时候,无论上次是正常关闭仍是异常关闭,老是会进行恢复操做。
由于redo log记录的是数据页的物理变化,所以恢复的时候速度比逻辑日志(如二进制日志)要快不少。并且,innodb自身也作了必定程度的优化,让恢复速度变得更快。
重启innodb时,checkpoint表示已经完整刷到磁盘上data page上的LSN,所以恢复时仅须要恢复从checkpoint开始的日志部分。例如,当数据库在上一次checkpoint的LSN为10000时宕机,且事务是已经提交过的状态。启动数据库时会检查磁盘中数据页的LSN,若是数据页的LSN小于日志中的LSN,则会从检查点开始恢复。
还有一种状况,在宕机前正处于checkpoint的刷盘过程,且数据页的刷盘进度超过了日志页的刷盘进度。这时候一宕机,数据页中记录的LSN就会大于日志页中的LSN,在重启的恢复过程当中会检查到这一状况,这时超出日志进度的部分将不会重作,由于这自己就表示已经作过的事情,无需再重作。
另外,事务日志具备幂等性,因此屡次操做获得同一结果的行为在日志中只记录一次。而二进制日志不具备幂等性,屡次操做会所有记录下来,在恢复的时候会屡次执行二进制日志中的记录,速度就慢得多。例如,某记录中id初始值为2,经过update将值设置为了3,后来又设置成了2,在事务日志中记录的将是无变化的页,根本无需恢复;而二进制会记录下两次update操做,恢复时也将执行这两次update操做,速度比事务日志恢复更慢。
innodb_flush_log_at_trx_commit=01|2# 指定什么时候将事务日志刷到磁盘,默认为1。
0表示每秒将"log buffer"同步到"os buffer"且从"os buffer"刷到磁盘日志文件中。
1表示每事务提交都将"log buffer"同步到"os buffer"且从"os buffer"刷到磁盘日志文件中。
2表示每事务提交都将"log buffer"同步到"os buffer"但每秒才从"os buffer"刷到磁盘日志文件中。
innodb_log_buffer_size:# log buffer的大小,默认8M
innodb_log_file_size:#事务日志的大小,默认5M
innodb_log_files_group =2:# 事务日志组中的事务日志文件个数,默认2个
innodb_log_group_home_dir =./:# 事务日志组路径,当前目录表示数据目录
innodb_mirrored_log_groups =1:# 指定事务日志组的镜像组个数,但镜像功能好像是强制关闭的,因此只有一个log group。在MySQL5.7中该变量已经移除。
undo log有两个做用:提供回滚和多个行版本控制(MVCC)。
在数据修改的时候,不只记录了redo,还记录了相对应的undo,若是由于某些缘由致使事务失败或回滚了,能够借助该undo进行回滚。
undo log和redo log记录物理日志不同,它是逻辑日志。能够认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。
当执行rollback时,就能够从undo log中的逻辑记录读取到相应的内容并进行回滚。有时候应用到行版本控制的时候,也是经过undo log来实现的:当读取的某一行被其余事务锁定时,它能够从undo log中分析出该行记录之前的数据是什么,从而提供该行版本信息,让用户实现非锁定一致性读取。
undo log是采用段(segment)的方式来记录的,每一个undo操做在记录的时候占用一个undo log segment。
另外,undo log也会产生redo log,由于undo log也要实现持久性保护。
innodb存储引擎对undo的管理采用段的方式。rollback segment称为回滚段,每一个回滚段中有1024个undo log segment。
在之前老版本,只支持1个rollback segment,这样就只能记录1024个undo log segment。后来MySQL5.5能够支持128个rollback segment,即支持128*1024个undo操做,还能够经过变量 innodb_undo_logs (5.6版本之前该变量是 innodb_rollback_segments )自定义多少个rollback segment,默认值为128。
undo log默认存放在共享表空间中。
若是开启了 innodb_file_per_table ,将放在每一个表的.ibd文件中。
在MySQL5.6中,undo的存放位置还能够经过变量 innodb_undo_directory 来自定义存放目录,默认值为"."表示datadir。
默认rollback segment所有写在一个文件中,但能够经过设置变量 innodb_undo_tablespaces 平均分配到多少个文件中。该变量默认值为0,即所有写入一个表空间文件。该变量为静态变量,只能在数据库示例中止状态下修改,如写入配置文件或启动时带上对应参数。可是innodb存储引擎在启动过程当中提示,不建议修改成非0的值,以下:
2017-03-31 13:16:00 7f665bfab720 InnoDB: Expected to open 3 undo tablespaces but was able
2017-03-31 13:16:00 7f665bfab720 InnoDB: to find only 0 undo tablespaces.
2017-03-31 13:16:00 7f665bfab720 InnoDB: Set the innodb_undo_tablespaces parameter to the
2017-03-31 13:16:00 7f665bfab720 InnoDB: correct value and retry. Suggested value is 0
undo相关的变量在MySQL5.6中已经变得不多。以下:它们的意义在上文中已经解释了。
当事务提交的时候,innodb不会当即删除undo log,由于后续还可能会用到undo log,如隔离级别为repeatable read时,事务读取的都是开启事务时的最新提交行版本,只要该事务不结束,该行版本就不能删除,即undo log不能删除。
可是在事务提交的时候,会将该事务对应的undo log放入到删除列表中,将来经过purge来删除。而且提交事务时,还会判断undo log分配的页是否能够重用,若是能够重用,则会分配给后面来的事务,避免为每一个独立的事务分配独立的undo log页而浪费存储空间和性能。
经过undo log记录delete和update操做的结果发现:(insert操做无需分析,就是插入行而已)
delete操做实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操做是purge线程完成的。
update分为两种状况:update的列是不是主键列。
若是不是主键列,在undo log中直接反向记录是如何update的。即update是直接进行的。
若是是主键列,update分两部执行:先删除该行,再插入一行目标行。
若是事务不是只读事务,即涉及到了数据的修改,默认状况下会在commit的时候调用fsync()将日志刷到磁盘,保证事务的持久性。
可是一次刷一个事务的日志性能较低,特别是事务集中在某一时刻时事务量很是大的时候。innodb提供了group commit功能,能够将多个事务的事务日志经过一次fsync()刷到磁盘中。
由于事务在提交的时候不只会记录事务日志,还会记录二进制日志,可是它们谁先记录呢?二进制日志是MySQL的上层日志,先于存储引擎的事务日志被写入。
在MySQL5.6之前,当事务提交(即发出commit指令)后,MySQL接收到该信号进入commit prepare阶段;进入prepare阶段后,当即写内存中的二进制日志,写完内存中的二进制日志后就至关于肯定了commit操做;而后开始写内存中的事务日志;最后将二进制日志和事务日志刷盘,它们如何刷盘,分别由变量 sync_binlog 和 innodb_flush_log_at_trx_commit 控制。
但由于要保证二进制日志和事务日志的一致性,在提交后的prepare阶段会启用一个prepare_commit_mutex锁来保证它们的顺序性和一致性。但这样会致使开启二进制日志后group commmit失效,特别是在主从复制结构中,几乎都会开启二进制日志。
在MySQL5.6中进行了改进。提交事务时,在存储引擎层的上一层结构中会将事务按序放入一个队列,队列中的第一个事务称为leader,其余事务称为follower,leader控制着follower的行为。虽然顺序仍是同样先刷二进制,再刷事务日志,可是机制彻底改变了:删除了原来的prepare_commit_mutex行为,也能保证即便开启了二进制日志,group commit也是有效的。
MySQL5.6中分为3个步骤:flush阶段、sync阶段、commit阶段。
flush阶段:向内存中写入每一个事务的二进制日志。
sync阶段:将内存中的二进制日志刷盘。若队列中有多个事务,那么仅一次fsync操做就完成了二进制日志的刷盘操做。这在MySQL5.6中称为BLGC(binary log group commit)。
commit阶段:leader根据顺序调用存储引擎层事务的提交,因为innodb本就支持group commit,因此解决了由于锁 prepare_commit_mutex 而致使的group commit失效问题。
在flush阶段写入二进制日志到内存中,可是不是写完就进入sync阶段的,而是要等待必定的时间,多积累几个事务的binlog一块儿进入sync阶段,等待时间由变量 binlog_max_flush_queue_time 决定,默认值为0表示不等待直接进入sync,设置该变量为一个大于0的值的好处是group中的事务多了,性能会好一些,可是这样会致使事务的响应时间变慢,因此建议不要修改该变量的值,除非事务量很是多而且不断的在写入和更新。
进入到sync阶段,会将binlog从内存中刷入到磁盘,刷入的数量和单独的二进制日志刷盘同样,由变量 sync_binlog 控制。
当有一组事务在进行commit阶段时,其余新事务能够进行flush阶段,它们本就不会相互阻塞,因此group commit会不断生效。固然,group commit的性能和队列中的事务数量有关,若是每次队列中只有1个事务,那么group commit和单独的commit没什么区别,当队列中事务愈来愈多时,即提交事务越多越快时,group commit的效果越明显。
做者:骏马金龙