MySQL

Replication Internals: Decoding the MySQL Binary Log - Part 6: QUERY_EVENT — DDL Statements and Transaction Boundaries

11 min read

about 2 months ago

Replication Internals: Decoding the MySQL Binary Log - Part 6: QUERY_EVENT — DDL Statements and Transaction Boundaries

In this sixth post of our series, we decode the QUERY_EVENT — the workhorse event that records DDL statements and transaction boundaries.


Introduction

The QUERY_EVENT (event type 2, 0x02) is one of the most versatile events in a binary log. It records:

  • DDL statements: CREATE, ALTER, DROP, TRUNCATE, etc.
  • Transaction boundaries: BEGIN statements that start a transaction
  • Statement-based DML: In statement-based replication mode, INSERT/UPDATE/DELETE

Even in row-based replication (the default since MySQL 5.7), DDL statements are always logged as QUERY_EVENTs because their effects can't be represented as row changes.


Event Locations

In our binary log, we have several QUERY_EVENTs:

Position 276: QUERY_EVENT (182 bytes) - CREATE TABLE Position 537: QUERY_EVENT (83 bytes) - BEGIN (for INSERT) Position 847: QUERY_EVENT (92 bytes) - BEGIN (for UPDATE) Position 1189: QUERY_EVENT (83 bytes) - BEGIN (for DELETE)

Let's decode both types: the DDL (CREATE TABLE) and the transaction boundary (BEGIN).


Common Header (19 bytes)

Let's start with the CREATE TABLE event at position 276:

2d103568 02 01000000 b6000000 ca010000 0000 │ │ │ │ │ │ │ │ │ │ │ └─→ Flags: 0x0000 │ │ │ │ └───────────→ Next Position: 458 │ │ │ └────────────────────→ Event Size: 182 bytes │ │ └─────────────────────────────→ Server ID: 1 │ └────────────────────────────────→ Event Type: 2 (QUERY_EVENT) └─────────────────────────────────────────→ Timestamp: 1748308013

FieldBytesLittle-EndianValue
Timestamp2d1035680x6835102d1748308013 (2025-05-27 01:06:53)
Event Type020x022 (QUERY_EVENT)
Server ID010000000x000000011
Event Sizeb60000000x000000b6182 bytes
Next Positionca0100000x000001ca458
Flags00000x0000No flags

The common header flags field is a bitmask. For QUERY_EVENTs, the most relevant flags are:

FlagValueMeaning
LOG_EVENT_THREAD_SPECIFIC_F0x04Query depends on a thread-specific value (e.g., uses TEMPORARY TABLE)
LOG_EVENT_SUPPRESS_USE_F0x08Don't prepend USE database before executing the query on the replica
LOG_EVENT_ARTIFICIAL_F0x20Artificially generated event — don't update the master log position
LOG_EVENT_RELAY_LOG_F0x40Event was created by the replica's IO thread (written to relay log)
LOG_EVENT_IGNORABLE_F0x80Event can be safely ignored by older replicas that don't understand it
LOG_EVENT_NO_FILTER_F0x100Event is not affected by replication filters
LOG_EVENT_MTS_ISOLATE_F0x200Forces isolation in the multi-threaded replica applier

Our CREATE TABLE event has flags 0x0000 — no special flags set. As we'll see later, the BEGIN events have 0x0008 (LOG_EVENT_SUPPRESS_USE_F) set.


Reading the Raw Bytes

$ xxd -s 276 -l 182 binlog.000024 00000114: 2d10 3568 0201 0000 00b6 0000 00ca 0100 -.5h............ 00000124: 0000 000a 0000 0000 0000 000c 0000 3700 ..............7. 00000134: 0000 0000 0001 2000 a045 0000 0000 0603 ...... ..E...... 00000144: 7374 6404 ff00 ff00 ff00 0c01 7072 6573 std.........pres 00000154: 656e 7461 7469 6f6e 0011 3600 0000 0000 entation..6..... 00000164: 0000 12ff 0013 0070 7265 7365 6e74 6174 .......presentat 00000174: 696f 6e00 4352 4541 5445 2054 4142 4c45 ion.CREATE TABLE 00000184: 2070 6572 736f 6e20 280a 2020 4944 2049 person (. ID I 00000194: 4e54 2050 5249 4d41 5259 204b 4559 2c0a NT PRIMARY KEY,. 000001a4: 2020 6e61 6d65 2056 4152 4348 4152 2831 name VARCHAR(1 000001b4: 3530 2920 4445 4641 554c 5420 4e55 4c4c 50) DEFAULT NULL 000001c4: 0a29 0536 a92b .).6.+

The event is 182 bytes: 19-byte common header + 13-byte post-header + 55-byte status variables + 13-byte database name + 78-byte query + 4-byte checksum.


QUERY_EVENT Structure

The QUERY_EVENT has a 13-byte post-header (QUERY_HEADER_LEN) followed by variable-length data:

FieldSizeDescription
Post-Header (13 bytes)
Thread ID4 bytesID of the thread that executed the query
Execution Time4 bytesTime in seconds the query took to execute
Database Length1 byteLength of the default database name (max 255)
Error Code2 bytesMySQL error code (0 for success)
Status Vars Length2 bytesLength of the status variables block
Data Body
Status VariablesVariableSession settings (SQL mode, character set, etc.) — Type-Value encoded
Database NameVariableNull-terminated default database (db_len + 1 bytes)
QueryVariableThe SQL statement (not null-terminated)

The query length is computed from the remaining bytes: event_size - 19 - 13 - status_vars_len - db_len - 1 - 4(checksum).


Post Header: Field-by-Field Decoding

0a000000 00000000 0c 0000 3700 │ │ │ │ │ │ │ │ │ └─→ Status Vars Length: 55 bytes │ │ │ └───────→ Error Code: 0 (success) │ │ └──────────→ Database Length: 12 ("presentation") │ └───────────────────→ Execution Time: 0 seconds └────────────────────────────→ Thread ID: 10

FieldBytesValue
Thread ID0a00000010
Execution Time000000000 seconds
Database Length0c12 bytes
Error Code00000 (success)
Status Vars Length370055 bytes

The Thread ID identifies the connection that ran the query. The Execution Time records how long the query took to execute on the source — replicas can use this to estimate replication lag. The Error Code is non-zero when a query that produces an error is intentionally replicated (e.g., DROP TABLE IF NOT EXISTS on a table that doesn't exist produces error 1051, but is still logged).


Status Variables (55 bytes)

Status variables carry session state that the replica needs to replay the query correctly. They are encoded as Type-Value pairs: a 1-byte type code followed by a value whose size depends on the type. The type codes are defined in Query_event_status_vars.

Note: Unlike TLV (Type-Length-Value) encoding from Part 1, status variables do not carry a length field. The size of each type code's value is hardcoded in the deserialization switch statement. This means a parser that encounters an unknown type code cannot skip over it — it has no way to determine how many bytes the value occupies. The MySQL parser handles this by stopping the status variable parse at that point.

Here are the status variables in our CREATE TABLE event:

00 00000000 01 2000a04500000000 06 03 737464 04 ff00ff00ff00 0c 01 70726573656e746174696f6e 00 11 3600000000000000 12 ff00 13 00
OffsetTypeNameRaw BytesDecoded Value
00x00Q_FLAGS2_CODE00 000000000x00000000 — no session flags set
50x01Q_SQL_MODE_CODE01 2000a045000000000x0000000045a00020
140x06Q_CATALOG_NZ_CODE06 03 737464length=3, "std"
190x04Q_CHARSET_CODE04 ff00 ff00 ff00client=255, connection=255, server=255
260x0cQ_UPDATED_DB_NAMES0c 01 707265...00count=1, ["presentation"]
410x11Q_DDL_LOGGED_WITH_XID11 3600000000000000xid=54
500x12Q_DEFAULT_COLLATION_FOR_UTF8MB412 ff00collation=255 (utf8mb4_0900_ai_ci)
530x13Q_SQL_REQUIRE_PRIMARY_KEY13 000 (not required)

Let's look at the most important ones in detail.

Q_FLAGS2_CODE (0x00) — Session Flags

4-byte bitmask of session options from OPTIONS_WRITTEN_TO_BIN_LOG:

BitFlagMeaning
14OPTION_AUTO_IS_NULL@@sql_auto_is_null behavior — SELECT * WHERE id IS NULL returns the last inserted auto-inc row
19OPTION_NOT_AUTOCOMMIT@@autocommit = 0 — transactions are not auto-committed
26OPTION_NO_FOREIGN_KEY_CHECKS@@foreign_key_checks = 0 — foreign key constraints are not enforced
27OPTION_RELAXED_UNIQUE_CHECKS@@unique_checks = 0 — unique index constraints are not enforced

In our event, flags2 = 0x00000000 — all defaults (autocommit on, FK checks on, unique checks on).

Q_SQL_MODE_CODE (0x01) — SQL Mode

8-byte bitmask representing @@sql_mode. The value 0x0000000045a00020 decodes to these active flags:

BitFlag
5MODE_ONLY_FULL_GROUP_BY
21MODE_STRICT_TRANS_TABLES
23MODE_NO_ZERO_IN_DATE
24MODE_NO_ZERO_DATE
26MODE_ERROR_FOR_DIVISION_BY_ZERO
30MODE_NO_ENGINE_SUBSTITUTION

Q_CATALOG_NZ_CODE (0x06) — Catalog Name

1-byte length prefix followed by the catalog string (no null terminator). Currently, MySQL only has one catalog: "std". This field exists for forward compatibility.

Q_CHARSET_CODE (0x04) — Character Sets

6 bytes encoding three 2-byte little-endian collation IDs:

BytesFieldValueMeaning
ff00character_set_client255utf8mb4_0900_ai_ci
ff00collation_connection255utf8mb4_0900_ai_ci
ff00collation_server255utf8mb4_0900_ai_ci

These ensure the replica uses the same character set settings when executing the query, so that string comparisons and conversions produce identical results.

Q_DDL_LOGGED_WITH_XID (0x11) — DDL Transaction ID

8-byte XID value. This is the internal transaction ID assigned to the DDL statement, making DDL crash-safe. In our event, xid=54. This field is only present for DDL statements (CREATE, ALTER, DROP, etc.), not for BEGIN queries.


Complete Status Variable Reference

For completeness, here is the full list of all status variable types defined in Query_event_status_vars:

CodeNameSizeDescription
0Q_FLAGS2_CODE4 bytesSession flags bitmask
1Q_SQL_MODE_CODE8 bytesSQL mode bitmask
2Q_CATALOG_CODE1 + N + 1 bytesLegacy catalog (with null terminator, MySQL 5.0.0–5.0.3 only)
3Q_AUTO_INCREMENT4 bytesTwo uint16: auto_increment_increment and auto_increment_offset
4Q_CHARSET_CODE6 bytesThree uint16: client charset, connection collation, server collation
5Q_TIME_ZONE_CODE1 + N bytesTime zone name (length-prefixed string)
6Q_CATALOG_NZ_CODE1 + N bytesCatalog name (length-prefixed, no null terminator)
7Q_LC_TIME_NAMES_CODE2 bytesLocale number for lc_time_names
8Q_CHARSET_DATABASE_CODE2 bytesDatabase default collation
9Q_TABLE_MAP_FOR_UPDATE_CODE8 bytes64-bit table map for multi-table UPDATE
10Q_MASTER_DATA_WRITTEN_CODE4 bytesPlaceholder — unused after MySQL 8.0.2
11Q_INVOKER1 + N + 1 + N bytesDEFINER's user and host (for stored routines, views, triggers)
12Q_UPDATED_DB_NAMES1 + variableCount of accessed databases + null-terminated names (for parallel replication)
13Q_MICROSECONDS3 bytesMicrosecond component of the query timestamp
14Q_COMMIT_TSObsolete (unused)
15Q_COMMIT_TS2Obsolete (unused)
16Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP1 byte@@explicit_defaults_for_timestamp value
17Q_DDL_LOGGED_WITH_XID8 bytesInternal XID for crash-safe DDL
18Q_DEFAULT_COLLATION_FOR_UTF8MB42 bytesDefault utf8mb4 collation (for cross-version replication)
19Q_SQL_REQUIRE_PRIMARY_KEY1 byte@@sql_require_primary_key value
20Q_DEFAULT_TABLE_ENCRYPTION1 byte@@default_table_encryption value

Database Name and Query

After the status variables, we have the database name (null-terminated):

70 72 65 73 65 6e 74 61 74 69 6f 6e 00 p r e s e n t a t i o n \0

Database: "presentation" (12 bytes + 1 null terminator)

The rest of the payload (before the 4-byte checksum) is the SQL statement (78 bytes, not null-terminated):

CREATE TABLE person ( ID INT PRIMARY KEY, name VARCHAR(150) DEFAULT NULL )

The BEGIN Event (Position 537)

Transaction boundaries for row-based events are marked with a QUERY_EVENT containing "BEGIN":

$ xxd -s 537 -l 83 binlog.000024 00000219: 3210 3568 0201 0000 0053 0000 006c 0200 2.5h.....S...l.. 00000229: 0008 000a 0000 0000 0000 000c 0000 1d00 ................ 00000239: 0000 0000 0001 2000 a045 0000 0000 0603 ...... ..E...... 00000249: 7374 6404 ff00 ff00 ff00 12ff 0070 7265 std..........pre 00000259: 7365 6e74 6174 696f 6e00 4245 4749 4e95 sentation.BEGIN. 00000269: c51d cf ...

Common Header

32103568 02 01000000 53000000 6c020000 0800 │ │ │ │ │ │ │ │ │ │ │ └─→ Flags: 0x0008 (LOG_EVENT_SUPPRESS_USE_F) │ │ │ │ └───────────→ Next Position: 620 │ │ │ └────────────────────→ Event Size: 83 bytes │ │ └─────────────────────────────→ Server ID: 1 │ └────────────────────────────────→ Event Type: 2 (QUERY_EVENT) └─────────────────────────────────────────→ Timestamp: 1748308018

Note the flags field is 0x0008 — this is LOG_EVENT_SUPPRESS_USE_F, which tells the replica not to execute a USE database statement before this query. This makes sense — BEGIN doesn't depend on the current database.

Post-Header

0a000000 00000000 0c 0000 1d00 │ │ │ │ │ │ │ │ │ └─→ Status Vars Length: 29 bytes │ │ │ └───────→ Error Code: 0 │ │ └──────────→ Database Length: 12 │ └───────────────────→ Execution Time: 0 └────────────────────────────→ Thread ID: 10

The BEGIN event has only 29 bytes of status variables (vs. 55 for CREATE TABLE) — it doesn't include Q_UPDATED_DB_NAMESQ_DDL_LOGGED_WITH_XID, or Q_SQL_REQUIRE_PRIMARY_KEY since those are DDL-specific.

Status Variables (29 bytes)

TypeNameValue
0x00Q_FLAGS2_CODE0x00000000
0x01Q_SQL_MODE_CODE0x0000000045a00020
0x06Q_CATALOG_NZ_CODE"std"
0x04Q_CHARSET_CODEclient=255, conn=255, server=255
0x12Q_DEFAULT_COLLATION_FOR_UTF8MB4255

Query

BEGIN

Comparing the BEGIN Events

Our binary log has three BEGIN events. Let's compare them:

PositionSizeFlagsStatus Vars LenExtra Status Variables
537830x000829
847920x000838Q_TABLE_MAP_FOR_UPDATE_CODE = 0x0000000000000001
1189830x000829

The UPDATE transaction's BEGIN event (position 847) is 9 bytes larger because it includes Q_TABLE_MAP_FOR_UPDATE_CODE (1 byte type + 8 bytes value). This 64-bit bitmask identifies which tables will be modified by the transaction — the replica's multi-threaded applier uses this to determine if transactions can be applied in parallel.


Visual Breakdown: CREATE TABLE Event

Position 276: QUERY_EVENT - CREATE TABLE (182 bytes) ┌─────────────────────────────────────────────────────────────────────────┐ │ COMMON HEADER (19 bytes) │ ├─────────────────────────────────────────────────────────────────────────┤ │ 2d103568 │ 02 │ 01000000 │ b6000000 │ ca010000 │ 0000 │ │ Timestamp │ Type │ ServerID │ Size │ NextPos │ Flags │ │ 1748308013 │ 2 │ 1 │ 182 │ 458 │ 0x0000 │ └─────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────┐ │ POST-HEADER (13 bytes) │ ├──────────────────────────────┬──────────────────────────────────────────┤ │ 0a000000 │ Thread ID: 10 │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 00000000 │ Execution Time: 0 seconds │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 0c │ Database Length: 12 │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 0000 │ Error Code: 0 (success) │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 3700 │ Status Vars Length: 55 │ └──────────────────────────────┴──────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────┐ │ STATUS VARIABLES (55 bytes) │ ├──────────────────────────────┬──────────────────────────────────────────┤ │ 00 00000000 │ Q_FLAGS2_CODE: 0x00000000 │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 01 2000a04500000000 │ Q_SQL_MODE_CODE: 0x0000000045a00020 │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 06 03 737464 │ Q_CATALOG_NZ_CODE: "std" │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 04 ff00ff00ff00 │ Q_CHARSET_CODE: 255/255/255 │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 0c 01 707265...00 │ Q_UPDATED_DB_NAMES: ["presentation"] │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 11 3600000000000000 │ Q_DDL_LOGGED_WITH_XID: xid=54 │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 12 ff00 │ Q_DEFAULT_COLLATION_FOR_UTF8MB4: 255 │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 13 00 │ Q_SQL_REQUIRE_PRIMARY_KEY: 0 │ └──────────────────────────────┴──────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────┐ │ DATABASE + QUERY │ ├──────────────────────────────┬──────────────────────────────────────────┤ │ 70726573656e746174696f6e00 │ Database: "presentation\0" (13 bytes) │ ├──────────────────────────────┼──────────────────────────────────────────┤ │ 435245415445205441424c4520...│ Query (78 bytes): │ │ │ CREATE TABLE person ( │ │ │ ID INT PRIMARY KEY, │ │ │ name VARCHAR(150) DEFAULT NULL │ │ │ ) │ └──────────────────────────────┴──────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────┐ │ CHECKSUM (4 bytes) │ ├──────────────────────────────┬──────────────────────────────────────────┤ │ 0536a92b │ CRC32 │ └──────────────────────────────┴──────────────────────────────────────────┘

Transaction Flow

Here's how a typical DML transaction looks in the binary log:

GTID_LOG_EVENT ← Transaction identifier (GNO=13) QUERY_EVENT (BEGIN) ← Transaction start TABLE_MAP_EVENT ← Table metadata WRITE_ROWS_EVENT ← The actual row data XID_EVENT ← Transaction commit

For DDL statements:

GTID_LOG_EVENT ← Transaction identifier (GNO=12) QUERY_EVENT (DDL) ← The DDL statement itself (implicitly committed)

Note that DDL statements don't have explicit BEGIN/COMMIT — they are implicitly committed. The Q_DDL_LOGGED_WITH_XID status variable ensures crash-safety for the DDL operation.


Try It Yourself

import struct STATUS_VAR_NAMES = { 0x00: ('Q_FLAGS2_CODE', 4), 0x01: ('Q_SQL_MODE_CODE', 8), 0x03: ('Q_AUTO_INCREMENT', 4), 0x04: ('Q_CHARSET_CODE', 6), 0x06: ('Q_CATALOG_NZ_CODE', None), # variable 0x07: ('Q_LC_TIME_NAMES_CODE', 2), 0x08: ('Q_CHARSET_DATABASE_CODE', 2), 0x09: ('Q_TABLE_MAP_FOR_UPDATE_CODE', 8), 0x0c: ('Q_UPDATED_DB_NAMES', None), # variable 0x11: ('Q_DDL_LOGGED_WITH_XID', 8), 0x12: ('Q_DEFAULT_COLLATION_FOR_UTF8MB4', 2), 0x13: ('Q_SQL_REQUIRE_PRIMARY_KEY', 1), 0x14: ('Q_DEFAULT_TABLE_ENCRYPTION', 1), } def decode_status_vars(data): """Decode QUERY_EVENT status variables (Type-Value pairs).""" off = 0 while off < len(data): code = data[off] off += 1 info = STATUS_VAR_NAMES.get(code) name = info[0] if info else f'UNKNOWN(0x{code:02x})' if code == 0x00: val = struct.unpack('<I', data[off:off+4])[0] print(f" 0x{code:02x} {name}: 0x{val:08x}") off += 4 elif code == 0x01: val = struct.unpack('<Q', data[off:off+8])[0] print(f" 0x{code:02x} {name}: 0x{val:016x}") off += 8 elif code == 0x04: c1, c2, c3 = struct.unpack('<HHH', data[off:off+6]) print(f" 0x{code:02x} {name}: client={c1}, conn={c2}, server={c3}") off += 6 elif code == 0x06: slen = data[off]; off += 1 s = data[off:off+slen].decode('utf-8'); off += slen print(f" 0x{code:02x} {name}: \"{s}\"") elif code == 0x09: val = struct.unpack('<Q', data[off:off+8])[0] print(f" 0x{code:02x} {name}: 0x{val:016x}") off += 8 elif code == 0x0c: count = data[off]; off += 1 dbs = [] for _ in range(count): end = data.index(0x00, off) dbs.append(data[off:end].decode('utf-8')); off = end + 1 print(f" 0x{code:02x} {name}: {dbs}") elif code == 0x11: val = struct.unpack('<Q', data[off:off+8])[0] print(f" 0x{code:02x} {name}: xid={val}") off += 8 elif code == 0x12: val = struct.unpack('<H', data[off:off+2])[0] print(f" 0x{code:02x} {name}: {val}") off += 2 elif code == 0x13 or code == 0x14 or code == 0x10: val = data[off]; off += 1 print(f" 0x{code:02x} {name}: {val}") else: print(f" 0x{code:02x} {name}: (unknown encoding, stopping)") break with open('binlog.000024', 'rb') as f: positions = [276, 537, 847, 1189] labels = ["CREATE TABLE", "BEGIN (INSERT)", "BEGIN (UPDATE)", "BEGIN (DELETE)"] for pos, label in zip(positions, labels): f.seek(pos) header = f.read(19) timestamp, event_type, server_id, event_size, next_pos, flags = \ struct.unpack('<IBIIIH', header) post_header = f.read(13) thread_id, exec_time, db_len, error_code, status_len = \ struct.unpack('<IIBHH', post_header) print(f"Position {pos}: QUERY_EVENT - {label}") print(f" Event Size: {event_size} bytes, Flags: 0x{flags:04x}") print(f" Thread ID: {thread_id}, Exec Time: {exec_time}s, " f"Error Code: {error_code}") print(f" Status Variables ({status_len} bytes):") status_vars = f.read(status_len) decode_status_vars(status_vars) db_name = f.read(db_len).decode('utf-8') f.read(1) # null terminator query_len = event_size - 19 - 13 - status_len - db_len - 1 - 4 query = f.read(query_len).decode('utf-8') print(f" Database: \"{db_name}\"") print(f" Query: \"{query}\"") print()

Output:

Position 276: QUERY_EVENT - CREATE TABLE Event Size: 182 bytes, Flags: 0x0000 Thread ID: 10, Exec Time: 0s, Error Code: 0 Status Variables (55 bytes): 0x00 Q_FLAGS2_CODE: 0x00000000 0x01 Q_SQL_MODE_CODE: 0x0000000045a00020 0x06 Q_CATALOG_NZ_CODE: "std" 0x04 Q_CHARSET_CODE: client=255, conn=255, server=255 0x0c Q_UPDATED_DB_NAMES: ['presentation'] 0x11 Q_DDL_LOGGED_WITH_XID: xid=54 0x12 Q_DEFAULT_COLLATION_FOR_UTF8MB4: 255 0x13 Q_SQL_REQUIRE_PRIMARY_KEY: 0 Database: "presentation" Query: "CREATE TABLE person ( ID INT PRIMARY KEY, name VARCHAR(150) DEFAULT NULL )" Position 537: QUERY_EVENT - BEGIN (INSERT) Event Size: 83 bytes, Flags: 0x0008 Thread ID: 10, Exec Time: 0s, Error Code: 0 Status Variables (29 bytes): 0x00 Q_FLAGS2_CODE: 0x00000000 0x01 Q_SQL_MODE_CODE: 0x0000000045a00020 0x06 Q_CATALOG_NZ_CODE: "std" 0x04 Q_CHARSET_CODE: client=255, conn=255, server=255 0x12 Q_DEFAULT_COLLATION_FOR_UTF8MB4: 255 Database: "presentation" Query: "BEGIN" Position 847: QUERY_EVENT - BEGIN (UPDATE) Event Size: 92 bytes, Flags: 0x0008 Thread ID: 10, Exec Time: 0s, Error Code: 0 Status Variables (38 bytes): 0x00 Q_FLAGS2_CODE: 0x00000000 0x01 Q_SQL_MODE_CODE: 0x0000000045a00020 0x06 Q_CATALOG_NZ_CODE: "std" 0x04 Q_CHARSET_CODE: client=255, conn=255, server=255 0x09 Q_TABLE_MAP_FOR_UPDATE_CODE: 0x0000000000000001 0x12 Q_DEFAULT_COLLATION_FOR_UTF8MB4: 255 Database: "presentation" Query: "BEGIN" Position 1189: QUERY_EVENT - BEGIN (DELETE) Event Size: 83 bytes, Flags: 0x0008 Thread ID: 10, Exec Time: 0s, Error Code: 0 Status Variables (29 bytes): 0x00 Q_FLAGS2_CODE: 0x00000000 0x01 Q_SQL_MODE_CODE: 0x0000000045a00020 0x06 Q_CATALOG_NZ_CODE: "std" 0x04 Q_CHARSET_CODE: client=255, conn=255, server=255 0x12 Q_DEFAULT_COLLATION_FOR_UTF8MB4: 255 Database: "presentation" Query: "BEGIN"
Note: The binary log files used in this series (binlog.000024binlog_gtid_tag.000001, and others) are available at github.com/altmannmarcelo/presentations/tree/main/binlog.

References


What's Next?

Now that we understand how DDL statements and transaction boundaries are recorded, we're ready to look at row-based events. In the next post, we'll decode the TABLE_MAP_EVENT — the event that describes the structure of tables involved in row changes.


Next up: Part 7: TABLE_MAP_EVENT — Table Metadata for Row-Based Replication


This series is based on a presentation given at the MySQL Online Summit. The goal is to help MySQL users understand what goes under the hood of replication by manually decoding binary log files.