MySQL为了保证master和slave的数据一致性,就必须保证binlog和InnoDB redo日志的一致性(因为备库通过二进制日志重放主库提交的事务,而主库binlog写入在commit之前,如果写完binlog主库crash,再次启动时会回滚事务。
一、为什么要保证binlog和redo log提交的顺序一致
MySQL为了保证master和slave的数据一致性,就必须保证binlog和InnoDB redo日志的一致性(因为备库通过二进制日志重放主库提交的事务,而主库binlog写入在commit之前,如果写完binlog主库crash,再次启动时会回滚事务。但此时从库已经执行,则会造成主备数据不一致)。所以在开启Binlog后,如何保证binlog和InnoDB redo日志的一致性呢?为此,MySQL引入二阶段提交(two phase commit or 2pc),MySQL内部会自动将普通事务当做一个XA事务(内部分布式事物)来处理:
– 自动为每个事务分配一个少数的ID(XID)。
– COMMIT会被自动的分成Prepare和Commit两个阶段。
– Binlog会被当做事务协调者(Transaction Coordinator),Binlog Event会被当做协调者日志。
Binlog在2PC中充当了事务的协调者(Transaction Coordinator)。由Binlog来通知InnoDB引擎来执行prepare,commit或者rollback的步骤。
事务的提交主要分为两个主要步骤:
1. 准备阶段(Storage Engine(InnoDB) Transaction Prepare Phase)
此时SQL已经成功执行,并生成xid信息及redo和undo的内存日志。然后调用prepare方法完成名列前茅阶段,papare方法实际上什么也没做,将事务状态设为TRX_PREPARED,并将redo log刷磁盘。
2. 提交阶段(Storage Engine(InnoDB)Commit Phase)
2.1 记录协调者日志,即Binlog日志。
如果事务涉及的所有存储引擎的prepare都执行成功,则调用TC_LOG_BINLOG::log_xid方法将SQL语句写到binlog(write()将binary log内存日志数据写入文件系统缓存,fsync()将binary log文件系统缓存日志数据永久写入磁盘)。此时,事务已经铁定要提交了。否则,调用ha_rollback_trans方法回滚事务,而SQL语句实际上也不会写到binlog。
2.2 告诉引擎做commit。
最后,调用引擎的commit完成事务的提交。会清除undo信息,刷redo日志,将事务设为TRX_NOT_STARTED状态。
PS:记录Binlog是在InnoDB引擎Prepare(即Redo Log写入磁盘)之后,这点至关重要。
由上面的二阶段提交流程可以看出,一旦步骤2中的操作完成,就确保了事务的提交,即使在执行步骤3时数据库发送了宕机。此外需要注意的是,每个步骤都需要进行一次fsync操作才能保证上下两层数据的一致性。步骤2的fsync参数由sync_binlog=1控制,步骤3的fsync由参数innodb_flush_log_at_trx_commit=1控制,俗称“双1”,是保证CrashSAFe的根本。
延伸阅读:
二、关系型数据库中的数据组织
关系型数据库中,数据组织涉及到两个最基本的结构:表与索引。表中存储的是完整记录,一般有两种组织形式:堆表(所有的记录无序存储),或者是聚簇索引表(所有的记录,按照记录主键进行排序存储)。索引中存储的是完整记录的一个子集,用于加速记录的查询速度,索引的组织形式,一般均为B+树结构。