Detaillierte Erläuterung der Beziehung zwischen dem Binlog-Zeitstempel von MySQL und exec_time

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_timeder 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 ( ) timestampan der Anfangsposition jeder Thread-Ausführung abrufen und ihn dem Zeitpunkt der Erstellung des Ereignisobjekts zuweisen .dispatch_command()thd->start_timethd->start_timeLog_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_timestampDer erhaltene Zeitstempel ist die Übermittlungszeit und der Masterknoten original_commit_timestampist 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 timestamphandelt 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_timeund das eigentliche BEGIN-Ereignis generiert.

exec_time

Gleichzeitig exec_timewird 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 timestampist es die Startzeit des ersten Vorgangs, der in das Binlog geschrieben werden muss (z. B. Schreiben/Aktualisieren/Löschen);
  • Bei anderen Ereignissen timestampist es die Startzeit, zu der die SQL-Anweisung ausgeführt wird.
  • immediate_commit_timestamp/original_commit_timestampDas 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_timestampRufen Sie den Zeitstempel der Übermittlungszeit des Slave-Knotens ab. Erhalten original_commit_timestampvom 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 timestampkommt das BEGIN-Ereignis des Hauptknotens timestamp. Wenn es tatsächlich ausgeführt wird, wird das BEGIN-Ereignis abgerufen timestampund zugewiesen thd->start_time/thd->user_time. Wenn Sie ein Ereignisobjekt von einem Knoten generieren, rufen Sie einfach weiterhin thd->start_timeden Zeitstempel von ab.

exec_time

Dann exec_timeerhält der Slave-Knoten immer noch den neuesten Zeitstempel im Prozess der Generierung des BEGIN-Ereignisses timestamp(beachten Sie, dass timestampdie 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 timestampdie Startzeit des letzten Änderungsvorgangs des Masterknotens;
  • Das GTID-Ereignis original_commit_timestampkommt vom Masterknoten und immediate_commit_timestampist 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_timeim 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
Wie viel Umsatz kann ein unbekanntes Open-Source-Projekt bringen? Das chinesische KI-Team von Microsoft hat zusammengepackt und ist mit Hunderten von Menschen in die USA gegangen. Huawei gab offiziell bekannt, dass Yu Chengdongs Jobwechsel an der „FFmpeg-Säule der Schande“ festgenagelt wurden vor, aber heute muss er uns danken – Tencent QQ Video rächt seine vergangene Demütigung? Die Open-Source-Spiegelseite der Huazhong University of Science and Technology ist offiziell für den externen Zugriff geöffnet. Bericht: Django ist immer noch die erste Wahl für 74 % der Entwickler. Zed-Editor hat Fortschritte bei der Linux-Unterstützung gemacht brachte die Nachricht: Nachdem er von einem Untergebenen herausgefordert wurde, wurde der technische Leiter wütend und unhöflich, wurde entlassen und schwanger. Die Mitarbeiterin von Alibaba Cloud veröffentlicht offiziell Tongyi Qianwen 2.5. Microsoft spendet 1 Million US-Dollar an die Rust Foundation
{{o.name}}
{{m.name}}

Ich denke du magst

Origin my.oschina.net/actiontechoss/blog/11112538