-
Nuno Carvalho authored
Group Replication uses transactions write-set and database version to check if parallel transactions from different members do conflict, on which case the ordered first is the one allowed to commit and the other(s) are rollback. The write-set is the list of the primary key equivalents that can unequivocally identify a row on a table. The database version is the GTID_EXECUTED on top of which the transaction was executed. These information combined with the Database State Machine (DBSM) approach allow us to check for conflicts between transactions. When two transactions write on the same rows on the same server, there was a race between InnoDB release locks and GTID_EXECUTED read: T1: W(A) T2: W(A) -- blocks T1: PREPARE (innodb) T1: before_commit hook T1: Read GTID_EXECUTED T1: FLUSH (binlog) T1: SYNC (binlog) T1: COMMIT (innodb) T1: release innodb locks (unblocks T2) T2: PREPARE (innodb) T2: before_commit hook T2: read GTID_EXECUTED T1: update GTID_EXECUTED The "T2: read GTID_EXECUTED" operation would have missed the fact that T1 had already committed, thence would treat it as conflicting. There was a race condition on GTID_EXECUTED read that could make transactions to read not updated GTID_EXECUTED, despite the binary log was already updated, that is, there was a small windows between write to binary log and update GTID_EXECUTED on which parallel threads could read a GTID_EXECUTED value which did not include the last logged transaction to binary log. When operating on single member, this window could cause a parallel transactions to be marked as conflicting despite it was not, since it was serialized be the server. The problem was the outdated GTID_EXECUTED value that this transaction has read. To eliminate this opportunity window, now GTID_EXECUTED is read under the protection of MYSQL_BIN_LOG::LOCK_commit to ensure that we when GTID_EXECUTED is read all previous transactions are committed and GTID_EXECUTED updated: T1: W(A) T2: W(A) -- blocks T1: PREPARE (innodb) T1: before_commit hook T1: Read GTID_EXECUTED T1: FLUSH (binlog) T1: SYNC (binlog) T1: COMMIT (innodb) T1: update GTID_EXECUTED T1: release innodb locks (unblocks T2) T2: PREPARE (innodb) T2: before_commit hook T2: read GTID_EXECUTED
Nuno Carvalho authoredGroup Replication uses transactions write-set and database version to check if parallel transactions from different members do conflict, on which case the ordered first is the one allowed to commit and the other(s) are rollback. The write-set is the list of the primary key equivalents that can unequivocally identify a row on a table. The database version is the GTID_EXECUTED on top of which the transaction was executed. These information combined with the Database State Machine (DBSM) approach allow us to check for conflicts between transactions. When two transactions write on the same rows on the same server, there was a race between InnoDB release locks and GTID_EXECUTED read: T1: W(A) T2: W(A) -- blocks T1: PREPARE (innodb) T1: before_commit hook T1: Read GTID_EXECUTED T1: FLUSH (binlog) T1: SYNC (binlog) T1: COMMIT (innodb) T1: release innodb locks (unblocks T2) T2: PREPARE (innodb) T2: before_commit hook T2: read GTID_EXECUTED T1: update GTID_EXECUTED The "T2: read GTID_EXECUTED" operation would have missed the fact that T1 had already committed, thence would treat it as conflicting. There was a race condition on GTID_EXECUTED read that could make transactions to read not updated GTID_EXECUTED, despite the binary log was already updated, that is, there was a small windows between write to binary log and update GTID_EXECUTED on which parallel threads could read a GTID_EXECUTED value which did not include the last logged transaction to binary log. When operating on single member, this window could cause a parallel transactions to be marked as conflicting despite it was not, since it was serialized be the server. The problem was the outdated GTID_EXECUTED value that this transaction has read. To eliminate this opportunity window, now GTID_EXECUTED is read under the protection of MYSQL_BIN_LOG::LOCK_commit to ensure that we when GTID_EXECUTED is read all previous transactions are committed and GTID_EXECUTED updated: T1: W(A) T2: W(A) -- blocks T1: PREPARE (innodb) T1: before_commit hook T1: Read GTID_EXECUTED T1: FLUSH (binlog) T1: SYNC (binlog) T1: COMMIT (innodb) T1: update GTID_EXECUTED T1: release innodb locks (unblocks T2) T2: PREPARE (innodb) T2: before_commit hook T2: read GTID_EXECUTED
Loading