[譯] MySQL 最佳實踐 —— 高效插入數據

Get the dolphin up to speed — Photo by [JIMMY ZHANG](https://blog-private.oss-cn-shanghai.aliyuncs.com/20200402002543.jpeg) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)

當你須要在 MySQL 數據庫中批量插入數百萬條數據時,你就會意識到,逐條發送 INSERT 語句並非一個可行的方法。html

MySQL 文檔中有些值得一讀的 INSERT 優化技巧前端

在這篇文章裏,我將概述高效加載數據到 MySQL 數據庫的兩大技術。mysql

LOAD DATA INFILE

若是你正在尋找提升原始性能的方案,這無疑是你的首選方案。LOAD DATA INFILE 是一個專門爲 MySQL 高度優化的語句,它直接將數據從 CSV / TSV 文件插入到表中。android

有兩種方法可使用 LOAD DATA INFILE。你能夠把數據文件拷貝到服務端數據目錄(一般 /var/lib/mysql-files/),而且運行:ios

LOAD DATA INFILE '/path/to/products.csv' INTO TABLE products;
複製代碼

這個方法至關麻煩,由於你須要訪問服務器的文件系統,爲數據文件設置合適的權限等。git

好消息是,你也能將數據文件存儲在客戶端,而且使用 LOCAL 關鍵詞:github

LOAD DATA LOCAL INFILE '/path/to/products.csv' INTO TABLE products;
複製代碼

在這種狀況下,從客戶端文件系統中讀取文件,將其透明地拷貝到服務端臨時目錄,而後從該目錄導入。總而言之,這幾乎與直接從服務器文件系統加載文件同樣快,不過,你須要確保服務器啓用了此 選項sql

LOAD DATA INFILE 有不少可選項,主要與數據文件的結構有關(字段分隔符、附件等)。請瀏覽 文檔 以查看所有內容。數據庫

雖然從性能角度考慮, LOAD DATA INFILE 是最佳選項,可是這種方式須要你先將數據以逗號分隔的形式導出到文本文件中。若是你沒有這樣的文件,你就須要花費額外的資源來建立它們,而且可能會在必定程度上增長應用程序的複雜性。幸運的是,還有一種另外的選擇。後端

擴展的插入語句(Extended inserts)

一個典型的 INSERT SQL 語句是這樣的:

INSERT INTO user (id, name) VALUES (1, 'Ben');
複製代碼

extended INSERT 將多條插入記錄聚合到一個查詢語句中:

INSERT INTO user (id, name) VALUES (1, 'Ben'), (2, 'Bob');
複製代碼

關鍵在於找到每條語句中要插入的記錄的最佳數量。沒有一個放之四海而皆準的數字,所以,你須要對數據樣本作基準測試,以找到性能收益的最大值,或者在內存使用和性能方面找到最佳折衷。

爲了充分利用 extended insert,咱們還建議:

  • 使用預處理語句
  • 在事務中運行該語句

基準測試

我要插入 120 萬條記錄,每條記錄由 6 個 混合類型數據組成,平均每條數據約 26 個字節大小。我使用了兩種常見的配置進行測試:

  • 客戶端和服務端在同一機器上,經過 UNIX 套接字進行通訊
  • 客戶端和服務端在不一樣的機器上,經過延遲很是低(小於 0.1 毫秒)的千兆網絡進行通訊

做爲比較的基礎,我使用 INSERT ... SELECT 複製了該表,這個操做的性能表現爲每秒插入 313,000 條數據

LOAD DATA INFILE

令我吃驚的是,測試結果證實 LOAD DATA INFILE 比拷貝表更快

  • LOAD DATA INFILE:每秒 377,000 次插入
  • LOAD DATA LOCAL INFILE 經過網絡:每秒 322,000 次插入

這兩個數字的差別彷佛與從客戶端到服務端傳輸數據的耗時有直接的關係:數據文件的大小爲 53 MB,兩個基準測試的時間差了 543 ms,這表示傳輸速度爲 780 mbps,接近千兆速度。

這意味着,頗有可能,在徹底傳輸文件以前,MySQL 服務器並無開始處理該文件:所以,插入的速度與客戶端和服務端之間的帶寬直接相關,若是它們不在同一臺機器上,考慮這一點則很是重要。

Extended inserts

我使用 BulkInserter 來測試插入的速度,BulkInserter 是我編寫的 開源庫 PHP 類的一部分,每一個查詢最多插入 10,000 條記錄:

正如咱們所看到的,隨着每條查詢插入數的增加,插入速度也會迅速提升。與逐條插入速度相比,咱們在本地主機上性能提高了 6 倍,在網絡主機上性能提高了 17 倍:

  • 在本地主機上每秒插入數量從 40,000 提高至 247,000
  • 在網絡主機上每秒插入數量從 1,2000 提高至 201,000

這兩種狀況都須要每一個查詢大約 1,000 個插入來達到最大吞吐量。可是每條查詢 40 個插入就足以在本地主機上達到 90% 的吞吐量,這多是一個很好的折衷。還須要注意的是,達到峯值以後,隨着每一個查詢插入數量的增長,性能其實是會降低。

extended insert 的優點在網絡鏈接的狀況下更加明顯,由於連續插入的速度取決於你的網絡延遲。

max sequential inserts per second ~= 1000 / ping in milliseconds
複製代碼

客戶端和服務端之間的延遲越高,你從 extended insert 中獲益越多。

結論

不出所料,LOAD DATA INFILE 是在單個鏈接上提高性能的首選方案。它要求你準備格式正確的文件,若是你必須先生成這個文件,並/或將其傳輸到數據庫服務器,那麼在測試插入速度時必定要把這個過程的時間消耗考慮進去。

另外一方面,extended insert 不須要臨時的文本文件,而且能夠達到至關於 LOAD DATA INFILE 65% 的吞吐量,這是很是合理的插入速度。有意思的是,不管是基於網絡仍是本地主機,彙集多條插入到單個查詢老是能獲得更好的性能

若是你決定開始使用 extended insert,必定要先用生產環境的數據樣本和一些不一樣的插入數來測試你的環境,以找出最佳的數值。。

在增長單個查詢的插入數的時候要當心,所以它可能須要:

  • 在客戶端分配更多的內存
  • 增長 MySQL 服務器的 max_allowed_packet 參數配置。

最後,值得一提的是,根據 Percona 的說法,你可使用併發鏈接、分區以及多個緩衝池,以得到更好的性能。更多信息請查看 他們博客的這篇文章

基準測試運行在裝有 Centos 7 和 MySQL 5.7 的裸服務器上,它的主要硬件配置有 Xeon E3 @3.8 GHz 處理器,32 GB RAM 和 NVMe SSD。MySQL 的基準表使用 InnoBD 存儲引擎。

基準測試的源代碼保存在 gist 上,結果圖保存在 plot.ly 上。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索