1. 程式人生 > >MySQL 對於千萬級的大表要怎麼優化?(讀寫分離、水平拆分、垂直拆分)

MySQL 對於千萬級的大表要怎麼優化?(讀寫分離、水平拆分、垂直拆分)

思考

如何設計或優化千萬級別的大表?此外無其他資訊,個人覺得這個話題有點範,就只好簡單說下該如何做,對於一個儲存設計,必須考慮業務特點,收集的資訊如下:

1.資料的容量:1-3年內會大概多少條資料,每條資料大概多少位元組;

2.資料項:是否有大欄位,那些欄位的值是否經常被更新; 
3.資料查詢SQL條件:哪些資料項的列名稱經常出現在WHERE、GROUP BY、ORDER BY子句中等; 
4.資料更新類SQL條件:有多少列經常出現UPDATE或DELETE 的WHERE子句中; 
5.SQL量的統計比,如:SELECT:UPDATE+DELETE:INSERT=多少?

6.預計大表及相關聯的SQL,每天總的執行量在何數量級? 
7.表中的資料:更新為主的業務 還是 查詢為主的業務 ?
8.打算採用什麼資料庫物理伺服器,以及資料庫伺服器架構? 
9.併發如何? 
10.儲存引擎選擇InnoDB還是MyISAM?

大致明白以上10個問題,至於如何設計此類的大表,應該什麼都清楚了!

至於優化若是指建立好的表,不能變動表結構的話,那建議InnoDB引擎,多利用點記憶體,減輕磁碟IO負載,因為IO往往是資料庫伺服器的瓶頸

另外對優化索引結構去解決效能問題的話,建議優先考慮修改類SQL語句,使他們更快些,不得已只靠索引組織結構的方式,當然此話前提是, 索引已經建立的非常好,若是讀為主,可以考慮開啟query_cache,以及調整一些引數值:sort_buffer_size,read_buffer_size,read_rnd_buffer_size,join_buffer_size

以及調整一些引數值:sort_buffer_size,read_buffer_size,read_rnd_buffer_size,join_buffer_size 

案列一

我現在的公司有三張表,是5億的資料,每天張表每天的增量是100w
每張表大概在10個columns左右
下面是我做的測試和對比
1.首先看engine,在大資料量情況下,在沒有做分割槽的情況下
mysiam比innodb在只讀的情況下,效率要高13%左右
2.在做了partition之後,你可以去讀一下mysql的官方文件,其實對於partition,專門是對myisam做的優化,對於innodb,所有的資料是存在ibdata裡面的,所以即使你可以看到schema變了,其實沒有本質的變化
在分割槽出於同一個physical disk下面的情況下,提升大概只有1%
在分割槽在不同的physical disk下,我分到了三個不同的disks下,提升大概在3%,其實所謂的吞吐量,由很多因素決定的,比如你的explain parition時候可以看到,record在那一個分割槽,如果每個分割槽都有,其實本質上沒有解決讀的問題,這樣只會提升寫的效率。
另外一個問題在於,分割槽,你怎麼分,如果一張表,有三個column都是經常被用於做查詢條件的,其實是一件很悲慘的事情,因為你沒有辦法對所有的sql做針對性的分割槽,如果你只是如mysql官方文件上說的,只對時間做一個分割槽,而且你也只用時間查詢的話,恭喜你
3.表主要用來讀還是寫,其實這個問題是不充分的,應該這樣問,你在寫入的時候,同時併發的查詢多麼?我的問題還比較簡單,因為mongodb的shredding支援不能,在crush之後,還是回到mysql,所以在通常情況下,9am-9pm,寫入的情況很多,這個時候我會做一個view,view是基於最近被插入或者經常被查詢的,通過做view來分離讀取,就是說寫是在table上的,讀在進行邏輯判斷前是在view上操作的
4做一些archive table,比如先對這些大表做很多已有的統計分析,然後通過已有的分析+增量來解決
5如果你用mysiam,還有一個問題你要注意,如果你的.configure的時候,加了一個max index length引數的時候,當你的record數大於制定長度的時候,這個index會被disable  

案列二

