在 MySQL 中,為什麼第一批通過客戶端準備好的語句執行較慢?
我有大約一百萬行要插入到 MySQL (InnoDB) 數據庫中,所以為了加快速度,我轉向了批處理/多行插入。具體來說,我連接到數據庫(我使用的是 Java,所以它是通過 Connector/J 完成的)並
rewriteBatchedStatements=true
附加在 URL 的末尾,然後我用prepareStatement()
. 我使用addBatch()
將行添加到單個準備好的語句中,並executeBatch()
在達到一定數量的行時呼叫。在幾次呼叫之後executeBatch()
,我會做一個送出。就最終結果而言,一切都很好,但我對一種行為非常好奇。假設我將何時呼叫的門檻值設置
executeBatch()
為 10,000 行,那麼在處理過程中,第一次呼叫executeBatch()
將明顯慢於後續呼叫(在我的場景中大約 5 秒 vs. < 1 秒)。感覺好像 MySQL 伺服器仍在“準備”一些東西。據我所知,PostgreSQL 有一個選項,比如
setPrepareThreshold
在實際編譯 SQL 語句之前設置發出的查詢數。MySQL是否在做類似的事情?如何減輕這種延遲,或者這種行為是 MySQL 實現所固有的?編輯(更多背景)
下面是我將在其中插入記錄的表之一(其他表具有類似的模式)。
'CREATE TABLE `flow_hourly` ( `datetime` datetime NOT NULL, `customer_id` varchar(7) CHARACTER SET utf8mb4 NOT NULL, `pages` bigint(14) NOT NULL, `hits` bigint(14) NOT NULL, `bandwidth` bigint(14) NOT NULL, `nvpages` bigint(14) NOT NULL, `nvhit` bigint(14) NOT NULL, `nvbandwidth` bigint(14) NOT NULL, PRIMARY KEY (`datetime`,`customer_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
由於此數據庫用於開發目的,目前最大大小也約為 100 萬條記錄,但在生產中增長速度應該相對較快(每天 n*100K+ 插入)。
可以從上面的模式中猜到我正在創建一個數據庫來記錄伺服器流量資訊。對於每個時間間隔(例如每小時、每天、每月…),都有一個專用表,以便可以相應地加快對不同時間間隔的查詢。我每小時接收一次交通資訊,所以除了每小時表之外,我會
INSERT ... ON DUPLICATE UPDATE
累積值。
可能您有一個普通的“冷記憶體”案例。
當您向表中插入 10,000 行時,需要將這些行添加到“數據”中的適當位置。此外,需要向每個索引的 BTree 添加 10,000 個條目。(你有多少索引?請提供
SHOW CREATE TABLE
。)如果你有
AUTO_INCREMENT
,那麼這些行將被“附加”到表中;這不是記憶體問題。另一方面,如果您有 UUID 索引或其他一些“隨機”索引,那麼索引中的“插入”將是隨機的——涉及讀取-修改-寫入。讀取和寫入被記憶體。但是,如果在執行前 10,000 次時系統“冷”,則可能需要進行大量讀取。將其乘以您擁有的索引數。桌子有多大?如果它足夠小以適合
innodb_buffer_pool_size
(如果是 InnoDB)或足夠小以適合索引key_buffer_size
(如果是 MyISAM),那麼很快所有索引塊將被記憶體,並且插入將加快。如果表對於記憶體來說太大,那麼“隨機”索引將繼續命中磁碟(並且速度很慢)。
AUTO_INCREMENT
將繼續快速。5 秒大約是從商品旋轉驅動器讀取 500 次。所以,我猜你有一張大小適中的桌子。由於接下來的 10K 行進入 <1sec(<100 磁碟命中),我猜它到目前為止適合記憶體。
我經常推薦 100-1000 的塊大小。這部分是因為 5 秒可能是個問題。你也可以
COMMIT
在每個塊之後,因為延遲它的好處很小。此外,如果您等待“太長時間”送出,“事務”可能會溢出 log_file,從而導致效率低下。塊大小為 100 比理論最大值慢約 10%。1000 比最大值慢約 1%。
如果您有複製,請記住 5 秒會干擾任何其他正在複製的內容。(另一個反對 10K 的論點。)