Ursachenanalyse und Lösung des E/A-Fehlers beim Löschen einer MySQL-Tabelle

Ursachenanalyse und Lösung des E/A-Fehlers beim Löschen einer MySQL-Tabelle

Problemphänomen

Ich habe kürzlich Sysbench verwendet, um MySQL zu testen. Da der Test lange dauerte, habe ich ein Skript geschrieben, das im Hintergrund in der Reihenfolge Vorbereiten->Ausführen->Bereinigen ausgeführt wurde. Nach dem Lauf habe ich das Protokoll überprüft und ein Problem festgestellt. Im Fehlerprotokoll des MySQL-Dienstes waren mehrere Fehler ähnlich den folgenden enthalten:

[FEHLER] InnoDB: Versuch, I/O auf einen Tablespace auszuführen, der nicht existiert. I/O-Typ: Lesen, Seite: [Seiten-ID: Space=32, Seitennummer=57890], I/O-Länge: 16384 Bytes.

Es sieht nach einem E/A-Fehler aus, aber der MySQL-Prozess ist nicht abgestürzt und der Sysbench-Client hat keinen Fehler gemeldet.

Problemerkennungsprozess

Anhand der Zeitaufzeichnung des Fehlers und des Vergleichs der Zeitpunkte der einzelnen Phasen der Skriptausgabe wurde ermittelt, dass der zu diesem Zeitpunkt vom Skript ausgeführte Befehl folgender war:

sysbench --tables=100 --table-size=4000000 --threads=50 --mysql-db=sbtest --time=300 oltp_delete-Bereinigung

Ich habe den Testfall erneut manuell ausgeführt, aber die gleiche Situation ist nicht erneut aufgetreten. Allerdings tritt diese Fehlermeldung bei der Ausführung des Skripts immer noch auf. Der erste Verdacht liegt darin, dass dieses Problem durch die kurze Zeitspanne zwischen Ausführung und Bereinigung ausgelöst wird. Da die Ausführung von 100 G Daten lange dauert und die Wiederholungskosten hoch sind, versuchen Sie zunächst, die Menge der Anwendungsfalldaten zu reduzieren. Ändern Sie --table-size=4000000 in 2000000. Bei der Ausführung des Skripts wird dieses Problem nicht mehr ausgelöst. Ändern Sie abschließend --table-size=3000000, um den Auslöser zu stabilisieren und die Reproduktionszeit zu verkürzen. Um zu bestätigen, dass das Problem aufgrund des langen Intervalls nicht reproduziert werden konnte, wurde das Skript so geändert, dass es zwischen der Ausführungs- und der Bereinigungsphase 10 Sekunden lang pausiert. Tatsächlich wurde diese Fehlermeldung nicht ausgelöst. Wenn Sie es auf einen Ruhezustand von 5 Sekunden ändern, kann es immer noch ausgelöst werden, aber die Anzahl der gemeldeten Fehler wurde reduziert.

Problemuntersuchung

Beim Betrachten des Codes der entsprechenden Version von mysql5.7.22 stellte ich fest, dass dieser Fehler nur an einer Stelle auftritt: in der Funktion fil_io() in Zeile 5578 der Datei fil0fil.cc. Verwenden Sie das GDB-Debugging direkt, fügen Sie an dieser Stelle einen Haltepunkt hinzu und führen Sie ein reproduzierbares Skript aus, um den folgenden Stapel zu erhalten:

(gdb) bt
#0 fil_io (Typ=..., Sync=Sync@Eintrag=false, Seiten-ID=..., Seitengröße=..., Byte-Offset=Byte-Offset@Eintrag=0, Länge=16384, Puffer=0x7f9ead544000, Nachricht=Nachricht@Eintrag=0x7f9ea8ce9c78) bei mysql-5.7.22/storage/innobase/fil/fil0fil.cc:5580
#1 0x00000000010f99fa in buf_read_page_low (Fehler=0x7f9ddaffc72c, Sync=<optimiert raus>, Typ=0, Modus=<optimiert raus>, Seiten-ID=..., Seitengröße=..., unzip=true) bei mysql-5.7.22/storage/innobase/buf/buf0rea.cc:195
#2 0x00000000010fc5fa in buf_read_ibuf_merge_pages (sync=sync@entry=false, space_ids=space_ids@entry=0x7f9ddaffc7e0, page_nos=page_nos@entry=0x7f9ddaffc7a0, n_stored=2) bei mysql-5.7.22/storage/innobase/buf/buf0rea.cc:834
#3 0x0000000000f3a86c in ibuf_merge_pages (n_pages=n_pages@entry=0x7f9ddaffce30, sync=sync@entry=false) bei mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2552
#4 0x0000000000f3a94a in ibuf_merge (sync=false, sync=false, n_pages=0x7f9ddaffce30) bei mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2656
#5 ibuf_merge_in_background (voll=voll@eintrag=false) bei mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2721
#6 0x000000000102bcf4 in srv_master_do_active_tasks () bei mysql-5.7.22/storage/innobase/srv/srv0srv.cc:2132
#7 srv_master_thread (arg=<optimized out>) bei mysql-5.7.22/storage/innobase/srv/srv0srv.cc:2383
#8 0x00007fa003eeddc5 in start_thread () von /lib64/libpthread.so.0
#9 0x00007fa002aab74d in Klon () von /lib64/libc.so.6

Offensichtlich ist dies der Hintergrund-Thread, der den Einfügepuffer-Zusammenführungsvorgang durchführt. Zu diesem Zeitpunkt wird festgestellt, dass space->stop_new_ops wahr ist, was bedeutet, dass der Bereich, zu dem die zu verarbeitende Seite gehört, gelöscht wird. Warum sollten Sie einen Speicherplatz bearbeiten, der gelöscht wird? Hierzu ist eine Untersuchung der Einfügepufferfunktion, des Einfügepufferzusammenführungsprozesses und des Tabellenlöschprozesses erforderlich.

Hintergrundwissen zum Einfügepuffer

Der Einfügepuffer ist eine spezielle Datenstruktur (B+-Baum), die Änderungen zwischenspeichert, wenn sich die Hilfsindexseite nicht im Pufferpool befindet, und sie später zusammenführt, wenn die Seite durch andere Lesevorgänge in den Pufferpool geladen wird. Als MySQL diese Funktion erstmals einführte, konnte es nur Einfügevorgänge zwischenspeichern, daher hieß es Einfügepuffer. Jetzt können diese Vorgänge INSERT, UPDATE oder DELETE (DML) sein, daher heißt es Änderungspuffer (in diesem Artikel wird es immer noch als Einfügepuffer beschrieben), aber ibuf wird immer noch als Bezeichner im Quellcode verwendet. Diese Funktion speichert mehrere Aktualisierungen derselben Seite im Cache und führt sie zu einem einmaligen Aktualisierungsvorgang zusammen. Dadurch wird der IO-Aufwand reduziert und zufälliger IO-Aufwand in sequentieller IO-Aufwand umgewandelt. Dadurch können Leistungsverluste durch zufälligen IO-Aufwand vermieden und die Schreibleistung der Datenbank verbessert werden.

Zugehörige Einfügepuffer-Zusammenführungslogik

Wenn die Pufferseite in den Pufferpool gelesen wird, wird eine Einfügepufferung durchgeführt. Der Zusammenführungsprozess findet in mehreren Hauptszenarien statt:

  • Wenn eine Seite in den Pufferpool gelesen wird, wird der ibuf nach Abschluss des Lesevorgangs zusammengeführt und dann ist die Seite verfügbar.
  • Der Zusammenführungsvorgang wird als Hintergrundaufgabe ausgeführt. Der Parameter innodb_io_capacity kann die Obergrenze der Seitenanzahl in jedem Zusammenführungsprozess der InnoDB-Hintergrundaufgabe festlegen.
  • Wenn während der Wiederherstellung nach einem Systemabsturz eine Indexseite in den Pufferpool gelesen wird, wird ein Insert Buffer Merge der entsprechenden Seite durchgeführt.
  • Der Einfügepuffer ist persistent und wird durch einen Systemabsturz nicht ungültig. Nach dem Neustart wird der Zusammenführungsvorgang für den Einfügepuffer wieder normal ausgeführt.
  • Sie können --innodb-fast-shutdown=0 verwenden, um eine vollständige Zusammenführung von ibufs zu erzwingen, wenn der Server heruntergefahren wird.

Unser Problem dieses Mal gehört eindeutig zum zweiten Fall. Der InnoDB-Hauptthread (svr_master_thread) führt jede Sekunde aktiv einen Zusammenführungsvorgang im Einfügepuffer aus. Stellen Sie zunächst fest, ob in der letzten Sekunde eine Aktivität auf dem Server stattgefunden hat (z. B. Einfügen von Tupeln in Seiten, Zeilenoperationen in Undo-Tabellen usw.). Wenn dies der Fall ist, beträgt die maximale Anzahl der zusammenzuführenden Seiten 5 % der innodb_io_capacity-Einstellung. Wenn nicht, ist die maximale Anzahl der zusammenzuführenden Seiten der durch innodb_io_capacity festgelegte Wert.

Der Hauptprozess zum Zusammenführen des InnoDB-Hauptthreads (svr_master_thread) ist wie folgt:

  • Der Hauptthread liest die Seitenzahl und die Leerzeichennummer aus dem Blattknoten des ibuf-Baums und zeichnet sie in einem binären Array auf (entsperrt).
  • Der Hauptthread prüft, ob sich der Speicherplatz im Tupel im Tabellenbereichscache befindet. Wenn nicht, bedeutet dies, dass er gelöscht wurde, und löscht den entsprechenden ibuf-Datensatz.
  • Der Hauptthread bestimmt, ob ein asynchroner Lesevorgang für einen zu löschenden Speicherplatz ausgeführt werden soll. Wenn dies der Fall ist, wird ein Fehler gemeldet und der entsprechende Ibuf-Datensatz gelöscht. Anschließend geht der Prozess zu Prozess 2 über, um mit der Beurteilung des nächsten Array-Elements fortzufahren.
  • Wenn alles normal ist, sendet der Hauptthread eine asynchrone E/A-Anforderung und liest asynchron die Indexseite, die zusammengeführt werden muss.
  • Der E/A-Handler-Thread führt einen Zusammenführungsvorgang aus, nachdem er die abgeschlossene asynchrone E/A erhalten hat.
  • Rufen Sie beim Durchführen einer Zusammenführung fil_space_acquire auf, um space->n_pending_ops zu erhöhen. Vermeiden Sie gleichzeitige Löschvorgänge.
  • Nach Abschluss der Ausführung wird fil_space_release aufgerufen, um space->n_pending_ops zu dekrementieren.

Logik zum Löschen verknüpfter Tabellen

  • Sperren Sie fil_system->mutex, setzen Sie sp->stop_new_ops = true, markieren Sie den Speicherplatz als gelöscht und verbieten Sie neue Vorgänge darauf, entsperren Sie dann fil_system->mutex;
  • Sperren Sie fil_system->mutex, prüfen Sie space->n_pending_ops und entsperren Sie fil_system->mutex. Wenn der erkannte Wert größer als 0 ist, bedeutet dies, dass noch abhängige Vorgänge vorhanden sind, die nicht abgeschlossen wurden. Versuchen Sie es nach 20 ms Ruhezeit erneut.
  • Sperren Sie fil_system->mutex, überprüfen Sie space->n_pending_flushes und (*node)->n_pending und entsperren Sie fil_system->mutex. Wenn der Erkennungswert größer als 0 ist, bedeutet dies, dass noch abhängige E/A-Vorgänge vorhanden sind, die nicht abgeschlossen wurden, und das System versucht es nach einer Ruhepause von 20 ms erneut.
  • An diesem Punkt wird davon ausgegangen, dass keine widersprüchlichen Vorgänge vorliegen und alle schmutzigen Seiten gelöscht oder alle Seiten des angegebenen Tabellenbereichs gelöscht sind.
  • Löschen Sie die Datensätze des angegebenen Bereichs aus dem Tabellenbereichscache.
  • Löschen Sie die entsprechende Datendatei.

Abschluss

