Databases 15 min read

Understanding MySQL GTID: Architecture, Lifecycle, Generation, and Maintenance

This article explains MySQL GTID (Global Transaction Identifier), covering its composition, lifecycle, how GTIDs are generated during commit, the mechanisms that maintain GTID state, and practical examples that illustrate GTID behavior in replication scenarios.

Tencent Database Technology
Tencent Database Technology
Tencent Database Technology
Understanding MySQL GTID: Architecture, Lifecycle, Generation, and Maintenance

MySQL introduced GTID (Global Transaction Identifier) in version 5.6 to uniquely identify each transaction across a replication cluster, allowing slaves to locate the correct replication position automatically and simplifying failover and topology management.

GTID Composition

A GTID consists of server_uuid:gno , where server_uuid is a 128‑bit UUID generated at server startup and gno is a monotonically increasing sequence number starting from 1 on each server. Example: 3E11FA47-71CA-11E1-9E33-C80AA9429562:23 . GTIDs from the same server are usually contiguous, while GTIDs from different servers are separated by commas.

GTID Lifecycle

When a transaction is executed on the primary, the server assigns the smallest unused gno as the GTID. Transactions that do not write to the binary log (e.g., filtered or read‑only) receive no GTID.

The assigned GTID is written to the binary log as a Gtid_log_event and persisted to mysql.gtid_executed before binlog rotation or server shutdown.

After commit, the GTID is added to the global variable @@GLOBAL.gtid_executed . With binlog enabled, the in‑memory set may differ from the persisted table until the next rotation.

During replication, the primary’s binlog is transferred to the relay log on the replica, which reads the GTID and sets gtid_next accordingly.

The replica checks that no other thread owns the GTID and that it is not already present in gtid_executed before execution, using @@GLOBAL.gtid_owned to track ownership.

Replicas reuse the GTID from the primary rather than generating a new one, preserving global uniqueness.

If the replica has binlog enabled, the GTID is also written to its binlog and persisted similarly.

If binlog is disabled, the replica writes the GTID directly to mysql.gtid_executed ; from MySQL 8.0 onward this operation is atomic for both DDL and DML.

GTID Generation

GTIDs are generated during the commit phase in the MYSQL_BIN_LOG::process_flush_stage_queue function, which calls assign_automatic_gtids_to_flush_group . The call stack includes:

MYSQL_BIN_LOG::ordered_commit()
 // stage #1 flushing transactions to binary log.
 |-process_flush_stage_queue
    |-assign_automatic_gtids_to_flush_group // Leader iterates queue
       |-gtid_state::generate_automatic_gtid
          |-get_automatic_gno // generates gno
          |-acquire_ownership // requests ownership

The function Gtid_state::generate_automatic_gtid obtains a GTID by calling Gtid_state::get_automatic_gno , which scans the current executed_gtids set to find the smallest unused gno :

rpl_gno Gtid_state::get_automatic_gno(rpl_sidno sidno) const {
  DBUG_TRACE;
  Gtid_set::Const_interval_iterator ivit(&executed_gtids, sidno);
  Gtid next_candidate = {sidno,
                         sidno == get_server_sidno() ? next_free_gno : 1};
  while (true) {
    const Gtid_set::Interval *iv = ivit.get();
    rpl_gno next_interval_start = iv != nullptr ? iv->start : MAX_GNO;
    while (next_candidate.gno < next_interval_start &&
           DBUG_EVALUATE_IF("simulate_gno_exhausted", false, true)) {
      DBUG_PRINT("debug", ("Checking availability of gno= %llu", next_candidate.gno));
      if (owned_gtids.is_owned_by(next_candidate, 0)) return next_candidate.gno;
      next_candidate.gno++;
    }
    if (iv == nullptr ||
        DBUG_EVALUATE_IF("simulate_gno_exhausted", true, false)) {
      my_error(ER_GNO_EXHAUSTED, MYF(0));
      return -1;
    }
    if (next_candidate.gno <= iv->end) next_candidate.gno = iv->end;
    ivit.next();
  }
}

After a GTID is generated, Gtid_state::acquire_ownership registers the GTID with the owning thread:

enum_return_status Gtid_state::generate_automatic_gtid(
    THD *thd, rpl_sidno specified_sidno, rpl_gno specified_gno,
    rpl_sidno *locked_sidno) {
  // ...
  if (automatic_gtid.gno == 0) {
    automatic_gtid.gno = get_automatic_gno(automatic_gtid.sidno);
    if (automatic_gtid.sidno == get_server_sidno() && automatic_gtid.gno != -1)
      next_free_gno = automatic_gtid.gno + 1;
  }
  if (automatic_gtid.gno != -1)
    acquire_ownership(thd, automatic_gtid);
  else
    ret = RETURN_STATUS_REPORTED_ERROR;
  // ...
}

