MySQL 组提交

prepare_commit_mutex锁

  • MySQL5.6以前,为了保证数据库上层二进制日志的写入顺序和InnoDB层的事务提交顺序一致,MySQL数据库内部使用了prepare_commit_mutex锁。但是持有这把锁之后,会导致组提交失败。锁的持有与释放在二阶段中如下:

    • InnoDB prepare (持有prepare_commit_mutex);

    • write/sync Binlog;

    • InnoDB commit (写入COMMIT标记后释放prepare_commit_mutex)。

  • 这样事务提交就是一个一个执行,导致组提交失败。

Binary Log Group Commit(BLGC)

  • MySQL5.6通过BLGC的方式实现了binlog的组提交。

  • binlog组提交的基本思想是,引入队列机制保证innodb commit顺序与binlog落盘顺序一致,并将事务分组,组内的binlog刷盘动作交给一个事务进行,实现组提交目的。 

  • binlog提交将提交分为了3个阶段,FLUSH阶段,SYNC阶段和COMMIT阶段。每个阶段都有一个队列,队列中的第一个事务称为leader,其他事务称为follower,leader控制着follower的行为。BLGC的步骤分为以下三个阶段:

  • FLUSH阶段:

    • 持有Lock_log mutex [leader持有,follower等待]

    • 获取队列中的一组binlog(队列中的所有事务)

    • 将binlog buffer到I/O cache

    • 通知dump线程dump binlog

  • SYNC阶段:

    • 释放Lock_log mutex,持有Lock_sync mutex[leader持有,follower等待]

    • 将一组binlog 落盘(sync动作,最耗时,也是group commit实现了的优化的重点所在)

  • COMMIT阶段:

    • 释放Lock_sync mutex,持有Lock_commit mutex[leader持有,follower等待]

    • 遍历队列中的事务,逐一进行innodb commit(这里不用写redo log,在prepare阶段已写)

    • 释放Lock_commit mutex

    • 唤醒队列中等待的线程

  • 每个stage分配一个线程进行操作。

  • 这种实现的优势在于三个阶段可以并发执行,从而提升效率。(PS:innodb prepare阶段没有变,还是write/sync redo log,打上prepare标记)

  • 每个stage都有自己的队列。每个队列各自有mutex保护,队列之间是顺序的。只有flush完成后,才能进入到sync阶段的队列中;sync完成后,才能进入到commit阶段的队列中。但是,这三个阶段的作业是可以同时并发执行的,即当一组事务在进行commit阶段时,其他新事务可以进行flush阶段,实现了group commit。

  • 当一个事务来到一个stage是一个空队列,那么他就是leader,后面来的事务就是follower,leader控制队列中follower的行为。如果leader带着自己的follower去下一个stage,是非空队列,那么leader变成follower。但是follower不会变成leader。

  • Tips:当引入Group Commit后,sync_binlog的含义就变了,假定设为1000,表示的不是1000个事务后做一次fsync,而是1000个事务组。也就是说,当设置sync_binlog=1,binlog还未落盘,此时系统crash,会丢失对应的最后一个事务组;如果这个事务组内有10个事务,那么这10个事务都会丢失。

  • 如何查看是否属于一个事务组:通过mysqlbinlog可以查看binlog日志中last_committed值,如果值一样,表明是在同一事务组内。

### INSERT INTO `wukong_test`.`wukong`
### SET
###   @1=3 /* INT meta=0 nullable=1 is_null=0 */
###   @2='ccccc' /* VARSTRING(80) meta=80 nullable=1 is_null=0 */
# at 496468
#170527  4:17:35 server id 12001  end_log_pos 496499 CRC32 0xd6e7f69f   Xid = 5556
COMMIT/*!*/;
# at 496499
#170527  4:17:35 server id 12001  end_log_pos 496564 CRC32 0x28816d5c   GTID    last_committed=1845 sequence_number=1846
SET @@SESSION.GTID_NEXT= '0a646c88-36e2-11e7-937d-fa163ed7a7b1:3624'/*!*/;
# at 496564
#170527  4:17:35 server id 12001  end_log_pos 496632 CRC32 0x03150d48   Query   thread_id=1852  exec_time=0 error_code=0
SET TIMESTAMP=1495873055/*!*/;
BEGIN