任何偏離業務場景的優化都是耍流氓,如果是訂單表,主要通過訂單id來查詢訂單資訊,則可以對這樣的表 進行垂直分庫,每個庫表容量500萬條,按訂單號維度 給拆分到多個庫,而在查詢的時候,使用訂單號查詢,通過某個業務規則,直接定位到要查詢的目標庫。或者通過使用者ID 、日期維度 進行分庫,但是千萬要注意,查詢時攜帶 分庫的條件。 如果是CRM系統 ,不直接使用訂單號直接查詢,而是一個範圍查詢,返回一個列表集合,而你還繼續執著於分庫分表就能解決你的效能問題,這樣你要對各個庫的查詢結果集進行union,資料庫的效能非但不能提高反而會適得其反!

解決方案

  • 首先,任何優化,都需要你瞭解你的業務,瞭解你的資料。
    • QPS要到多少?- 頻寬及儲存夠的情況下,單機幾千QPS妥妥的。
    • 讀寫比例如何?- 讀多寫少和寫多讀少,優化方法是有很大差別的。設置於只讀場景,果斷壓縮。
    • 資料是否快速增長?- 基本就是QPS的要求。
    • 資料及服務的SLA要到多少?- 資料需不需要強一致?HA做到什麼程度?
    • 諸如此類。

不同的場景有不同的側重,解決方案是不同的。而對於一些典型的場景可能會有成熟的解決方案。
題主已註明“千萬級”,因此以下假設題主為最常見的場景:大量資料,QPS要求高,讀多寫少,資料快速增長,SLA要求高

  • 其次,說優化的方法。

主要從三個維度說:Why, How, When。
0. sql vs nosql
有些跑題,但也是很重要的一方面。
Why: nosql天生分佈,而且大多針對某種型別的資料、某種使用場景做過優化。
比如大批量的監控資料,用mysql存費時費力,可以選擇mongo,甚至時間序列資料庫,存取會有量級提升。
How: 找對應解決方案。
When: 有足夠誘惑 - 針對使用場景,有成熟解決方案,效率獲得大量提升。
1. 優化shema、sql語句+索引
Why: 再好的MySQL架構也扛不住一個頻繁的垃圾查詢。不合理的schema設計也會導致資料存取慢。索引的作用不必多說,但如innodb下,錯的索引帶來的可能不只是查詢變慢而已。
How: 設計階段就需要預計QPS及資料規模,參考業務場景對資料的要求,合理設計表結構(參考mysql線上DDL問題),甚至違反設計正規化做到適當冗餘。生產環境分析慢日誌,優化語句。索引的設計需要知道索引是怎麼用的,比如innodb的加鎖機制。
When: 這個不僅僅是第一個要考慮的,而應該是需要持續去優化的。特別是要參考業務。但實際環境中如果是這個的問題,那一般比較幸運了,因為一般已經優化過很多了。實際中遇到的一般是更深的問題。
2. 快取
快取沒有那麼簡單。
快取對於應用不是完全透明的,除非你用Django這種成熟框架,而且快取粒度很大,但實際。。。像python,最少也得加幾個裝飾器。
如何保證快取裡面的資料是始終正確的?寫資料前失效快取還是寫資料後?
快取掛了或者過冷,流量壓到後端mysql了怎麼辦?
快取也不是萬能的。寫多讀少,命中率會很低。
How: memcache用做快取,redis用於需要持久化的場景。(redis能不能完全取代memcache?呵呵。。)
還可以使用mysql自帶的query cache,對應用基本完全透明。但會受限於本機。而且只快取查詢結果,mc和redis可以快取一些加工後的資料。
而且資料量大、QPS大的情況下,也需要考慮分片及HA的問題。如果有一個數據過熱,把一個節點壓垮了怎麼辦?
When: 基本上大多數讀多寫少的場景都能用,寫多的情況下可能需要考慮考慮。
3. 複製及讀寫分離(做主從複製或主主複製,讀寫分離,可以在應用層做,效率高,也可以用三方工具,第三方工具推薦360的atlas,其它的要麼效率不高,要麼沒人維護)
Why: 這個其實是大多數場景下都必須的。因為複製可以實現備份、高可用、負載均衡。就算嫌麻煩不做負載均衡,那備份下總是要的吧?既然已經備份了,何不加個LVS+HAProxy做下HA?順便稍微修改下應用,讀寫分離也就成了。
How: 節點少的情況下,主備。前面加Keepalived+HAProxy等元件,失效自動切換。讀寫分離可能需要修改下應用。
節點多的情況下,一是考慮多級備份,減輕主的壓力。其次可以引入第三方元件,接管主節點的備份工作。
主主不是很推薦。一是需要考慮資料衝突的情況,比如錯開id,同時操作資料後衝突解決。其次如果強一致會導致延遲增加,如果有節點掛了,需要等到超時才返回。
When: 主備幾乎大多數場景。甚至不論資料大小。高可用對應用透明,為啥不用?主主麻煩,建議先用切分。
4. 切分
包括垂直切分和水平切分,實現方式上又包括分庫、分表。
雖然有些難度,但還是推薦常用的。
Why: 垂直切分保證業務的獨立性,防止不同業務爭搶資源,畢竟業務是有優先順序的。
水平切分主要用於突破單機瓶頸。除了主主外,只有切分能真正做到將負載分配下去。
切分後也可對不同片資料進行不同優化。如按時間切分,超過一定時間資料不允許修改,就可以引入壓縮了,資料傳輸及讀取減少很多。
How: 根據業務垂直切分。業務內部分庫、分表。一般都需要修改應用。除分表外,其餘實現不是很複雜。有第三方元件可用,但通用高效又靈活的方式,還是自己寫client。
When: 垂直切分一般都要做,只不過業務粒度大小而已。
分庫有是經常用的,就算當前壓力小,也儘量分出幾個邏輯庫出來。等規模上去了,很方便就遷移擴充套件。
水平拆分有一定難度,但如果將來一定會到這個規模,又可能用到,建議越早做越好。因為對應用的改動較大,而且遷移成本高。

綜上,資料庫設計要面向現代化,面向世界,面向未來。。。

在一般運維的角度來看,我們什麼情況下需要考慮分庫分表?

首先說明,這裡所說的分庫分表是指把資料庫資料的物理拆分到多個例項或者多臺機器上去,而不是類似分割槽表的原地切分。

原則零:能不分就不分。

是的,MySQL 是關係資料庫,資料庫表之間的關係從一定的角度上映射了業務邏輯。任何分庫分表的行為都會在某種程度上提升業務邏輯的複雜度,資料庫除了承載資料的儲存和訪問外,協助業務更好的實現需求和邏輯也是其重要工作之一。分庫分表會帶來資料的合併,查詢或者更新條件的分離,事務的分離等等多種後果,業務實現的複雜程度往往會翻倍或者指數級上升。所以,在分庫分表之前,不要為分而分,去做其他力所能及的事情吧,例如升級硬體,升級,升級網路,升級資料庫版本,讀寫分離,負載均衡等等。所有分庫分表的前提是,這些你已經盡力了。

原則一:資料量太大,正常的運維影響正常業務訪問。

這裡說的運維,例如:

(1)對資料庫的備份。如果單表或者單個例項太大,在做備份的時候需要大量的磁碟IO或者網路IO資源。例如1T的資料,網路傳輸佔用50MB的時候,需要20000秒才能傳輸完畢,在此整個過程中的維護風險都是高於平時的。我們在Qunar的做法是給所有的資料庫機器新增第二塊網絡卡,用來做備份,或者SST,Group Communication等等各種內部的資料傳輸。1T的資料的備份,也會佔用大量的磁碟IO,如果是SSD還好,當然這裡忽略某些廠商的產品在集中IO的時候會出一些BUG的問題。如果是普通的物理磁碟,則在不限流的情況下去執行xtrabackup,該例項基本不可用。

(2)對資料表的修改。如果某個表過大,對此表做DDL的時候,MySQL會鎖住全表,這個時間可能很長,在這段時間業務不能訪問此表,影響甚大。解決的辦法有類似騰訊遊戲DBA自己改造的可以線上秒改表,不過他們目前也只是能新增欄位而已,對別的DDL還是無效;或者使用pt-online-schema-change,當然在使用過程中,它需要建立觸發器和影子表,同時也需要很長很長的時間,在此操作過程中的所有時間,都可以看做是風險時間。把資料表切分,總量減小,有助於改善這種風險。

