Databases 9 min read

Understanding GTID Maintenance in InnoDB with MySQL Clone Plugin

This article explains how MySQL 8.0’s clone plugin changes GTID handling by storing GTID information in InnoDB undo logs, detailing the allocation of undo log segments, recording GTIDs in undo log headers, persisting them to the gtid_executed table, and coordinating purge operations.

Tencent Database Technology
Tencent Database Technology
Tencent Database Technology
Understanding GTID Maintenance in InnoDB with MySQL Clone Plugin

The article introduces the background of GTID handling in MySQL. Prior to the clone plugin, GTIDs were persisted via the server binlog and the mysql.gtid_executed table. Starting with MySQL 8.0.17, the InnoDB‑based clone plugin records GTID information directly in undo logs, requiring new mechanisms for allocation, persistence, and purge coordination.

2.1 Allocate Undo Log Segment

Before committing a transaction, MySQL allocates an appropriate undo log segment (Update or Insert) to enable rollback. With the clone plugin, GTID data is also written to the undo log header. The function trx_undo_gtid_add_update_undo checks whether an undo segment has been allocated and, for insert_only transactions, forces an Update undo segment.

dberr_t trx_undo_gtid_add_update_undo(trx_t *trx, bool prepare, bool rollback) {
  // ...
  if (undo_ptr->is_insert_only() || gtid_explicit) {
    ut_ad(!rollback);
    mutex_enter(&trx->undo_mutex);
    db_err = trx_undo_assign_undo(trx, undo_ptr, TRX_UNDO_UPDATE);
    mutex_exit(&trx->undo_mutex);
  }
  // ...
}

2.2 Record GTID in Undo Log Header

After allocating the Update undo segment, the GTID is extracted from the transaction object via gtid_persistor.get_gtid_info and written into the undo log header.

void trx_undo_gtid_write(trx_t *trx, trx_ulogf_t *undo_header, trx_undo_t *undo, mtr_t *mtr, bool is_xa_prepare) {
  // ......
  std::tie(gtid_flag, gtid_offset) = undo->gtid_get_details(is_xa_prepare);
  // ......
  gtid_persistor.get_gtid_info(trx, gtid_desc);      // extract GTID from trx->mysql_thd
  if (gtid_desc.m_is_set) {
    /* Persist GTID version */
    mlog_write_ulint(undo_header + TRX_UNDO_LOG_GTID_VERSION,
                     gtid_desc.m_version, MLOG_1BYTE, mtr);
    /* Persist fixed length GTID */
    ut_ad(TRX_UNDO_LOG_GTID_LEN == GTID_INFO_SIZE);
    mlog_write_string(undo_header + gtid_offset, &gtid_desc.m_info[0],
                      TRX_UNDO_LOG_GTID_LEN, mtr);
    undo->flag |= gtid_flag;
  }
  mlog_write_ulint(undo_header + TRX_UNDO_FLAGS, undo->flag, MLOG_1BYTE, mtr);
}

The exact location within the header is decided by gtid_get_details , using fields such as TRX_UNDO_LOG_GTID and TRX_UNDO_LOG_GTID_XA :

|----gtid version-(1)---|---------gtid--(64)---------|--------gtid xa--(64)---------|

2.3 Persist GTID

After a transaction commits, the GTID is added to the Clone plugin’s Clone_persist_gtid object (named gtid_persistor ). It maintains an Active list of GTIDs that are periodically flushed to the mysql.gtid_executed table.

int Clone_persist_gtid::write_to_table(uint64_t flush_list_number, Gtid_set &table_gtid_set, Sid_map &sid_map) {
  Gtid_set write_gtid_set(&sid_map, nullptr);
  auto &flush_list = get_list(flush_list_number);
  /* Extract GTIDs from flush list. */
  for (auto &gtid_info : flush_list) {
    auto gtid_str = reinterpret_cast
(&gtid_info[0]); // get GTID text
    auto status = write_gtid_set.add_gtid_text(gtid_str);
  }

  /* Write GTIDs to table. */
  if (!write_gtid_set.is_empty()) {
    ++m_compression_counter;
    err = gtid_table_persistor->save(&write_gtid_set, false); // persist to table
  }

  /* Clear flush list and return */
  flush_list.clear();
  ut_ad((m_flush_number + 1) == flush_list_number);
  m_flush_number.store(flush_list_number);
  return (err);
}

The persistor also tracks the oldest un‑persisted transaction number ( m_gtid_trx_no ) so that purge operations know which undo logs are safe to remove.

void Clone_persist_gtid::flush_gtids(THD *thd) {
  // ....
  /* Update trx number up to which GTID is written to table. */
  update_gtid_trx_no(oldest_trx_no);
  // ...
}

2.4 Purge Undo Log

The purge thread consults the clone plugin to obtain the oldest transaction whose GTID has not yet been persisted, and adjusts the read view’s low‑limit accordingly, preventing premature purge of undo logs that still contain un‑flushed GTIDs.

void MVCC::clone_oldest_view(ReadView *view) {
  // ...
  /* Update view to block purging transaction till GTID is persisted. */
  auto &gtid_persistor = clone_sys->get_gtid_persistor();
  auto gtid_oldest_trxno = gtid_persistor.get_oldest_trx_no();
  view->reduce_low_limit(gtid_oldest_trxno);
}

Conclusion

The article summarizes how MySQL 8.0’s clone plugin shifts GTID management to the InnoDB layer, covering undo segment allocation, GTID recording in undo headers, periodic persistence to mysql.gtid_executed , and coordination with the purge thread. Readers are encouraged to explore the referenced source files for deeper understanding.

InnoDBmysqlDatabase InternalsGTIDClone Plugin
Tencent Database Technology
Written by

Tencent Database Technology

Tencent's Database R&D team supports internal services such as WeChat Pay, WeChat Red Packets, Tencent Advertising, and Tencent Music, and provides external support on Tencent Cloud for TencentDB products like CynosDB, CDB, and TDSQL. This public account aims to promote and share professional database knowledge, growing together with database enthusiasts.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.