Die Situation ist sehr klar. Es gibt keine Sperre, die den gegenseitigen Ausschluss zwischen dem Prozess des Hauptthreads zum Abrufen von Ibufs (Speicherplatz, Seite) und dem Prozess zum Ausführen des Löschvorgangs gewährleistet. Nur der Zusammenführungsvorgang und der Löschvorgang nach Abschluss der asynchronen E/A schließen sich gegenseitig aus. Wenn der Hintergrund-Thread ibuf merge startet und Schritt 2 der Erkennung ausgeführt hat, aber Schritt 3 der Erkennung noch nicht ausgeführt hat, und der Benutzer-Thread mit dem Löschen der Tabelle beginnt und das Flag stop_new_ops setzt, aber Schritt 5 zum Löschen des Tablespace-Cache noch nicht ausgeführt hat, wird diese Fehlermeldung angezeigt. Die Interaktion zwischen den beiden Threads wird in der folgenden Abbildung dargestellt:

Wenn nichts Unerwartetes passiert, muss es einen Thread geben, der den Löschvorgang der entsprechenden Tabelle ausführt, wenn der Haltepunkt erreicht wird. Tatsächlich können wir den folgenden Stapel finden:

Thread 118 (Thread 0x7f9de0111700 (LWP 5234)):
#0 0x00007fa003ef1e8e in pthread_cond_broadcast@@GLIBC_2.3.2 () von /lib64/libpthread.so.0
#1 0x0000000000f82f41 im Broadcast (dies=0xd452ef8) bei mysql-5.7.22/storage/innobase/os/os0event.cc:184
#2 gesetzt (dies=0xd452ef8) bei mysql-5.7.22/storage/innobase/os/os0event.cc:75
#3 os_event_set (Ereignis=0xd452ef8) bei mysql-5.7.22/storage/innobase/os/os0event.cc:483
#4 0x00000000010ec8a4 im Signal (dieses = <optimized out>) bei mysql-5.7.22/storage/innobase/include/ut0mutex.ic:105
#5 beenden (dies = <optimized out>) bei mysql-5.7.22/storage/innobase/include/ib0mutex.h:690
#6 beenden (dies = <optimized out>) bei mysql-5.7.22/storage/innobase/include/ib0mutex.h:961
#7 buf_flush_yield (bpage=<optimiert aus>, buf_pool=<optimiert aus>) bei mysql-5.7.22/storage/innobase/buf/buf0lru.cc:405
#8 buf_flush_try_yield (verarbeitet=<optimiert>, bpage=<optimiert>, buf_pool=<optimiert>) bei mysql-5.7.22/storage/innobase/buf/buf0lru.cc:449
#9 buf_flush_or_remove_pages (trx=<optimiert aus>, flush=<optimiert aus>, observer=<optimiert aus>, id=<optimiert aus>, buf_pool=<optimiert aus>) bei mysql-5.7.22/storage/innobase/buf/buf0lru.cc:632
#10 buf_flush_dirty_pages (buf_pool=<optimiert aus>, id=<optimiert aus>, observer=<optimiert aus>, flush=<optimiert aus>, trx=<optimiert aus>) bei mysql-5.7.22/storage/innobase/buf/buf0lru.cc:693
#11 0x00000000010f6de7 in buf_LRU_remove_pages (trx=0x0, buf_remove=BUF_REMOVE_FLUSH_NO_WRITE, id=55, buf_pool=0x31e55e8) bei mysql-5.7.22/storage/innobase/buf/buf0lru.cc:893
#12 buf_LRU_flush_or_remove_pages (id=id@entry=55, buf_remove=buf_remove@entry=BUF_REMOVE_FLUSH_NO_WRITE, trx=trx@entry=0x0) bei mysql-5.7.22/storage/innobase/buf/buf0lru.cc:951
#13 0x000000000114e488 in fil_delete_tablespace (id=id@entry=55, buf_remove=buf_remove@entry=BUF_REMOVE_FLUSH_NO_WRITE) bei mysql-5.7.22/storage/innobase/fil/fil0fil.cc:2800
#14 0x0000000000fe77bd in row_drop_single_table_tablespace (trx=0x0, is_encrypted=false, is_temp=false, Dateipfad=0x7f9d7c209f38 "./sbtest/sbtest25.ibd", Tabellenname=0x7f9d7c209dc8 "sbtest/sbtest25", Space-ID=55) bei mysql-5.7.22/storage/innobase/row/row0mysql.cc:4189
#15 row_drop_table_for_mysql (name=name@entry=0x7f9de010e020 "sbtest/sbtest25", trx=trx@entry=0x7f9ff9515750, drop_db=<optimiert>, nonatomic=<optimiert>, nonatomic@entry=true, handler=handler@entry=0x0) bei mysql-5.7.22/storage/innobase/row/row0mysql.cc:4741
#16 0x0000000000f092f3 in ha_innobase::delete_table (dies=<optimized out>, Name=0x7f9de010f5e0 "./sbtest/sbtest25") bei mysql-5.7.22/storage/innobase/handler/ha_innodb.cc:12539
#17 0x0000000000801a30 in ha_delete_table (thd=thd@entry=0x7f9d7c1f6910, table_type=table_type@entry=0x2ebd100, path=path@entry=0x7f9de010f5e0 "./sbtest/sbtest25", db=db@entry=0x7f9d7c00e560 "sbtest", alias=0x7f9d7c00df98 "sbtest25", generate_warning=generate_warning@entry=true) bei mysql-5.7.22/sql/handler.cc:2586
#18 0x0000000000d0a6af in mysql_rm_table_no_locks (thd=thd@entry=0x7f9d7c1f6910, tables=tables@entry=0x7f9d7c00dfe0, if_exists=true, drop_temporary=false, drop_view=drop_view@entry=false, dont_log_query=dont_log_query@entry=false) bei mysql-5.7.22/sql/sql_table.cc:2546
#19 0x0000000000d0ba58 in mysql_rm_table (thd=thd@entry=0x7f9d7c1f6910, tables=tables@entry=0x7f9d7c00dfe0, if_exists=<optimized out>, drop_temporary=<optimized out>) bei mysql-5.7.22/sql/sql_table.cc:2196
#20 0x0000000000c9d90b in mysql_execute_command (thd=thd@entry=0x7f9d7c1f6910, first_level=first_level@entry=true) bei mysql-5.7.22/sql/sql_parse.cc:3589
#21 0x0000000000ca1edd in mysql_parse (thd=thd@entry=0x7f9d7c1f6910, parser_state=parser_state@entry=0x7f9de01107a0) bei mysql-5.7.22/sql/sql_parse.cc:5582
#22 0x0000000000ca2a20 im dispatch_command (thd=thd@entry=0x7f9d7c1f6910, com_data=com_data@entry=0x7f9de0110e00, Befehl=COM_QUERY) bei mysql-5.7.22/sql/sql_parse.cc:1458
#23 0x0000000000ca4377 in do_command (thd=thd@entry=0x7f9d7c1f6910) bei mysql-5.7.22/sql/sql_parse.cc:999
#24 0x0000000000d5ed00 in handle_connection (arg=arg@entry=0x10b8e910) bei mysql-5.7.22/sql/conn_handler/connection_handler_per_thread.cc:300
#25 0x0000000001223d74 in pfs_spawn_thread (Argument = 0x10c48f40) bei mysql-5.7.22/storage/perfschema/pfs.cc:2190
#26 0x00007fa003eeddc5 in start_thread () von /lib64/libpthread.so.0
#27 0x00007fa002aab74d in Klon () von /lib64/libc.so.6

Lösung

Fügen Sie einen neuen Parameter ignore_missing_space für buf_read_ibuf_merge_pages, buf_read_page_low und fil_io hinzu. Gibt an, dass der zu löschende Speicherplatz ignoriert wird. Der Standardwert ist „false“ und wird auf „true“ gesetzt, wenn ibuf_merge_pages aufgerufen wird. Im Fehlermeldebereich von fil_io wird zusätzlich ermittelt, ob der Parameter true ist. Wenn dies der Fall ist, wird kein Fehler gemeldet und andere Prozesse werden fortgesetzt.

Oder übergeben Sie direkt den Parameter IORequest::IGNORE_MISSING, wenn buf_read_ibuf_merge_pages buf_read_page_low aufruft.

Den spezifischen Code finden Sie im MariaDB-Commit: 8edbb1117a9e1fd81fbd08b8f1d06c72efe38f44

Betroffene Versionen

Überprüfen Sie die relevanten Informationen. Dieses Problem trat beim Löschen der Tablespace-Version beim Ändern von Bug#19710564 auf.

  • Eingeführt in MySQL Community Server 5.7.6, nicht behoben in Version 5.7.22, behoben in Version 8.0.0.
  • Betroffen ist MariaDB Server 10.2. MariaDB Server 10.2.9, 10.3.2 wurde behoben

Optimierungsvorschläge

Die Leistung kann wie folgt optimiert werden: Notieren Sie die fehlerhafte Speicher-ID in buf_read_ibuf_merge_pages, bestimmen Sie während der Schleife die Speicher-ID der nächsten Seite und löschen Sie, wenn die Speicher-ID dieselbe ist, direkt den entsprechenden ibuf-Datensatz (die aktuell zugewiesene maximale Speicher-ID wird im Systemtabellenbereich aufgezeichnet, die Speicher-ID belegt 4 Bytes, was niedriger als 0xFFFFFFF0UL ist. Lesen Sie beim Zuweisen den im Systemtabellenbereich gespeicherten Wert und fügen Sie dann eins hinzu, um ihn eindeutig zu machen).

Zusammenfassen

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Lernwert für Ihr Studium oder Ihre Arbeit hat. Wenn Sie Fragen haben, können Sie eine Nachricht hinterlassen. Vielen Dank für Ihre Unterstützung von 123WORDPRESS.COM.

Das könnte Sie auch interessieren:
  • Implementierung der MySQL-Tabellenlöschoperation (Unterschiede zwischen Löschen, Abschneiden und Löschen)
  • Zusammenfassung der Methoden zum Suchen und Löschen doppelter Daten in MySQL-Tabellen
  • Praktische Methode zum Löschen einer Zeile in einer MySql-Tabelle
  • Detailliertes Beispiel zum Erstellen und Löschen von Tabellen in MySQL
  • Einfache Implementierung zum Ignorieren von Fremdschlüsseleinschränkungen beim Löschen von MySQL-Tabellen
  • So löschen Sie eine MySQL-Tabelle

<<:  Analyse der Implementierung der Nginx Rush-Kaufstrombegrenzungskonfiguration

>>:  So verstehen Sie den einfachen Speichermodus der Statusverwaltung von Vue

Artikel empfehlen

js, um den Popup-Effekt zu erzielen

In diesem Artikelbeispiel wird der spezifische Co...

So fragen Sie Daten aus mehreren unabhängigen Tabellen und Paging in MySQL ab

Mysql mehrere unabhängige Tabellen Abfragedaten u...

Detaillierte Anwendungsfälle von MySql Escape

MySQL-Escape Escape bedeutet die ursprüngliche Se...

Die umfassendsten 50 Mysql-Datenbankabfrageübungen

Diese Datenbankabfrageanweisung ist eine von 50 D...

Optimierung von JavaScript und CSS zur Verbesserung der Website-Leistung

<br /> Im ersten und zweiten Teil haben wir ...

Was ist COLLATE in MYSQL?

Vorwort Führen Sie den Befehl show create table &...

JavaScript-Ereigniserfassungs-Blubbern und Erfassungsdetails

Inhaltsverzeichnis 1. Ereignisablauf 1. Konzept 2...

Beispiele für die Verwendung von MySQL-Abdeckungsindizes

Was ist ein Deckungsindex? Das Erstellen eines In...

Objektorientierte JavaScript-Implementierung eines Lupengehäuses

In diesem Artikel wird der spezifische Code der o...

Umfassende Analyse der Isolationsebenen in MySQL

Wenn die Datenbank gleichzeitig denselben Datenst...

So verwenden Sie Vue3-Mixin

Inhaltsverzeichnis 1. Wie verwende ich Mixin? 2. ...

React realisiert sekundäre Verknüpfung (linke und rechte Verknüpfung)

In diesem Artikel wird der spezifische Code von R...

JavaScript Canvas realisiert dynamische Punkt- und Linieneffekte

In diesem Artikel wird der spezifische Code für J...