Das Einfügen in eine große MySQL-Tabelle ohne automatisch inkrementierten Primärschlüssel ist sehr langsam
P粉845862826
2023-08-29 20:31:33
<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>
我会带着这个答案冒险。
假设
innodb_buffer_pool_size
的值略小于 20MB,并且系统最近变得受 I/O 限制,因为下一个 Select 所需的“下一个”块越来越经常不缓存在 buffer_pool 中。
简单的解决方案是获取更多 RAM 并提高该可调参数的设置。但表格只会增长到您购买的下一个限制。
相反,这里有一些部分解决方案。
INT UNSIGNED
(4 个字节而不是 8),甚至可能是MEDIUMINT UNSIGNED
(3 个字节) )。注意ALTER TABLE
会长时间锁定表。DATETIME
和TIMESTAMP
占用 5 个字节(而不是 8 个字节)。更多
是的,情况就是这样。
将其作为
INDEX
,或者更好的是,作为PRIMARY KEY
的开头,可以为您的两个查询提供最佳帮助:回复:
如果它用于控制其他一些 SQL,请考虑将其添加到其他 SQL 中:
该查询(无论哪种形式)都需要您已有的内容
您的其他查询需求
所以,添加索引,或...
更好...这种组合对于两者来说都更好
SELECT
:有了这个组合,