When a manual gtid_next creates a gap, MySQL automatically fills the smallest unused GTID on the next transaction, as demonstrated by the following session:

# Show current GTID variables
mysql> show variables like '%gtid%';
+---------------------------+-----------------------------------------------+
| Variable_name             | Value                                         |
+---------------------------+-----------------------------------------------+
| binlog_gtid_simple_recovery| ON                                           |
| enforce_gtid_consistency   | ON                                           |
| gtid_executed               | d4255688-0718-11ec-9687-506b4b430198:1-119686 |
| gtid_next                  | AUTOMATIC                                      |
+---------------------------+-----------------------------------------------+
# Manually set a gap
mysql> set gtid_next='d4255688-0718-11ec-9687-506b4b430198:119690';
mysql> update sbtest9 set k=k+1 where id=1;
# Gap appears
mysql> show variables like '%gtid%';
+---------------------------+------------------------------------------------------+
| Variable_name             | Value                                                |
+---------------------------+------------------------------------------------------+
| gtid_executed             | d4255688-0718-11ec-9687-506b4b430198:1-119686:119690 |
+---------------------------+------------------------------------------------------+
# Restore automatic mode and run another update
mysql> set gtid_next='automatic';
mysql> update sbtest9 set k=k+1 where id=1;
# New GTID fills the smallest unused number (119687)
mysql> show variables like '%gtid%';
+---------------------------+------------------------------------------------------+
| Variable_name             | Value                                                |
+---------------------------+------------------------------------------------------+
| gtid_executed             | d4255688-0718-11ec-9687-506b4b430198:1-119687:119690 |
+---------------------------+------------------------------------------------------+

GTID Maintenance Structures

MySQL uses a Gtid_state object to manage GTID data. Key structures include:

sid : the server UUID (128‑bit).

gno : per‑server incrementing sequence number.

gtid : the pair (sid, gno) , globally unique.

sidno : integer index for the UUID in sid_map .

sid_map : bidirectional map between sidno and sid .

gtid_set : array mapping sidno to a linked list of Interval objects, each representing a range of gno values.

Important runtime objects:

owned_gtids : tracks which thread currently owns each GTID, preventing concurrent execution of the same transaction.

executed_gtids : the in‑memory set of committed GTIDs, visible via @@GLOBAL.gtid_executed .

mysql.gtid_executed table: persists the GTID set to disk; necessary when binlog is disabled on a replica.

lost_gtids (exposed as gtid_purged ): records GTIDs removed from binlog files during cleanup.

Group Commit Call Stack

MYSQL_BIN_LOG::ordered_commit()
 // stage #1 flushing transactions to binary log.
 |-process_flush_stage_queue
    |-fetch_and_process_flush_stage_queue
       |-ha_flush_logs
    |-assign_automatic_gtids_to_flush_group // Leader iterates queue
       |-gtid_state::generate_automatic_gtid
          |-get_automatic_gno // generate gno
          |-acquire_ownership // request ownership, add to owned_gtids
    |-flush_thread_caches
 |-flush_cache_to_file
 // stage #2 Syncing binary log file to disk.
 // stage #3 Commit all transactions in order.
 |-change_stage // may be limited by binlog_order_commits
 |-process_commit_stage_queue
    |-ha_commit_low
    |-gtid_state::update_commit_group // Leader iterates queue
        |-Gtid_state::update_gtids_impl_own_gtid // move from owned_gtids to executed_gtids
 |-process_after_commit_stage_queue
 |-stage_manager.signal_done
 |-finish_commit // when binlog_order_commits=0, threads commit independently
    |-Gtid_state::update_on_commit/update_on_rollback
       |-Gtid_state::update_gtids_impl
           |-Gtid_state::update_gtids_impl_own_gtid // remove from owned_gtids, add to executed_gtids

Conclusion

The article reviewed GTID composition, lifecycle, generation, and maintenance in MySQL. GTIDs simplify replication management but rely on several global and per‑server locks ( global_sid_lock and sid_locks ), which can become bottlenecks under high concurrency, leaving room for further optimization.

References

https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html https://dev.mysql.com/doc/refman/8.0/en/replication-gtids.html

mysqlReplicationtransaction IDDatabase Internalsbinary logGTID
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.