Das Einfügen in eine große MySQL-Tabelle ohne automatisch inkrementierten Primärschlüssel ist sehr langsam
P粉845862826
P粉845862826 2023-08-29 20:31:33
0
1
588
<p>Mir ist kürzlich ein deutlicher Anstieg des Zeitunterschieds aufgefallen, der zum Abschließen einer einfachen INSERT-Anweisung erforderlich ist. Während diese Anweisungen im Durchschnitt etwa 11 Millisekunden dauern, können sie manchmal auch 10 bis 30 Sekunden dauern, und ich habe sogar bemerkt, dass die Ausführung mehr als 5 Minuten dauert. </p> <p>MySQL-Version ist <code>8.0.24</code>, läuft auf Windows Server 2016. Soweit ich weiß, waren die Ressourcen des Servers nie überlastet. Dem Server steht ausreichend CPU-Overhead zur Verfügung und ihm sind 32 GB RAM zugewiesen. </p> <p>Dies ist die Tabelle, die ich verwende: </p> <pre class="brush:php;toolbar:false;">CREATE TABLE `saved_segment` ( `recording_id` bigint unsigned NOT NULL, `index` bigint unsigned NOT NULL, `start_filetime` bigint unsigned NOT NULL, `end_filetime` bigint unsigned NOT NULL, `offset_and_size` bigint unsigned NOT NULL DEFAULT '18446744073709551615', „storage_id“ tinyint unsigned NOT NULL, PRIMÄRSCHLÜSSEL („recording_id“, „index“) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci</pre> <p>Diese Tabelle hat keine anderen Indizes oder Fremdschlüssel und wird in keiner anderen Tabelle als Referenz auf Fremdschlüssel verwendet. Die gesamte Tabellengröße beträgt etwa 20 GB und die Anzahl der Zeilen beträgt etwa 281 MB, was meiner Meinung nach nicht zu groß ist. </p> <p>Die Tabelle wird fast ausschließlich im schreibgeschützten Modus verwendet, mit bis zu 1000 Lesevorgängen pro Sekunde. Alle diese Lesevorgänge erfolgen in einfachen SELECT-Abfragen und nicht in komplexen Transaktionen und nutzen den Primärschlüsselindex effizient. Es gibt, wenn überhaupt, nur sehr wenige gleichzeitige Schreibvorgänge in dieser Tabelle. Dies geschah mit Absicht, um herauszufinden, ob es beim langsamen Einführen helfen würde, aber das ist nicht der Fall. Bis dahin sind immer bis zu 10 gleichzeitige Einfügungen im Gange. UPDATE- oder DELETE-Anweisungen werden niemals für diese Tabelle ausgeführt. </p> <p>Die Abfragen, mit denen ich Probleme habe, sind alle auf diese Weise aufgebaut. Sie erscheinen nie in der Transaktion.Während Einfügungen, die auf dem geclusterten Primärschlüssel basieren, definitiv nicht nur durch Anhängen möglich sind, fügt die Abfrage fast immer zwischen 1 und 20 benachbarte Zeilen in die Tabelle ein: </p> <pre class="brush:php;toolbar:false;">INSERT IGNORE INTO saved_segment (recording_id, `index`, start_filetime, end_filetime, offset_and_size, storage_id) WERTE (19173, 631609, 133121662986640000, 133121663016640000, 20562291758298876, 10), (19173, 631610, 133121663016640000, 133121663046640000, 20574308942546216, 10), (19173, 631611, 133121663046640000, 133121663076640000, 20585348350688128, 10), (19173, 631612, 133121663076640000, 133121663106640000, 20596854568114720, 10), (19173, 631613, 133121663106640000, 133121663136640000, 20609723363860884, 10), (19173, 631614, 133121663136640000, 133121663166640000, 20622106425668780, 10), (19173, 631615, 133121663166640000, 133121663196640000, 20634653501528448, 10), (19173, 631616, 133121663196640000, 133121663226640000, 20646967172721148, 10), (19173, 631617, 133121663226640000, 133121663256640000, 20657773176227488, 10), (19173, 631618, 133121663256640000, 133121663286640000, 20668825200822108, 10)</pre> <p>Dies ist die Ausgabe der EXPLAIN-Anweisung für die obige Abfrage: </p> <table class="s-table"> <thead> <tr> <th>id</th> <th>Typ auswählen</th> <th>Tabelle</th> <th>Partition</th> <th>Typ</th> <th>Mögliche Schlüssel</th> <th>key</th> <th>key_len</th> <th>Referenz</th> <th>OK</th> <th>Gefiltert</th> <th>Extra</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>Einfügen</td> <td>Gespeicherte Segmente</td> <td>Leer</td> <td>Alle</td> <td>Leer</td> <td>Leer</td> <td>Leer</td> <td>Leer</td> <td>Leer</td> <td>Leer</td> <td>Leer</td> </tr> </tbody> </table> <p>Diese Probleme sind relativ neu und fielen nicht auf, als der Tisch etwa doppelt so klein war. </p> <p>Ich habe versucht, die Anzahl gleichzeitiger Einfügungen in der Tabelle von etwa 10 auf 1 zu reduzieren. Ich habe auch die Fremdschlüssel (<code>recording_id</code>) für einige Spalten entfernt, um die Einfügungen noch schneller zu machen. Die <code>Analyse von Tabellen</code> und die Schemaanalyse ergaben keine verwertbaren Informationen.</p> <p>Eine Lösung, die ich mir überlegt habe, bestand darin, den gruppierten Primärschlüssel zu entfernen und einen automatisch inkrementierenden Primärschlüssel und einen regulären Index für die Spalte <code>(recording_id, index)</code> hinzuzufügen. Meiner Meinung nach würde dies dazu beitragen, die Einfügung „nur anhängen“ zu machen.Ich bin für alle Vorschläge offen, vielen Dank im Voraus! </p> <p>Bearbeiten: Ich werde auf einige der in den Kommentaren und Antworten aufgeworfenen Punkte und Fragen eingehen: </p> <ul> <li><code>autocommit</code> ist auf <code>EIN</code></li> gesetzt. Der Wert von <li><code>innodb_buffer_pool_size</code> ist <code>21474836480</code> und der Wert von <code>innodb_buffer_pool_chunk_size</code> ist <code>134217728</code> ; </ li> <li>Ein Kommentar äußerte Bedenken hinsichtlich eines Konflikts zwischen der Lesesperre für Lesevorgänge und der exklusiven Sperre für Schreibvorgänge. Die Tabelle wird in gewisser Weise wie ein Cache verwendet. Ich muss nicht lesen, um immer den neuesten Status der Tabelle anzuzeigen, wenn dies eine Leistungssteigerung bedeutet. Allerdings soll die Tabelle auch bei Serverabstürzen und Hardwareausfällen langlebig bleiben. Könnte dies mit einer lockereren Transaktionsisolationsstufe erreicht werden? </li> <li>Die Architektur kann definitiv optimiert werden; <code>recording_id</code> kann eine 4-Byte-Ganzzahl sein, <code>end_filetime</code> start_filetime</code> ; kann auch kleiner sein. Ich befürchte, dass diese Änderungen das Problem nur eine Weile hinauszögern werden, bis die Größe der Tabelle vergrößert wird, um den eingesparten Platz auszugleichen. </li> <li>Einfügungen in die Tabelle sind immer fortlaufend Ein für die Tabelle ausgeführtes SELECT sieht folgendermaßen aus: </li> </ul> <pre class="brush:php;toolbar:false;">SELECT TRUE VON gespeichertes_segment WHERE Recording_id = ? AND `index` = ?</pre> <pre class="brush:php;toolbar:false;">SELECT index, start_filetime, end_filetime, offset_and_size, storage_id VON gespeichertes_segment WO Recording_id = ? start_filetime >= ? UND start_filetime <= ? ORDER BY `index` ASC</pre> <p>Der zweite Abfragetyp könnte sicherlich durch einen Index verbessert werden, aber ich befürchte, dass dies die INSERT-Leistung weiter beeinträchtigen würde. </p> <p>Eine weitere Sache, die ich vergessen habe zu erwähnen, ist, dass es eine Tabelle gibt, die dieser sehr ähnlich ist. Es werden genau die gleichen Abfragen und Einfügungen durchgeführt, es kann jedoch zu einem weiteren E/A-Ausfall kommen. </p> <p>Bearbeiten 2: <code>SHOW TABLE STATUS</code> die Ergebnisse der Tabelle <code>saved_segment</code> und einer sehr ähnlichen Tabelle <code>saved_screenshot</code> (die in <code>bigint nicht signiert ist). Es gibt einen zusätzlichen Index für die Spalte null</code></p> <table class="s-table"> <thead> <tr> <th>Name</th> <th>Motor</th> <th>Version</th> <th>Zeilenformat</th> <th>OK</th> <th>Durchschnittliche Zeilenlänge</th> <th>Datenlänge</th> <th>Maximale Datenlänge</th> <th>Indexlänge</th> <th>Keine Daten</th> <th>Auto-Inkrement</th> <th>Erstellungszeit</th> <th>Aktualisiert</th> <th>Zeit prüfen</th> <th>Organisation</th> <th>Prüfsumme</th> <th>Erstellungsoptionen</th> <th>Kommentare</th> </tr> </thead> <tbody> <tr> <td>Gespeicherter Screenshot</td> <td>InnoDB</td> <td>10</td> <td>Neuigkeiten</td> <td>483430208</td> <td>61</td> <td>29780606976</td> <td>0</td> <td>21380464640</td> <td>6291456</td> <td>Leer</td> <td>“2021-10-21 01:03:21”</td> <td>“2022-11-07 16:51:45”</td> <td>Leer</td> <td>utf8mb4_0900_ai_ci</td> <td>Leer</td> <td></td> <td></td> </tr> <tr> <td>Gespeicherte Segmente</td> <td>InnoDB</td> <td>10</td> <td>Neuigkeiten</td> <td>281861164</td> <td>73</td> <td>20802699264</td> <td>0</td> <td>0</td> <td>4194304</td> <td>Leer</td> <td>“2022-11-02 09:03:05”</td> <td>“2022-11-07 16:51:22”</td> <td>Leer</td> <td>utf8mb4_0900_ai_ci</td> <td>Leer</td> <td></td> <td></td> </tr> </tbody> </table></p>
P粉845862826
P粉845862826

Antworte allen(1)
P粉022140576

我会带着这个答案冒险。

假设

  • innodb_buffer_pool_size 的值略小于 20MB,并且
  • 每秒 1K 个选择随机到达表的各个部分,然后

系统最近变得受 I/O 限制,因为下一个 Select 所需的“下一个”块越来越经常不缓存在 buffer_pool 中。

简单的解决方案是获取更多 RAM 并提高该可调参数的设置。但表格只会增长到您购买的下一个限制。

相反,这里有一些部分解决方案。

  • 如果数字不太大,前两列可能是 INT UNSIGNED(4 个字节而不是 8),甚至可能是 MEDIUMINT UNSIGNED(3 个字节) )。注意 ALTER TABLE 会长时间锁定表。
  • 这些开始和结束时间看起来像带有小数秒的时间戳,且始终为“.000”。 DATETIMETIMESTAMP 占用 5 个字节(而不是 8 个字节)。
  • 您的示例显示经过时间为 0。如果 (end-start) 通常非常小,那么存储经过时间而不是结束时间会进一步缩小数据。 (但是使用结束时间会让事情变得混乱)。
  • 您提供的示例数据看起来“连续”。这与自动增量的效率差不多。这是常态吗?如果不是,INSERT 可能是 I/O 抖动的一部分。
  • 您建议添加人工智能以及二级索引,这使得插入的工作量增加了一倍;所以我不推荐它。

