Detaillierte Erläuterung der Beziehung zwischen Binlog-Zeitstempel und exec_time.
Autor: Li Xichao, DBA der Jiangsu Commercial Bank, verantwortlich für den Betrieb, die Wartung und den Aufbau von Datenbanken und Middleware. Gut in MySQL, Python, Oracle und liebt Radfahren, Technologieforschung und Austausch.
Der von der Aikeson-Open-Source-Community erstellte Originalinhalt darf nicht ohne Genehmigung verwendet werden. Bitte wenden Sie sich an den Herausgeber und geben Sie die Quelle für den Nachdruck an.
Dieser Artikel umfasst etwa 2.000 Wörter und die Lektüre dauert voraussichtlich 8 Minuten.
Überblick
Als kürzlich ein System getestet wurde, wurde festgestellt, dass es bei der Master-Slave-Synchronisation zu einer Verzögerung kam, und die Ursache der Verzögerung wurde durch Binlog bestätigt. Nachdem ich es mit dem Befehl mysqlbinlog analysiert hatte, stellte ich fest, dass die darin enthaltenen Informationen „etwas vage, aber nicht verständlich“ waren.
Zum Beispiel für den folgenden Binlog-Snippet:
# at 449880
#240430 18:38:49 server id 345 end_log_pos 449967 CRC32 0xb3e8a02a GTID last_committed=13 sequence_number=14 rbr_only=yes original_committed_timestamp=1714473533138376 immediate_commit_timestamp=1714473539246294 transaction_length=446792
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
# original_commit_timestamp=1714473533138376 (2024-04-30 18:38:53.138376 CST)
# immediate_commit_timestamp=1714473539246294 (2024-04-30 18:38:59.246294 CST)
/*!80001 SET @@session.original_commit_timestamp=1714473533138376*//*!*/;
/*!80014 SET @@session.original_server_version=80027*//*!*/;
/*!80014 SET @@session.immediate_server_version=80027*//*!*/;
SET @@SESSION.GTID_NEXT= 'c0ac4587-6046-11ee-9fa7-001c42c92a7b:44'/*!*/;
# at 449967
#240430 18:38:16 server id 345 end_log_pos 450039 CRC32 0x0c7cb74e Query thread_id=16 exec_time=37 error_code=0
SET TIMESTAMP=1714473496/*!*/;
BEGIN
/*!*/;
/*!*/;
# at 450039
#240430 18:38:16 server id 345 end_log_pos 450098 CRC32 0xf9a84808 Table_map: `testdb`.`tb3` mapped to number 110
# at 450098
#240430 18:38:16 server id 345 end_log_pos 458309 CRC32 0xad84e9b0 Write_rows: table id 110
...
# at 896439
#240430 18:38:46 server id 345 end_log_pos 896498 CRC32 0x5cd7cd3b Table_map: `testdb`.`tb3` mapped to number 110
# at 896498
#240430 18:38:46 server id 345 end_log_pos 896540 CRC32 0x21b77031 Write_rows: table id 110 flags: STMT_END_F
...
### INSERT INTO `testdb`.`tb3`
### SET
### @1=131060 /* INT meta=0 nullable=0 is_null=0 */
### @2='c' /* VARSTRING(80) meta=80 nullable=1 is_null=0 */
# at 896540
#240430 18:38:49 server id 345 end_log_pos 896599 CRC32 0x6d6bf911 Table_map: `testdb`.`tb3` mapped to number 110
# at 896599
#240430 18:38:49 server id 345 end_log_pos 896641 CRC32 0xccd2fbb1 Write_rows: table id 110 flags: STMT_END_F
...
### INSERT INTO `testdb`.`tb3`
### SET
### @1=131061 /* INT meta=0 nullable=0 is_null=0 */
### @2='c' /* VARSTRING(80) meta=80 nullable=1 is_null=0 */
# at 896641
#240430 18:38:49 server id 345 end_log_pos 896672 CRC32 0xadb14b9d Xid = 85
Aus dem obigen Binlog können wir (P1) wissen:
#240430 18:38:16 执行 begin 开启了事务 (为便于表述,将时间字段名为timestamp)
#240430 18:38:16 执行了 tb3的insert 操作
#240430 18:38:46 执行了 tb3的insert 操作
#240430 18:38:49 执行了 tb3的insert 操作
#240430 18:38:49 执行了commit操作
Zusätzlich (P2):
original_commit_timestamp=2024-04-30 18:38:53
immediate_commit_timestamp=2024-04-30 18:38:59
exec_time=37
Bezüglich P2-Informationen werden folgende Fragen gestellt:
- F1: Was bedeuten die Felder in P2? Wie wird es berechnet?
- F2: Welche Beziehung besteht zwischen den Feldern von P2 und dem von P1 gesehenen Zeitstempel?
- F4: Wie wird der Zeitstempel in P1 erhalten? Besonders in einer Master-Slave-Umgebung
Zu diesem Zweck wird durch Testverifizierung und Quellcodeanalyse exec_time
der Ursprung gemeinsamer Ereigniszeiten und im Binlog analysiert und die Beziehung zwischen Feldern zusammengefasst.
Die folgende Analyse basiert auf MySQL 8.0 und die Felder können in verschiedenen Versionen unterschiedlich sein.
Binlog-Protokoll des Masterknotens
1. GTID-Ereignis
Zeitstempel
Für den Hauptknoten: Wenn keine speziellen Anweisungen vorliegen, muss das Ereignis den neuesten Zeitstempel ( ) timestamp
an der Anfangsposition jeder Thread-Ausführung abrufen und ihn dem Zeitpunkt der Erstellung des Ereignisobjekts zuweisen .dispatch_command()
thd->start_time
thd->start_time
Log_event::common_header->when
Die Hauptstapelinformationen lauten wie folgt:
|-handle_connection (./sql/conn_handler/connection_handler_per_thread.cc:302)
|-do_command (./sql/sql_parse.cc:1343)
|-dispatch_command (./sql/sql_parse.cc:1922)
// 设置 thd->start_time
|-thd->set_time()
|-my_micro_time_to_timeval(start_utime, &start_time)
|-dispatch_sql_command (./sql/sql_parse.cc:5135)
|-mysql_execute_command (./sql/sql_parse.cc:3518)
|-Sql_cmd_dml::execute (./sql/sql_select.cc:579)
……
|-Table_map_log_event the_event(this, table, table->s->table_map_id,is_transactional)
……
|-Rows_log_event *const ev = new RowsEventT(this, table, table->s->table_map_id, )
……
|-Xid_log_event end_evt(thd, xid)
unmittelbarer_commit_timestamp/original_commit_timestamp
immediate_commit_timestamp
Der erhaltene Zeitstempel ist die Übermittlungszeit und der Masterknoten original_commit_timestamp
ist gleich immediate_commit_timestamp
.
|-error = trx_cache.flush(thd, &trx_bytes, wrote_xid)
|-Transaction_ctx *trn_ctx = thd->get_transaction()
|-trn_ctx->sequence_number = mysql_bin_log.m_dependency_tracker.step()
|-if (trn_ctx->last_committed == SEQ_UNINIT): trn_ctx->last_committed = trn_ctx->sequence_number - 1
|-if (!error): if ((error = mysql_bin_log.write_transaction(thd, this, &writer)))
|-int64 sequence_number, last_committed
|-m_dependency_tracker.get_dependency(thd, sequence_number, last_committed)
|-thd->get_transaction()->last_committed = SEQ_UNINIT
|-ulonglong immediate_commit_timestamp = my_micro_time()
//|-ulonglong original_commit_timestamp = thd->variables.original_commit_timestamp
|-ulonglong original_commit_timestamp = immediate_commit_timestamp
|-uint32_t trx_immediate_server_version = do_server_version_int(::server_version)
|-Gtid_log_event gtid_event(thd, cache_data->is_trx_cache(), last_committed, sequence_number,
cache_data->may_have_sbr_stmts(), original_commit_timestamp,
immediate_commit_timestamp, trx_original_server_version,
trx_immediate_server_version)
2. BEGIN-Ereignis
Zeitstempel
Hinweis: Beim BEGIN-Ereignis des Masterknotens timestamp
handelt es sich nicht um den Zeitstempel der Ausführung von BEGIN, sondern um den ersten Änderungsvorgang. Nach Abschluss der Änderung der ersten Datenzeile in der InnoDB-Ebene wird das Table_map-Ereignis generiert und geschrieben. Wenn vor dem Generieren des Table_map-Ereignisses der Binlog-Cache der gesamten Transaktion zu diesem Zeitpunkt leer ist, wird der Vorgang sofort abgerufen thd->start_time
und das eigentliche BEGIN-Ereignis generiert.
exec_time
Gleichzeitig exec_time
wird es für den Masterknoten durch Abrufen des neuesten Zeitstempels - BEGIN-Ereignis im Prozess der Generierung des BEGIN-Ereignisses - abgerufen timestamp
.
exec_time = A - B
- A: Der Zeitpunkt, zu dem das BEGIN-Ereignis generiert wird, nachdem die erste geänderte SQL ausgeführt und die erste Zeilenänderungsoperation (Schreiben/Aktualisieren/Löschen) abgeschlossen wurde.
- B: Die Startausführungszeit des ersten geänderten SQL (thd->start_time)
Der interne Stapel und die Ausführungssequenz sind wie folgt:
3. Table_map-Ereignis
4. Ereignis schreiben
5. Xid-Ereignis
6. Zusammenfassung des Masterknotens
- Zusätzlich zum BEGIN-Ereignis
timestamp
ist es die Startzeit des ersten Vorgangs, der in das Binlog geschrieben werden muss (z. B. Schreiben/Aktualisieren/Löschen); - Bei anderen Ereignissen
timestamp
ist es die Startzeit, zu der die SQL-Anweisung ausgeführt wird. immediate_commit_timestamp/original_commit_timestamp
Das ist der Zeitstempel bei der Übermittlung;- exec_time = A - B
- A: Der Zeitpunkt, zu dem das BEGIN-Ereignis generiert wird, nachdem die erste geänderte SQL ausgeführt und die erste Zeilenänderungsoperation (Schreiben/Aktualisieren/Löschen) abgeschlossen wurde.
- B: Die Startausführungszeit des ersten geänderten SQL (thd->start_time)
Binlog-Protokoll des Slave-Knotens
1. GTID-Ereignis
Zeitstempel
Auf dem Slave-Knoten: Für GTID-Ereignisse erhält MySQL beim Parsen des Ereignisses nicht den Zeitstempel des GTID/XID-Ereignisses des Masterknotens, sodass es den Zeitstempel des vorherigen Vorgangs der Transaktion „erbt“. Die Zeitstempel aller Änderungsvorgänge auf dem Slave-Knoten stammen aus dem Zeitstempel, zu dem der Master-Knoten den Vorgang ausführt. Daher ist die Zeit des GTID/XID-Ereignisses des Slave-Knotens der Zeitstempel des letzten Änderungsvorgangs des Master-Knotens.
unmittelbarer_commit_timestamp/original_commit_timestamp
immediate_commit_timestamp
Rufen Sie den Zeitstempel der Übermittlungszeit des Slave-Knotens ab. Erhalten original_commit_timestamp
vom GTID-Ereignis original_commit_timestamp
, das heißt, der Hauptknoten sendet den Vorgang timestamp
.
Die Hauptstapelinformationen lauten wie folgt:
|-handle_slave_worker (./sql/rpl_replica.cc:5891)
|-slave_worker_exec_job_group (./sql/rpl_rli_pdb.cc:2549)
|-Slave_worker::slave_worker_exec_event (./sql/rpl_rli_pdb.cc:1760)
|-Xid_apply_log_event::do_apply_event_worker (./sql/log_event.cc:6179)
|-Xid_log_event::do_commit (./sql/log_event.cc:6084)
|-trans_commit (./sql/transaction.cc:246)
|-ha_commit_trans (./sql/handler.cc:1765)
|-MYSQL_BIN_LOG::commit (./sql/binlog.cc:8170)
|-MYSQL_BIN_LOG::ordered_commit (./sql/binlog.cc:8789)
|-MYSQL_BIN_LOG::process_flush_stage_queue (./sql/binlog.cc:8326)
|-MYSQL_BIN_LOG::flush_thread_caches (./sql/binlog.cc:8218)
|-binlog_cache_mngr::flush (./sql/binlog.cc:1099)
|-binlog_cache_data::flush (./sql/binlog.cc:2098)
|-MYSQL_BIN_LOG::write_transaction (./sql/binlog.cc:1586)
// 生成并写入 GTID event
|-ulonglong immediate_commit_timestamp = my_micro_time()
|-if (original_commit_timestamp == UNDEFINED_COMMIT_TIMESTAMP){...}
|-Gtid_log_event gtid_event(thd, cache_data->is_trx_cache(), last_committed, sequence_number,
cache_data->may_have_sbr_stmts(), original_commit_timestamp, immediate_commit_timestamp, trx_original_server_version,
trx_immediate_server_version)
offiziell
unmittelbarer_commit_timestamp - original_commit_timestamp = A + B + C
- A = Die Zeit, die der Master-Knoten benötigt, um Binlog an den Slave-Knoten zu übertragen
- B = Die Zeit, die zum Wiedergeben des Binlogs vom Slave-Knoten benötigt wird
- C = Synchronisationsverzögerung/Unterbrechungszeit
2. BEGIN-Ereignis
Zeitstempel
Hier timestamp
kommt das BEGIN-Ereignis des Hauptknotens timestamp
. Wenn es tatsächlich ausgeführt wird, wird das BEGIN-Ereignis abgerufen timestamp
und zugewiesen thd->start_time/thd->user_time
. Wenn Sie ein Ereignisobjekt von einem Knoten generieren, rufen Sie einfach weiterhin thd->start_time
den Zeitstempel von ab.
exec_time
Dann exec_time
erhält der Slave-Knoten immer noch den neuesten Zeitstempel im Prozess der Generierung des BEGIN-Ereignisses timestamp
(beachten Sie, dass timestamp
die Startausführungszeit des geänderten SQL vom Master-Knoten abgerufen wird).
Die Hauptstapelinformationen lauten wie folgt:
|-handle_slave_worker (./sql/rpl_replica.cc:5891)
|-slave_worker_exec_job_group (./sql/rpl_rli_pdb.cc:2549)
|-Slave_worker::slave_worker_exec_event (./sql/rpl_rli_pdb.cc:1760)
|-Log_event::do_apply_event_worker (./sql/log_event.cc:1083)
|-Query_log_event::do_apply_event (./sql/log_event.cc:4443)
|-Query_log_event::do_apply_event (./sql/log_event.cc:4606)
// 设置 user_time=start_time=ev.common_header->when
|-thd->set_time(&(common_header->when))
// query_arg="BEGIN"
|-thd->set_query(query_arg, q_len_arg)
...
offiziell
exec_time = A + B + C + D
- A = Masterknoten, die gesamte Transaktionszeit
- B = Binlog-Übertragungszeit
- C = Synchronisationsverzögerung/Unterbrechungszeit (wahrscheinlich – schwerwiegend)
- D = Vervollständigen Sie die erste Zeile der Datenänderung vom Knoten
original_commit_timestamp – timestamp = of begin event gibt den tatsächlichen Zeitverbrauch der gesamten Transaktion auf dem Masterknoten an ([Main-First-Modifikation] bis [Main-Commit-Start]).
3. Table_map-Ereignis
4. Ereignis schreiben
5. Xid-Ereignis
6. Vom Knotenabschnitt
- Mit Ausnahme des GTID/XID-Ereignisses stammen die Zeitstempel anderer Ereignisse von den Ereignissen des Masterknotens;
- Das GTID/XID-Ereignis ist
timestamp
die Startzeit des letzten Änderungsvorgangs des Masterknotens; - Das GTID-Ereignis
original_commit_timestamp
kommt vom Masterknoten undimmediate_commit_timestamp
ist der neueste Zeitstempel; - exec_time = A - B
- A = Der letzte Zeitstempel des vom Knoten generierten BEGIN-Ereignisses
- B = Der Masterknoten beginnt mit der Ausführung der ersten DML-Operation
Abschluss
Zu diesem Zeitpunkt wurden die Zeitstempel und Informationen im Binlog exec_time
im Wesentlichen aussortiert. Interessierte Freunde können zum Anfang des Artikels zurückkehren und sehen, ob es Antworten auf Q1-Q3 gibt.
Abschließend wird den Lesern empfohlen, mehrere Fälle zu simulieren, um ein tieferes Verständnis der relevanten Felder zu erlangen und sich bei der Analyse von Master-Slave-Synchronisationsproblemen mit binlog wohler zu fühlen.
Die oben genannten Informationen dienen nur der Kommunikation. Das Niveau des Autors ist begrenzt. Wenn es Mängel gibt, können Sie diese gerne im Kommentarbereich mitteilen.
Weitere technische Artikel finden Sie unter: https://opensource.actionsky.com/
Über SQLE
SQLE ist eine umfassende SQL-Qualitätsmanagementplattform, die die SQL-Prüfung und -Verwaltung von der Entwicklung bis zur Produktionsumgebung abdeckt. Es unterstützt gängige Open-Source-, kommerzielle und inländische Datenbanken, bietet Prozessautomatisierungsfunktionen für Entwicklung, Betrieb und Wartung, verbessert die Online-Effizienz und verbessert die Datenqualität.
SQLE erhalten
Typ | Adresse |
---|---|
Repository | https://github.com/actiontech/sqle |
dokumentieren | https://actiontech.github.io/sqle-docs/ |
Neuigkeiten veröffentlichen | https://github.com/actiontech/sqle/releases |
Entwicklungsdokumentation für das Datenaudit-Plug-in | https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse |