使用 InnoDB 隨機慢速插入
TL;DR :如何改進表中的插入時間,使每次插入的執行時間低於 100 毫秒?
該表由一個接收事務的網路服務使用,因此我無法對插入進行分組。Web 服務的超時時間必須在 150 毫秒以下。
- 基礎設施: AWS
- 應用程序(php): EC2 t2.micro
- Mysql : RDS t2.small - 100GB 磁儲存
這是我在生產中所做的測量
**左上角:**我們可以看到很多插入耗時超過 500 毫秒,有些甚至超過 1 秒。只有少數低於 50 毫秒。(紅條是第 95 個百分位)
右上角: RDS cpu 總是很低(記憶體使用量也很穩定)
**左下角:**表格中的插入數量
**右下角:**藍線是寫入 IOPS,紅線是寫入 lentency,藍條是 disk_queue_depth * 100。磁碟隊列幾乎總是低於 1。當其他一切看起來都很好時,我們可以看到寫入 lentency 的巨大峰值:IOPS 沒有增加也不是硬體資源消耗。
我創建了一個腳本來單獨測量插入時間
account_transaction
:// pseudo code for ($i=0; $i<100; $i++) { $ts=microtime(true); $db->insert(data); echo microtime(true)-$ts; }
100 次插入的輸出:(已排序)
0.012468814849854 0.012633085250854 0.012740135192871 0.012858867645264 0.012872934341431 ... ... ... 0.038048028945923 0.040112018585205 0.040735960006714 0.042028903961182 0.42462396621704 Avg : 0.025037434101105
我幾乎總是得到相同的結果:1% 或 2% 的插入非常慢。如果我執行 1000 次插入,則相同。
我試過的
禁用除 PK 之外的所有索引:沒有改進。
重建所有索引:沒有改進。
innodb_flush_log_at_trx_commit=0
: 很大的改進:
- 慢速插入完全消失
- 平均插入時間快 5 倍
切換到更大的 RDS(具有 100GB SSD 和 1000 IOPS 預配置的 m4.large):一些改進:
- 緩慢的插入幾乎消失了:如果有的話,可能是 0.1%,而且很少超過 100 毫秒
- 平均插入時間快 3 倍
不幸
innodb_flush_log_at_trx_commit=0
的是,這不是一個選擇,因為我不能承受任何交易的損失。我的想法
即使在基本硬體上,我希望插入在大多數情況下(<1ms)都會非常快。表只有 4 個索引和 200k 行。我認為這是錯誤的嗎?
由於測試,我懷疑瓶頸在 I/O 領域,
innodb_flush_log_at_trx_commit=0
但我從 AWS 獲得的指標似乎並不能證實這一點。我又錯了嗎?表 account_transaction
CREATE TABLE `account_transaction` ( `id` char(21) COLLATE utf8_unicode_ci NOT NULL, `seqid` int(9) unsigned NOT NULL AUTO_INCREMENT, `account_id` char(21) COLLATE utf8_unicode_ci NOT NULL, `token` bigint(20) unsigned DEFAULT NULL, ... ... `creation_date` datetime NOT NULL, `processing_date` datetime DEFAULT NULL, PRIMARY KEY (`seqid`), UNIQUE KEY `id` (`id`), KEY `consolidation_idx` (`trans_link`,`token`), KEY `idx_token` (`token`) ) ENGINE=InnoDB AUTO_INCREMENT=239304 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
id
看起來像1473430240HBCRSPL9FPB
= unix epoch + 10 個隨機字元:我們使用這種格式而不是標準 UUID 來保持良好的插入性能。始終為 21 個字元。
seqid
用於跟踪行號。我們id
有一個隨機部分,因此我們不能將其用於此目的。
select count(1) from account_transaction
= 239k問題
- 有沒有辦法消除高貸款峰值?
- 我應該使用更大的硬體來提高性能嗎?我的流量真的很低…
- 我必須確保 99.9% 的插入在 100 毫秒內完成。這在 RDS mysql 上是否現實?
- 我建議您距離 AWS 數據中心 12 毫秒(延遲)。你被這個成本困住了。結論:注意時間。
- 只有當客戶端和伺服器在同一個數據中心並且磁碟是 SSD 時,“<1ms”才是現實的。什麼是“磁儲存”?也許那是一個舊的、旋轉的磁碟。在這種情況下,10 毫秒是一次寫入的合理猜測。而且很難從可用的數字中確定延遲到底是什麼。(估計不到12ms。) 結論:一定要使用SSD。
- 亞馬遜對同一硬體上其他實例的干擾有何看法?你有一個“小”實例;可能有許多其他人共享相同的 I/O。如果其中一個有尖峰,您可能會放慢速度。使用“大”,您的競爭客戶可能會更少。它可能是SSD。結論:關注平均或第 95 個百分位,而不是峰值。
innodb_flush_log_at_trx_commit=1
每個事務至少 需要1 次磁碟寫入。=0
延遲和合併寫入,因此顯著加速。軟化它的一種方法是將多個查詢批處理到單個BEGIN
…COMMIT
中。(只有在邏輯合理的情況下才這樣做。) 結論:更大的交易。(但是,您仍然會遇到每個事務而不是每個插入12+ms 的問題。)- 對索引(索引除外
UNIQUE
)的更新會延遲,通常可以視為零影響。char(21) COLLATE utf8_unicode_ci
– 總是21個字元嗎?如果沒有,請使用VARCHAR
. 也許它總是ascii,而不是utf8?如果是這樣,請使用CHARACTER SET ascii
.PRIMARY KEY (
seqid), UNIQUE KEY
id(
id)
– 為什麼兩者都有?如前所述,它會導致時間中包含成本,即INSERT
在送出之前驗證唯一性。- 活動似乎很低。通常,當人們遇到每秒數百或數千個查詢的問題時,他們會問像您這樣的問題。
- 這是獲取 16 字節、按時間順序排列的 UUID 的方法:http: //mysql.rjweb.org/doc.php/uuid。(但不是那麼容易顯示。)