https://cloud.tencent.com/developer/article/1999970
個人創作公約:本人宣告創作的所有文章皆為自己原創,如果有參考任何文章的地方,會標註出來,如果有疏漏,歡迎大家批判。如果大家發現網上有抄襲本文章的,歡迎舉報,並且積極向這個 github 倉庫 提交 issue,謝謝支援~
本文是“為什麼我建議”系列第三篇,本系列中會針對一些在高併發場景下,我對於組內後臺開發的一些開發建議以及開發規範的要求進行說明和分析解讀,相信能讓各位在面對高併發業務的時候避開一些坑。 往期回顧:
- 為什麼我建議在複雜但是效能關鍵的表上所有查詢都加上 force index
- 為什麼我建議線上高併發量的日誌輸出的時候不能帶有程式碼位置
一般現在對於業務要查詢的資料量以及要保持的併發量高於一定配置的單例項 MySQL 的極限的情況,都會採取分庫分表的方案解決。當然,現在也有很多 new SQL 的分散式資料庫的解決方案,如果你用的是 MySQL,那麼你可以考慮 TiDB(實現了 MySQL 協議,相容 MySQL 客戶端以及 SQL 語句)。如果你用的是的 PgSQL,那麼你可以考慮使用 YugaByteDB(實現了 PgSQL 協議,相容 PgSQL 客戶端以及 SQL 語句),他們目前都有自己的雲部署解決方案,你可以試試:
- TiDB Cloud
- YugaByte Cloud
但是對於傳統分庫分表的專案,底層的資料庫還是基於 MySQL 以及 PgSQL 這樣的傳統關係型資料庫。一般在業務剛開始的時候,會考慮按照某個分片鍵多分一些表,例如訂單表,我們估計使用者直接要查的訂單記錄是最近一年內的。如果是一年前的,提供其他入口去查,這時候查的就不是有業務資料庫了,而是歸檔資料庫,例如 HBase 這樣的。例如我們估計一年內使用者訂單,最多不會超過 10 億,更新的併發 TPS (非查詢 QPS)不會超過 10 萬/s。那麼我們可以考慮分成 64 張表(個數最好是 2^n,因為 2^n 取餘數 = 對 2^n - 1 取與運算,減少分片鍵運算量)。然後我們還會定時的歸檔掉一年前的資料,使用類似於 delete from table 這樣的語句進行“徹底刪除”(注意這裡是引號的刪除)。這樣保證業務表的資料量級一直維持在
然而,日久天長以後,會發現,某些帶分片鍵(這裡就是使用者 id)的普通查詢,也會有些慢,有些走錯本地索引。
查詢越來越慢的原因
例如這個 SQL:
select * from t_pay_record
WHERE
((
user_id = 'user_id1'
AND is_del = 0
))
ORDER BY
id DESC
LIMIT 20
這個表的分片鍵就是 user_id
一方面,正如我在“為什麼我建議在複雜但是效能關鍵的表上所有查詢都加上 force index”中說的,資料量可能有些超出我們的預期,導致某些分片表大於一定界限,導致 MySQL 對於索引的隨機取樣越來越不準,由於統計資料不是實時更新,而是更新的行數超過一定比例才會開始更新。並且統計資料不是全量統計,是抽樣統計。所以在表的資料量很大的時候,這個統計資料很難非常準確。依靠表本身自動重新整理資料機制,引數比較難以調整(主要是 STATS_SAMPLE_PAGES
這個引數,STATS_PERSISTENT
我們一般不會改,我們不會能接受在記憶體中儲存,這樣萬一資料庫重啟,表就要重新分析,這樣減慢啟動時間,STATS_AUTO_RECALC
我們也不會關閉,這樣會導致最佳化器分析的越來越不準確),很難預測出到底調整到什麼數值最合適。並且業務的增長,使用者的行為導致的資料的傾斜,也是很難預測的。透過 Alter Table 修改某個表的 STATS_SAMPLE_PAGES
的時候,會導致和 Analyze 這個 Table 一樣的效果,會在表上加讀鎖,會阻塞表上的更新以及事務。所以不能在這種線上業務關鍵表上面使用。所以最好一開始就能估計出大表的量級,但是這個很難。
所以,我們考慮對於資料量比較大的表,最好能提前透過分庫分表控制每個表的資料量,但是業務增長與產品需求都是不斷在迭代並且變複雜的。很難保證不會出現大並且索引比較複雜的表。這種情況下需要我們,在適當調高 STATS_SAMPLE_PAGES
的前提下,對於一些使用者觸發的關鍵查詢 SQL,使用 force index 引導它走正確的索引。
但是,有時候即使索引走對了,查詢依然有點慢。具體去看這個 SQL 掃描的資料行數的時候,發現並沒有很多。
+----+-------------+--------------+------------+-------+-----------------------------------------------------------------------------------------+-------------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+-------+-----------------------------------------------------------------------------------------+-------------+---------+------+-------+----------+----------