千萬級大表如何優化,這是一個頗有技術含量的問題,一般咱們的直覺思惟都會跳轉到拆分或者數據分區,在此我想作一些補充和梳理,想和你們作一些這方面的經驗總結,也歡迎你們提出建議。 數據庫
從一開始腦海裏開始也是火光四現,到不斷的自我批評,後來也參考了一些團隊的經驗,我整理了下面的大綱內容。緩存
既然要吃透這個問題,咱們勢必要回到本源,我把這個問題分爲三部分:性能優化
「千萬級」,「大表」,「優化」,架構
也分別對應咱們在圖中標識的負載均衡
「數據量」,「對象」和「目標」。運維
我來逐步展開說明一下,從而給出一系列的解決方案。 異步
1.數據量:千萬級分佈式
千萬級其實只是一個感官的數字,就是咱們印象中的數據量大。 這裏咱們須要把這個概念細化,由於隨着業務和時間的變化,數據量也會有變化,咱們應該是帶着一種動態思惟來審視這個指標,從而對於不一樣的場景咱們應該有不一樣的處理策略。函數
1) 數據量爲千萬級,可能達到億級或者更高工具
一般是一些數據流水,日誌記錄的業務,裏面的數據隨着時間的增加會逐步增多,超過千萬門檻是很容易的一件事情。
2) 數據量爲千萬級,是一個相對穩定的數據量
若是數據量相對穩定,一般是在一些偏向於狀態的數據,好比有1000萬用戶,那麼這些用戶的信息在表中都有相應的一行數據記錄,隨着業務的增加,這個量級相對是比較穩定的。
3) 數據量爲千萬級,不該該有這麼多的數據
這種狀況是咱們被動發現的居多,一般發現的時候已經晚了,好比你看到一個配置表,數據量上千萬;或者說一些表裏的數據已經存儲了好久,99%的數據都屬於過時數據或者垃圾數據。
數據量是一個總體的認識,咱們須要對數據作更近一層的理解,這就能夠引出第二個部分的內容。
2.對象:數據表
數據操做的過程就比如數據庫中存在着多條管道,這些管道中都流淌着要處理的數據,這些數據的用處和歸屬是不同的。
通常根據業務類型把數據分爲三種:
(1)流水型數據
流水型數據是無狀態的,多筆業務之間沒有關聯,每次業務過來的時候都會產生新的單據,好比交易流水、支付流水,只要能插入新單據就能完成業務,特色是後面的數據不依賴前面的數據,全部的數據按時間流水進入數據庫。
(2)狀態型數據
狀態型數據是有狀態的,多筆業務之間依賴於有狀態的數據,並且要保證該數據的準確性,好比充值時必需要拿到原來的餘額,才能支付成功。
(3)配置型數據
此類型數據數據量較小,並且結構簡單,通常爲靜態數據,變化頻率很低。
至此,咱們能夠對總體的背景有一個認識了,若是要作優化,其實要面對的是這樣的3*3的矩陣,若是要考慮表的讀寫比例(讀多寫少,讀少寫多...),那麼就會是3*3*4=24種,顯然作窮舉是不顯示的,並且也徹底沒有必要,能夠針對不一樣的數據存儲特性和業務特色來指定不一樣的業務策略。
對此咱們採起抓住重點的方式,把常見的一些優化思路梳理出來,尤爲是裏面的核心思想,也是咱們整個優化設計的一把尺子,而難度決定了咱們作這件事情的動力和風險。
數據量增加狀況 |
數據表類型 |
業務特色 |
優化核心思想 |
優化難度 |
數據量爲千萬級,是一個相對穩定的數據量 |
狀態表 |
OLTP業務方向 |
能不拆就不拆讀需求水平擴展 |
**** |
數據量爲千萬級,可能達到億級或者更高 |
流水錶 |
OLTP業務的歷史記錄 |
業務拆分,面向分佈式存儲設計 |
**** |
OLAP業務統計數據源 |
設計數據統計需求存儲的分佈式擴展 |
*** |
||
數據量爲千萬級,不該該有這麼多的數據 |
配置表 |
通用業務 |
小而簡,避免大一統 |
* |
而對於優化方案,我想採用面向業務的維度來進行闡述。
3.目標:優化
在這個階段,咱們要說優化的方案了,總結的有點多,相對來講是比較全了。
總體分爲五個部分:
其實咱們一般所說的分庫分表等方案只是其中的一小部分,若是展開以後就比較豐富了。
其實不難理解,咱們要支撐的表數據量是千萬級別,相對來講是比較大了,DBA要維護的表確定不止一張,如何可以更好的管理,同時在業務發展中可以支撐擴展,同時保證性能,這是擺在咱們面前的幾座大山。
咱們分別來講一下這五類改進方案:
優化設計方案1.規範設計
在此咱們先提到的是規範設計,而不是其餘高大上的設計方案。
黑格爾說:秩序是自由的第一條件。在分工協做的工做場景中尤爲重要,不然團隊之間互相牽制太多,問題多多。
規範設計我想提到以下的幾個規範,其實只是屬於開發規範的一部份內容,能夠做爲參考。
規範的本質不是解決問題,而是有效杜絕一些潛在問題,對於千萬級大表要遵照的規範,我梳理了以下的一些細則,基本能夠涵蓋咱們常見的一些設計和使用問題,好比表的字段設計無論三七二十一,都是varchar(500),實際上是很不規範的一種實現方式,咱們來展開說一下這幾個規範。
1)配置規範
(1)MySQL數據庫默認使用InnoDB存儲引擎。
(2)保證字符集設置統一,MySQL數據庫相關係統、數據庫、表的字符集使都用UTF8,應用程序鏈接、展現等能夠設置字符集的地方也都統一設置爲UTF8字符集。
注:UTF8格式是存儲不了表情類數據,須要使用UTF8MB4,可在MySQL字符集裏面設置。在8.0中已經默認爲UTF8MB4,能夠根據公司的業務狀況進行統一或者定製化設置。
(3)MySQL數據庫的事務隔離級別默認爲RR(Repeatable-Read),建議初始化時統一設置爲RC(Read-Committed),對於OLTP業務更適合。
(4)數據庫中的表要合理規劃,控制單表數據量,對於MySQL數據庫來講,建議單表記錄數控制在2000W之內。
(5)MySQL實例下,數據庫、表數量儘量少;數據庫通常不超過50個,每一個數據庫下,數據表數量通常不超過500個(包括分區表)。
2)建表規範
(1)InnoDB禁止使用外鍵約束,能夠經過程序層面保證。
(2)存儲精確浮點數必須使用DECIMAL替代FLOAT和DOUBLE。
(3)整型定義中無需定義顯示寬度,好比:使用INT,而不是INT(4)。
(4)不建議使用ENUM類型,可以使用TINYINT來代替。
(5)儘量不使用TEXT、BLOB類型,若是必須使用,建議將過大字段或是不經常使用的描述型較大字段拆分到其餘表中;另外,禁止用數據庫存儲圖片或文件。
(6)存儲年時使用YEAR(4),不使用YEAR(2)。
(7)建議字段定義爲NOT NULL。
(8)建議DBA提供SQL審覈工具,建表規範性須要經過審覈工具審覈後
3)命名規範
(1)庫、表、字段所有采用小寫。
(2)庫名、表名、字段名、索引名稱均使用小寫字母,並以「_」分割。
(3)庫名、表名、字段名建議不超過12個字符。(庫名、表名、字段名支持最多64個字符,但爲了統一規範、易於辨識以及減小傳輸量,統一不超過12字符)
(4)庫名、表名、字段名見名知意,不須要添加註釋。
對於對象命名規範的一個簡要總結以下表4-1所示,供參考。
命名列表
對象中文名稱 |
對象英文全稱 |
MySQL對象簡寫 |
視圖 |
view |
view_ |
函數 |
function |
func_ |
存儲過程 |
procedure |
proc_ |
觸發器 |
trigger |
trig_ |
普通索引 |
index |
idx_ |
惟一索引 |
unique index |
uniq_ |
主鍵索引 |
primary key |
pk_ |
4)索引規範
(1)索引建議命名規則:idx_col1_col2[_colN]、uniq_col1_col2[_colN](若是字段過長建議採用縮寫)。
(2)索引中的字段數建議不超過5個。
(3)單張表的索引個數控制在5個之內。
(4)InnoDB表通常都建議有主鍵列,尤爲在高可用集羣方案中是做爲必須項的。
(5)創建複合索引時,優先將選擇性高的字段放在前面。
(6)UPDATE、DELETE語句須要根據WHERE條件添加索引。
(7)不建議使用%前綴模糊查詢,例如LIKE 「%weibo」,沒法用到索引,會致使全表掃描。
(8)合理利用覆蓋索引,例如:
(9)SELECT email,uid FROM user_email WHERE uid=xx,若是uid不是主鍵,能夠建立覆蓋索引idx_uid_email(uid,email)來提升查詢效率。
(10)避免在索引字段上使用函數,不然會致使查詢時索引失效。
(11)確認索引是否須要變動時要聯繫DBA。
5)應用規範
(1)避免使用存儲過程、觸發器、自定義函數等,容易將業務邏輯和DB耦合在一塊兒,後期作分佈式方案時會成爲瓶頸。
(2)考慮使用UNION ALL,減小使用UNION,由於UNION ALL不去重,而少了排序操做,速度相對比UNION要快,若是沒有去重的需求,優先使用UNION ALL。
(3)考慮使用limit N,少用limit M,N,特別是大表或M比較大的時候。
(4)減小或避免排序,如:group by語句中若是不須要排序,能夠增長order by null。
(5)統計表中記錄數時使用COUNT(*),而不是COUNT(primary_key)和COUNT(1);InnoDB表避免使用COUNT(*)操做,計數統計實時要求較強可使用Memcache或者Redis,非實時統計可使用單獨統計表,定時更新。
(6)作字段變動操做(modify column/change column)的時候必須加上原有的註釋屬性,不然修改後,註釋會丟失。
(7)使用prepared statement能夠提升性能而且避免SQL注入。
(8)SQL語句中IN包含的值不該過多。
(9)UPDATE、DELETE語句必定要有明確的WHERE條件。
(10)WHERE條件中的字段值須要符合該字段的數據類型,避免MySQL進行隱式類型轉化。
(11)SELECT、INSERT語句必須顯式的指明字段名稱,禁止使用SELECT * 或是INSERT INTO table_name values()。
(12)INSERT語句使用batch提交(INSERT INTO table_name VALUES(),(),()……),values的個數不該過多。
優化設計方案2:業務層優化
業務層優化應該是收益最高的優化方式了,並且對於業務層徹底可見,主要有業務拆分,數據拆分和兩類常見的優化場景(讀多寫少,讀少寫多)
1)業務拆分
ü 將混合業務拆分爲獨立業務
ü 將狀態和歷史數據分離
業務拆分實際上是把一個混合的業務剝離成爲更加清晰的獨立業務,這樣業務1,業務2。。。獨立的業務使得業務總量依舊很大,可是每一個部分都是相對獨立的,可靠性依然有保證。
對於狀態和歷史數據分離,我能夠舉一個例子來講明。
例如:咱們有一張表Account,假設用戶餘額爲100。
咱們須要在發生數據變動後,可以追溯數據變動的歷史信息,若是對帳戶更新狀態數據,增長100的餘額,這樣餘額爲200。
這個過程可能對應一條update語句,一條insert語句。
對此咱們能夠改造爲兩個不一樣的數據源,account和account_hist
在account_hist中就會是兩條insert記錄,以下:
而在account中則是一條update語句,以下:
這也是一種很基礎的冷熱分離,能夠大大減小維護的複雜度,提升業務響應效率。
2)數據拆分
2.1 按照日期拆分,這種使用方式比較廣泛,尤爲是按照日期維度的拆分,其實在程序層面的改動很小,可是擴展性方面的收益很大。
-
數據按照日期維度拆分,如test_20191021
-
數據按照周月爲維度拆分,如test_201910
-
數據按照季度,年維度拆分,如test_2019
2.2 採用分區模式,分區模式也是常見的使用方式,採用hash,range等方式會多一些,在MySQL中我是不大建議使用分區表的使用方式,由於隨着存儲容量的增加,數據雖然作了垂直拆分,可是歸根結底,數據其實難以實現水平擴展,在MySQL中是有更好的擴展方式。
2.3 讀多寫少優化場景
採用緩存,採用Redis技術,將讀請求打在緩存層面,這樣能夠大大下降MySQL層面的熱點數據查詢壓力。
2.4 讀少寫多優化場景,能夠採用三步走:
1) 採用異步提交模式,異步對於應用層來講最直觀的就是性能的提高,產生最少的同步等待。
2) 使用隊列技術,大量的寫請求能夠經過隊列的方式來進行擴展,實現批量的數據寫入。
3) 下降寫入頻率,這個比較難理解,我舉個例子
對於業務數據,好比積分類,相比於金額來講業務優先級略低的場景,若是數據的更新過於頻繁,能夠適度調整數據更新的範圍(好比從原來的每分鐘調整爲10分鐘)來減小更新的頻率。
例如:更新狀態數據,積分爲200,以下圖所示
能夠改造爲,以下圖所示。
若是業務數據在短期內更新過於頻繁,好比1分鐘更新100次,積分從100到10000,則能夠根據時間頻率批量提交。
例如:更新狀態數據,積分爲100,以下圖所示。
無需生成100個事務(200條SQL語句)能夠改造爲2條SQL語句,以下圖所示。
對於業務指標,好比更新頻率細節信息,能夠根據具體業務場景來討論決定。
優化設計方案3:架構層優化
架構層優化其實就是咱們認爲的那種技術含量很高的工做,咱們須要根據業務場景在架構層面引入一些新的花樣來。
3.1.系統水平擴展場景
3.1.1採用中間件技術,能夠實現數據路由,水平擴展,常見的中間件有MyCAT,ShardingSphere,ProxySQL等
3.1.2 採用讀寫分離技術,這是針對讀需求的擴展,更側重於狀態表,在容許必定延遲的狀況下,能夠採用多副本的模式實現讀需求的水平擴展,也能夠採用中間件來實現,如MyCAT,ProxySQL,MaxScale,MySQL Router等
3.1.3 採用負載均衡技術,常見的有LVS技術或者基於域名服務的Consul技術等
3.2.兼顧OLTP+OLAP的業務場景,能夠採用NewSQL,優先兼容MySQL協議的HTAP技術棧,如TiDB
3.3.離線統計的業務場景,有幾類方案可供選擇。
3.3.1 採用NoSQL體系,主要有兩類,一類是適合兼容MySQL協議的數據倉庫體系,常見的有Infobright或者ColumnStore,另一類是基於列式存儲,屬於異構方向,如HBase技術
3.3.2 採用數倉體系,基於MPP架構,如使用Greenplum統計,如T+1統計
優化設計方案4:數據庫優化
數據庫優化,其實可打的牌也很多,可是相對來講空間沒有那麼大了,咱們來逐個說一下。
4.1 事務優化
根據業務場景選擇事務模型,是不是強事務依賴
對於事務降維策略,咱們來舉出幾個小例子來。
4.1.1 降維策略1:存儲過程調用轉換爲透明的SQL調用
對於新業務而言,使用存儲過程顯然不是一個好主意,MySQL的存儲過程和其餘商業數據庫相比,功能和性能都有待驗證,並且在目前輕量化的業務處理中,存儲過程的處理方式太「重」了。
有些應用架構看起來是按照分佈式部署的,但在數據庫層的調用方式是基於存儲過程,由於存儲過程封裝了大量的邏輯,難以調試,並且移植性不高,這樣業務邏輯和性能壓力都在數據庫層面了,使得數據庫層很容易成爲瓶頸,並且難以實現真正的分佈式。
因此有一個明確的改進方向就是對於存儲過程的改造,把它改造爲SQL調用的方式,能夠極大地提升業務的處理效率,在數據庫的接口調用上足夠簡單並且清晰可控。
4.1.2 降維策略2:DDL操做轉換爲DML操做
有些業務常常會有一種緊急需求,老是須要給一個表添加字段,搞得DBA和業務同窗都挺累,能夠想象一個表有上百個字段,並且基本都是name1,name2……name100,這種設計自己就是有問題的,更不用考慮性能了。究其緣由,是由於業務的需求動態變化,好比一個遊戲裝備有20個屬性,可能過了一個月以後就增長到了40個屬性,這樣一來,全部的裝備都有40個屬性,無論用沒用到,並且這種方式也存在諸多的冗餘。
咱們在設計規範裏面也提到了一些設計的基本要素,在這些基礎上須要補充的是,保持有限的字段,若是要實現這些功能的擴展,其實徹底能夠經過配置化的方式來實現,好比把一些動態添加的字段轉換爲一些配置信息。配置信息能夠經過DML的方式進行修改和補充,對於數據入口也能夠更加動態、易擴展。
4.1.3 降維策略3:Delete操做轉換爲高效操做
有些業務須要按期來清理一些週期性數據,好比表裏的數據只保留一個月,那麼超出時間範圍的數據就要清理掉了,而若是表的量級比較大的狀況下,這種Delete操做的代價實在過高,咱們能夠有兩類解決方案來把Delete操做轉換爲更爲高效的方式。
第一種是根據業務創建週期表,好比按照月表、周表、日表等維度來設計,這樣數據的清理就是一個相對可控並且高效的方式了。
第二種方案是使用MySQL rename的操做方式,好比一張2千萬的大表要清理99%的數據,那麼須要保留的1%的數據咱們能夠很快根據條件過濾補錄,實現「移形換位」。
4.2 SQL優化
其實相對來講須要的極簡的設計,不少點都在規範設計裏面了,若是遵照規範,八九不離十的問題都會杜絕掉,在此補充幾點:
4.2.1 SQL語句簡化,簡化是SQL優化的一大利器,由於簡單,因此優越。
4.2.2 儘量避免或者杜絕多表複雜關聯,大表關聯是大表處理的噩夢,一旦打開了這個口子,愈來愈多的需求須要關聯,性能優化就沒有回頭路了,更況且大表關聯是MySQL的弱項,儘管Hash Join才推出,不要像掌握了絕對大殺器同樣,在商業數據庫中早就存在,問題照樣層出不窮。
4.2.3 SQL中儘量避免反鏈接,避免半鏈接,這是優化器作得薄弱的一方面,什麼是反鏈接,半鏈接?其實比較好理解,舉個例子,not in ,not exists就是反鏈接,in,exists就是半鏈接,在千萬級大表中出現這種問題,性能是幾個數量級的差別。
4.3 索引優化
應該是大表優化中須要把握的一個度。
4.3.1 首先必須有主鍵,規範設計中第一條就是,此處不接收反駁。
4.3.2 其次,SQL查詢基於索引或者惟一性索引,使得查詢模型儘量簡單。
4.3.3 最後,儘量杜絕範圍數據的查詢,範圍掃描在千萬級大表狀況下仍是儘量減小。
優化設計方案4:管理優化
這部分應該是在全部的解決方案中最容易被忽視的部分了,我放在最後,在此也向運維同事致敬,老是爲不少認爲本應該正常的問題盡職盡責(背鍋)。
千萬級大表的數據清理通常來講是比較耗時的,在此建議在設計中須要完善冷熱數據分離的策略,可能聽起來比較拗口,我來舉一個例子,把大表的Drop 操做轉換爲可逆的DDL操做。
Drop操做是默認提交的,並且是不可逆的,在數據庫操做中都是跑路的代名詞,MySQL層面目前沒有相應的Drop操做恢復功能,除非經過備份來恢復,可是咱們能夠考慮將Drop操做轉換爲一種可逆的DDL操做。
MySQL中默認每一個表有一個對應的ibd文件,其實能夠把Drop操做轉換爲一個rename操做,即把文件從testdb遷移到testdb_arch下面;從權限上來講,testdb_arch是業務不可見的,rename操做能夠平滑的實現這個刪除功能,若是在必定時間後確承認以清理,則數據清理對於已有的業務流程是不可見的,以下圖所示。
此外,還有兩個額外建議,一個是對於大表變動,儘量考慮低峯時段的在線變動,好比使用pt-osc工具或者是維護時段的變動,就再也不贅述了。
最後總結一下,其實就是一句話:
千萬級大表的優化是根據業務場景,以成本爲代價進行優化的,絕對不是孤立的一個層面的優化。