(3)整個表熱點,資料訪問和更新頻繁,經常有鎖等待,你又沒有能力去修改原始碼,降低鎖的粒度,那麼只會把其中的資料物理拆開,用空間換時間,變相降低訪問壓力。

原則二:表設計不合理,需要對某些欄位垂直拆分

這裡舉一個例子,如果你有一個使用者表,在最初設計的時候可能是這樣:

table :users

id bigint 使用者的ID

name varchar 使用者的名字

last_login_time datetime 最近登入時間

personal_info text 私人資訊

xxxxx 其他資訊欄位。

一般的users表會有很多欄位,我就不列舉了。如上所示,在一個簡單的應用中,這種設計是很常見的。但是:

設想情況一:你的業務中彩了,使用者數從100w飆升到10個億。你為了統計活躍使用者,在每個人登入的時候都會記錄一下他的最近登入時間。並且的使用者活躍得很,不斷的去更新這個login_time,搞的你的這個表不斷的被update,壓力非常大。那麼,在這個時候,只要考慮對它進行拆分,站在業務的角度,最好的辦法是先把last_login_time拆分出去,我們叫它 user_time。這樣做,業務的程式碼只有在用到這個欄位的時候修改一下就行了。如果你不這麼做,直接把users表水平切分了,那麼,所有訪問users表的地方,都要修改。或許你會說,我有proxy,能夠動態merge資料。到目前為止我還從沒看到誰家的proxy不影響效能的。

設想情況二:personal_info這個欄位本來沒啥用,你就是讓使用者註冊的時候填一些個人愛好而已,基本不查詢。一開始的時候有它沒它無所謂。但是到後來發現兩個問題,一,這個欄位佔用了大量的空間,因為是text嘛,有很多人喜歡長篇大論地介紹自己。更糟糕的是二,不知道哪天哪個產品經理心血來潮,說允許個人資訊公開吧,以方便讓大家更好的相互瞭解。那麼在所有人獵奇窺私心理的影響下,對此欄位的訪問大幅度增加。資料庫壓力瞬間抗不住了,這個時候,只好考慮對這個表的垂直拆分了。

原則三:某些資料表出現了無窮增長

例子很好舉,各種的評論,訊息,日誌記錄。這個增長不是跟人口成比例的,而是不可控的,例如微博的feed的廣播,我發一條訊息,會擴散給很多很多人。雖然主體可能只存一份,但不排除一些索引或者路由有這種儲存需求。這個時候,增加儲存,提升機器配置已經蒼白無力了,水平切分是最佳實踐。拆分的標準很多,按使用者的,按時間的,按用途的,不在一一舉例。

原則四:安全性和可用性的考慮

這個很容易理解,雞蛋不要放在一個籃子裡,我不希望我的資料庫出問題,但我希望在出問題的時候不要影響到100%的使用者,這個影響的比例越少越好,那麼,水平切分可以解決這個問題,把使用者,庫存,訂單等等本來同統一的資源切分掉,每個小的資料庫例項承擔一小部分業務,這樣整體的可用性就會提升。這對Qunar這樣的業務還是比較合適的,人與人之間,某些庫存與庫存之間,關聯不太大,可以做一些這樣的切分。

原則五:業務耦合性考慮

這個跟上面有點類似,主要是站在業務的層面上,我們的火車票業務和烤羊腿業務是完全無關的業務,雖然每個業務的資料量可能不太大,放在一個MySQL例項中完全沒問題,但是很可能烤羊腿業務的DBA 或者開發人員水平很差,動不動給你出一些么蛾子,直接把資料庫搞掛。這個時候,火車票業務的人員雖然技術很優秀,工作也很努力,照樣被老闆打屁股。解決的辦法很簡單:惹不起,躲得起。

20條規則摘要如下:

規則1:一般情況可以選擇MyISAM儲存引擎,如果需要事務支援必須使用InnoDB儲存引擎。

規則2:命名規則。

規則3:資料庫欄位型別定義

  1. 經常需要計算和排序等消耗CPU的欄位,應該儘量選擇更為迅速的欄位,如用TIMESTAMP(4個位元組,最小值1970-01-01 00:00:00)代替Datetime(8個位元組,最小值1001-01-01 00:00:00),通過整型替代浮點型和字元型
  2. 變長欄位使用varchar,不要使用char
  3. 對於二進位制多媒體資料,流水佇列資料(如日誌),超大文字資料不要放在資料庫欄位中

規則4:業務邏輯執行過程必須讀到的表中必須要有初始的值。避免業務讀出為負或無窮大的值導致程式失敗

規則5:並不需要一定遵守正規化理論,適度的冗餘,讓Query儘量減少Join

規則6:訪問頻率較低的大欄位拆分出資料表。有些大欄位佔用空間多,訪問頻率較其他欄位明顯要少很多,這種情況進行拆分,頻繁的查詢中就不需要讀取大欄位,造成IO資源的浪費。

規則7: 水平分表,這個我還是建議 三思,搞不好非但不能提升效能反而多了很多的join和磁碟IO,開發起來也麻煩,有很多的業務就是要求一次查詢大部分的欄位 看你業務場景了。大表可以考慮水平拆分。大表影響查詢效率,根據業務特性有很多拆分方式,像根據時間遞增的資料,可以根據時間來分。以id劃分的資料,可根據id%資料庫個數的方式來拆分。

規則8:業務需要的相關索引是根據實際的設計所構造sql語句的where條件來確定的,業務不需要的不要建索引,不允許在聯合索引(或主鍵)中存在多於的欄位。特別是該欄位根本不會在條件語句中出現。

規則9:唯一確定一條記錄的一個欄位或多個欄位要建立主鍵或者唯一索引,不能唯一確定一條記錄,為了提高查詢效率建普通索引。

規則10:業務使用的表,有些記錄數很少,甚至只有一條記錄,為了約束的需要,也要建立索引或者設定主鍵。

規則11:對於取值不能重複,經常作為查詢條件的欄位,應該建唯一索引(主鍵預設唯一索引),並且將查詢條件中該欄位的條件置於第一個位置。沒有必要再建立與該欄位有關的聯合索引。

規則12:對於經常查詢的欄位,其值不唯一,也應該考慮建立普通索引,查詢語句中該欄位條件置於第一個位置,對聯合索引處理的方法同樣。

規則13:業務通過不唯一索引訪問資料時,需要考慮通過該索引值返回的記錄稠密度,原則上可能的稠密度最大不能高於0.2,如果稠密度太大,則不合適建立索引了。

規則14:需要聯合索引(或聯合主鍵)的資料庫要注意索引的順序。SQL語句中的匹配條件也要跟索引的順序保持一致。

注意:索引的順勢不正確也可能導致嚴重的後果。

規則15:表中的多個欄位查詢作為查詢條件,不含有其他索引,並且欄位聯合值不重複,可以在這多個欄位上建唯一的聯合索引,假設索引欄位為 (a1,a2,...an),則查詢條件(a1 op val1,a2 op val2,...am op valm)m<=n,可以用到索引,查詢條件中欄位的位置與索引中的欄位位置是一致的。

規則16:聯合索引的建立原則(以下均假設在資料庫表的欄位a,b,c上建立聯合索引(a,b,c))。

規則17:重要業務訪問資料表時。但不能通過索引訪問資料時,應該確保順序訪問的記錄數目是有限的,原則上不得多於10。

規則18:合理構造Query語句,慢SQL監控,檢查是否有大量的的子查詢和關聯查詢 巢狀查詢等,儘量避免使用這些查詢,使用連線(JOIN)來代替子查詢(Sub-Queries),使用聯合(UNION)來代替手動建立的臨時表。

規則19:應用系統的優化。

規則20:可以結合redis,memcache等快取服務,把這些複雜的sql進行拆分,充分利用二級快取,減少資料庫IO操作。對資料庫連線池,mybatis,hiberante二級快取充分利用上。儘量使用順序IO代替隨機IO。合理使用索引,儘量避免全表掃描。