更多

是的,情况就是这样。

将其作为 INDEX,或者更好的是,作为 PRIMARY KEY 的开头,可以为您的两个查询提供最佳帮助:

(recording_id, index)

回复:

SELECT  TRUE
FROM    saved_segment
WHERE   recording_id = ? AND `index` = ?

如果它用于控制其他一些 SQL,请考虑将其添加到其他 SQL 中:

... EXISTS ( SELECT 1
        FROM    saved_segment
        WHERE   recording_id = ? AND `index` = ? ) ...

该查询(无论哪种形式)都需要您已有的内容

PRIMARY KEY(recording_id, index)

您的其他查询需求

INDEX(recording_id, start_filetime)

所以,添加索引,...

更好...这种组合对于两者来说都更好SELECT

PRIMARY KEY(recording_id, start_filetime, index).
INDEX(recording_id, index)

有了这个组合,

  • 单行存在性检查将“使用索引”执行,因为它是“覆盖”。
  • 另一个查询将找到在 PK 上聚集在一起的所有相关行。
  • (PK 有这 3 列,因为它需要是唯一的。它们按这个顺序排列有利于您的第二个查询。而且它是 PK,而不仅仅是一个 INDEX,因此它不需要在索引的 BTree 之间反弹和数据的 BTree。)
  • “集群”可以通过减少此类查询所需的磁盘块数量来提高性能。这会减少 buffer_pool 中的“颠簸”,从而减少增加 RAM 的需要。
  • 我的索引建议大部分与我的数据类型建议正交。
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage