簡體   English   中英

哪個更快:多個單插入還是一個多行插入?

[英]Which is faster: multiple single INSERTs or one multiple-row INSERT?

我正在嘗試優化將數據插入 MySQL 的代碼的一部分。 我應該鏈接 INSERT 來制作一個巨大的多行 INSERT 還是多個單獨的 INSERT 更快?

https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html

插入一行所需的時間由以下因素決定,其中數字表示大致比例:

  • 連接: (3)
  • 向服務器發送查詢:(2)
  • 解析查詢:(2)
  • 插入行:(1 × 行大小)
  • 插入索引:(1 × 索引數)
  • 閉幕式:(1)

由此可以看出,發送一個大語句將為每個插入語句節省 7 的開銷,在進一步閱讀文本時還說:

如果您同時從同一客戶端插入多行,請使用帶有多個 VALUES 列表的 INSERT 語句一次插入多行。 這比使用單獨的單行 INSERT 語句快得多(在某些情況下快很多倍)。

我知道我在回答這個問題之前有人問近兩年半年后,但我只是想現在該節目確實做得每個刀片有多個值塊是大大提供從項目我工作的一些硬數據比順序的單個 VALUE 塊 INSERT 語句更快。

我在 C# 中為此基准編寫的代碼使用 ODBC 將數據從 MSSQL 數據源(約 19,000 行,在任何寫入開始之前讀取)和 MySql .NET 連接器 (Mysql.Data.*) 內容讀取到內存中通過准備好的語句將內存中的數據插入到 MySQL 服務器上的表中。 它的編寫方式允許我動態調整每個准備好的 INSERT 的 VALUE 塊的數量(即,一次插入 n 行,我可以在運行前調整 n 的值。)我還運行了測試每個 n 多次。

執行單個 VALUE 塊(例如,一次 1 行)需要 5.7 - 5.9 秒的運行時間。 其他值如下:

一次 2 行:3.5 - 3.5 秒
一次 5 行:2.2 - 2.2 秒
一次 10 行:1.7 - 1.7 秒
一次 50 行:1.17 - 1.18 秒
一次 100 行:1.1 - 1.4 秒
一次 500 行:1.1 - 1.2 秒
一次 1000 行:1.17 - 1.17 秒

所以是的,即使只是將 2 或 3 個寫入捆綁在一起也可以顯着提高速度(運行時間減少 n 倍),直到您達到 n = 5 和 n = 10 之間的某個位置,此時改善顯着下降,在 n = 10 到 n = 50 范圍內的某個地方,改進變得可以忽略不計。

希望能幫助人們決定 (a) 是否使用 multiprepare 想法,以及 (b) 每個語句創建多少個 VALUE 塊(假設您想要處理的數據可能足夠大以將查詢推送超過最大查詢大小對於 MySQL,我認為在很多地方默認為 16MB,可能更大或更小,具體取決於服務器上設置的 max_allowed_pa​​cket 的值。)

一個主要因素是您是否使用事務引擎以及是否啟用了自動提交。

默認情況下自動提交是開啟的,你可能想保持開啟; 因此,您所做的每個插入都會執行自己的事務。 這意味着如果您每行執行一次插入,您將為每一行提交一個事務。

假設是單線程,這意味着服務器需要為每一行同步一些數據到磁盤。 它需要等待數據到達持久存儲位置(希望是 RAID 控制器中的電池供電內存)。 這本質上是相當緩慢的,並且可能會成為這些情況下的限制因素。

我當然假設您使用的是事務引擎(通常是 innodb)並且您沒有調整設置以降低耐用性。

我還假設您使用單個線程來執行這些插入。 使用多線程會使事情變得有些混亂,因為某些版本的 MySQL 在 innodb 中有工作組提交 - 這意味着執行自己提交的多個線程可以共享對事務日志的單次寫入,這很好,因為它意味着更少的同步到持久存儲.

另一方面,結果是,您真的很想使用多行插入。

有一個限制,它會適得其反,但在大多數情況下,它至少為 10,000 行。 因此,如果您將它們批處理到 1,000 行,您可能是安全的。

如果您使用的是 MyISAM,還有很多其他的東西,但我不會讓您厭煩這些。 和平。

一次通過電線發送盡可能多的插入。 實際插入速度應該相同,但您會看到網絡開銷減少帶來的性能提升。

通常,對數據庫的調用次數越少越好(意味着更快、更有效),因此請嘗試以最小化數據庫訪問的方式編寫插入代碼。 請記住,除非您使用連接池,否則每次訪問數據庫都必須創建一個連接,執行 sql,然后斷開連接。 相當多的開銷!

你可能想要 :

  • 檢查自動提交是否關閉
  • 打開連接
  • 在單個事務中發送多批插入(大小約為 4000-10000 行?你看)
  • 關閉連接

根據您的服務器擴展的程度(它對PostgreSQlOracleMSSQL絕對沒問題),使用多個線程和多個連接執行上述操作。

通常,由於連接開銷,多次插入會更慢。 一次執行多個插入將降低每個插入的開銷成本。

根據您使用的語言,您可以在轉到數據庫之前用您的編程/腳本語言創建一個批處理並將每個插入添加到批處理中。 然后,您將能夠使用一個連接操作來執行大批量。 這是Java 中一個示例。

MYSQL 5.5 一個 sql 插入語句花費了 ~300 到 ~450 毫秒。 而以下統計數據用於內聯多個插入語句。

(25492 row(s) affected)
Execution Time : 00:00:03:343
Transfer Time  : 00:00:00:000
Total Time     : 00:00:03:343

我會說內聯是要走的路:)

我只是做了一個小的基准測試,看起來對於很多線來說它並不快。 這是我插入 280 000 行的結果:

  • 由 10 000 : 164.96 秒
  • 由 5 000 : 37 秒
  • 按 1000 : 12.56 秒
  • 按 600 : 12.59 秒
  • 按 500 : 13.81 秒
  • 由 250 : 17.96 秒
  • 按 400:14.75 秒
  • 按 100 : 27 秒

看起來 1000 x 1000 是最好的選擇。

在插入方面優化 Mysql 和 MariaDB 是多么荒謬。 我測試了 mysql 5.7 和 mariadb 10.3,它們沒有真正的區別。

我已經在具有 NVME 磁盤、70,000 IOPS、1.1 GB/秒 seq 吞吐量的服務器上對此進行了測試,這可能是全雙工(讀取和寫入)。
該服務器也是高性能服務器。
給它 20 GB 的內存。
數據庫完全空了。

在進行多行插入時,我收到的速度是每秒 5000 次插入(嘗試使用 1MB 到 10MB 的數據塊)

現在的線索:
如果我添加另一個線程並插入到 SAME 表中,我突然有 2x5000 /秒。 多一個線程,我總共有 15000 個/秒

考慮一下:當執行 ONE 線程插入時,這意味着您可以按順序寫入磁盤(索引除外)。 使用線程時,實際上會降低可能的性能,因為它現在需要進行更多的隨機訪問。 但是現實檢查表明 mysql 優化得非常糟糕,線程有很大幫​​助。

這種服務器的實際性能可能是每秒數百萬,CPU 空閑磁盤空閑。
原因很明顯,mariadb和mysql一樣有內部延遲。

這是我做的一個小的 PHP 工作台的結果:

我正在嘗試使用 PHP 8.0、MySQL 8.1 (mysqli) 以 3 種不同方式插入 3000 條記錄

多個插入查詢,具有多個事務:

$start = microtime(true);
for($i = 0; $i < 3000; $i++)
{
    mysqli_query($res, "insert into app__debuglog VALUE (null,now(), 'msg : $i','callstack','user','debug_speed','vars')");
}
$end = microtime(true);
echo "Took " . ($end - $start) . " s\n";

做了 5 次,平均: 11.132 秒(+/- 0.6 秒)

多個插入查詢,單個事務:

$start = microtime(true);
mysqli_begin_transaction($res, MYSQLI_TRANS_START_READ_WRITE);
for($i = 0; $i < 3000; $i++)
{
    mysqli_query($res, "insert into app__debuglog VALUE (null,now(), 'msg : $i','callstack','user','debug_speed','vars')");
}
mysqli_commit($res);
$end = microtime(true);
echo "Took " . ($end - $start) . " ms\n";

5 次測試的結果: 0.48s (+/- 0.04s)

單個聚合插入查詢

$start = microtime(true);

$values = "";

for($i = 0; $i < 3000; $i++)
{
    $values .= "(null,now(), 'msg : $i','callstack','user','debug_speed','vars')";
    if($i !== 2999)
        $values .= ",";
}
mysqli_query($res, "insert into app__debuglog VALUES $values");

$end = microtime(true);
echo "Took " . ($end - $start) . " ms\n";

5 次測試的結果: 0.085s (+/- 0.05s)

因此,對於 3000 行插入,看起來像:

  • 在單個寫入事務中使用多個查詢比為每個插入使用多個事務進行多個查詢快約 22 倍。
  • 使用單個聚合插入語句仍然比在單個寫入事務中使用多個查詢快 6 倍

我會添加這樣的信息,即一次過多的行取決於它們的內容可能會導致Got a packet 大於 'max_allowed_pa​​cket'

也許考慮使用像PHP 的 array_chunk這樣的函數來為你的大數據集做多次插入。

多個插入速度更快,但它有閾值。 另一個 thrik 是禁用約束檢查臨時使插入快得多。 不管你的桌子有沒有。 例如測試禁用外鍵並享受速度:

SET FOREIGN_KEY_CHECKS=0;

offcourse你應該在插入后重新打開它:

SET FOREIGN_KEY_CHECKS=1;

這是插入大量數據的常用方法。 數據完整性可能會中斷,因此您應該在禁用外鍵檢查之前注意這一點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM