1. 程式人生 > >MySQL 對於千萬級的大表要怎麼優化?

MySQL 對於千萬級的大表要怎麼優化?

https://www.zhihu.com/question/19719997
 

千萬級,MySQL實際上確實不是什麼壓力,InnoDB的儲存引擎,使用的是B+樹儲存結構,千萬級的資料量,基本也就是三到四層的搜尋,如果有合適的索引,效能基本也不是問題。

但經常出現的情況是,業務上面的增長,導致資料量還會繼續增長,為了應對這方面的問題而必須要做擴充套件了此時可能首先需要考慮的就是分表策略了。

當然分表,可能還有其它幾個原因,比如表變大了,千萬級的資料庫,為了減少運維成本,降低風險,就想到了通過分表來解決問題,這都是比較合適的。

分表,還有另一個方面的意思,就是在資料量更大的情況下,為了分擔業務壓力,將資料表分到不同的例項中去,這樣有兩方面的好處:1. 降低業務風險,如果一套資料庫叢集出問題了,那至少還有其它的可以服務,這樣被影響的業務可能只是一部分。2. 降低運維成本,如果資料庫想要做遷移,或者正常維護等操作了,那涉及到的資料量小,下線時間短,操作快,從而對業務影響也就小了。這種方式,我們稱之為“分例項”。

分表的話,還是要根據具體的業務邏輯等方面來做,這方面有更精彩的回答,我這裡貼一下:

========================================

分庫分表是MySQL永遠的話題,一般情況下認為MySQL是個簡單的資料庫,在資料量大到一定程度之後處理查詢的效率降低,如果需要繼續保持高效能運轉的話,必須分庫或者分表了。關於資料量達到多少大是個極限這個事兒,本文先不討論,研究原始碼的同學已經證實MySQL或者Innodb內部的鎖粒度太大的問題大大限制了MySQL提供QPS的能力或者處理大規模資料的能力。在這點上,一般的使用者只好坐等官方不斷推出的優化版本了。

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

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

原則零:能不分就不分。

是的,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 或者開發人員水平很差,動不動給你出一些么蛾子,直接把資料庫搞掛。這個時候,火車票業務的人員雖然技術很優秀,工作也很努力,照樣被老闆打屁股。解決的辦法很簡單:惹不起,躲得起。

《三國演義》第一回:“話說天下大勢,分久必合,合久必分。”其實在實踐中,有時候可能你原本要分,後來又發現分了還得合,分分合合,完全是現實的需求,隨需而變才是王道,而DBA的價值也能在此體現。或分或合的情況太多,不能窮舉,歡迎繼續交流這個話題,如果以上有錯誤之後,也請批評指正。

給生活加點料。

================================

文章摘自微信公眾號formysql。

如何分表的方案,其實這個不能一概而論,與業務邏輯有關係,與資料性質有關係,比如訂單型別的,那就非常容易了,通過時間這個特性,可以通過一個路由表,把資料分散到多個例項上面,或者多個表上面,擴充套件性非常強,但是如果是使用者關係等類似的表,他的唯一可以做HASH的值就是使用者ID,做HASH時,涉及到不均勻、可擴充套件能力,遷移麻煩等問題,所以還是不太容易的,所以只能是具體問題具體分析了。

上面說的是分表的優化方案,當然還有其它方案,那就是要儘可能的寫好SQL語句,不要留坑,MySQL就是適合那種快進快出的語句,儘可能的別把業務邏輯放到MySQL中去處理,要保持MySQL的高效執行才是最正確的選擇。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

優化順序:優化sql和索引 > 加快取  memcached,redis > 主從複製或主主複製,讀寫分離 > mysql分割槽表 > 垂直分割槽 > 水平分割槽

很多人第一反應是各種切分;我給的順序是:
第一優化你的sql和索引;

第二加快取,memcached,redis;

第三以上都做了後,還是慢,就做主從複製或主主複製,讀寫分離,可以在應用層做,效率高,也可以用三方工具,第三方工具推薦360的atlas,其它的要麼效率不高,要麼沒人維護;

第四如果以上都做了還是慢,不要想著去做切分,mysql自帶分割槽表,先試試這個,對你的應用是透明的,無需更改程式碼,但是sql語句是需要針對分割槽表做優化的,sql條件中要帶上分割槽條件的列,從而使查詢定位到少量的分割槽上,否則就會掃描全部分割槽,另外分割槽表還有一些坑,在這裡就不多說了;

第五如果以上都做了,那就先做垂直拆分,其實就是根據你模組的耦合度,將一個大的系統分為多個小的系統,也就是分散式系統;

第六才是水平切分,針對資料量大的表,這一步最麻煩,最能考驗技術水平,要選擇一個合理的sharding key,為了有好的查詢效率,表結構也要改動,做一定的冗餘,應用也要改,sql中儘量帶sharding key,將資料定位到限定的表上去查,而不是掃描全部的表;

mysql資料庫一般都是按照這個步驟去演化的,成本也是由低到高;

有人也許要說第一步優化sql和索引這還用說嗎?的確,大家都知道,但是很多情況下,這一步做的並不到位,甚至有的只做了根據sql去建索引,根本沒對sql優化(中槍了沒?),除了最簡單的增刪改查外,想實現一個查詢,可以寫出很多種查詢語句,不同的語句,根據你選擇的引擎、表中資料的分佈情況、索引情況、資料庫優化策略、查詢中的鎖策略等因素,最終查詢的效率相差很大;優化要從整體去考慮,有時你優化一條語句後,其它查詢反而效率被降低了,所以要取一個平衡點;即使精通mysql的話,除了純技術面優化,還要根據業務面去優化sql語句,這樣才能達到最優效果;你敢說你的sql和索引已經是最優了嗎?

再說一下不同引擎的優化,myisam讀的效果好,寫的效率差,這和它資料儲存格式,索引的指標和鎖的策略有關的,它的資料是順序儲存的(innodb資料儲存方式是聚簇索引),他的索引btree上的節點是一個指向資料物理位置的指標,所以查詢起來很快,(innodb索引節點存的則是資料的主鍵,所以需要根據主鍵二次查詢);myisam鎖是表鎖,只有讀讀之間是併發的,寫寫之間和讀寫之間(讀和插入之間是可以併發的,去設定concurrent_insert引數,定期執行表優化操作,更新操作就沒有辦法了)是序列的,所以寫起來慢,並且預設的寫優先順序比讀優先順序高,高到寫操作來了後,可以馬上插入到讀操作前面去,如果批量寫,會導致讀請求餓死,所以要設定讀寫優先順序或設定多少寫操作後執行讀操作的策略;myisam不要使用查詢時間太長的sql,如果策略使用不當,也會導致寫餓死,所以儘量去拆分查詢效率低的sql,

innodb一般都是行鎖,這個一般指的是sql用到索引的時候,行鎖是加在索引上的,不是加在資料記錄上的,如果sql沒有用到索引,仍然會鎖定表,mysql的讀寫之間是可以併發的,普通的select是不需要鎖的,當查詢的記錄遇到鎖時,用的是一致性的非鎖定快照讀,也就是根據資料庫隔離級別策略,會去讀被鎖定行的快照,其它更新或加鎖讀語句用的是當前讀,讀取原始行;因為普通讀與寫不衝突,所以innodb不會出現讀寫餓死的情況,又因為在使用索引的時候用的是行鎖,鎖的粒度小,競爭相同鎖的情況就少,就增加了併發處理,所以併發讀寫的效率還是很優秀的,問題在於索引查詢後的根據主鍵的二次查詢導致效率低;

ps:很奇怪,為什innodb的索引葉子節點存的是主鍵而不是像mysism一樣存資料的實體地址指標嗎?如果存的是實體地址指標不就不需要二次查找了嗎,這也是我開始的疑惑,根據mysism和innodb資料儲存方式的差異去想,你就會明白了,我就不費口舌了!

所以innodb為了避免二次查詢可以使用索引覆蓋技術,無法使用索引覆蓋的,再延伸一下就是基於索引覆蓋實現延遲關聯;不知道什麼是索引覆蓋的,建議你無論如何都要弄清楚它是怎麼回事!

盡你所能去優化你的sql吧!說它成本低,卻又是一項費時費力的活,需要在技術與業務都熟悉的情況下,用心去優化才能做到最優,優化後的效果也是立竿見影的!

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

答主答得非常好了,但是不太贊同答主給出的順序。
一個合格的技術人,應該能做出適用於未來的產品。引入分散式會帶來很多麻煩,但它會讓你走得更遠。這不正是技術人的價值所在嗎?
看到有人回覆說有錢就上Oracle了。Facebook,阿里沒錢嗎?錢不是最重要的考慮。再說他們的技術實力沒有甲骨文強嗎?萬萬不要迷信IOE。

  • 首先,任何優化,都需要你瞭解你的業務,瞭解你的資料。
    • 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. 複製及讀寫分離
Why: 這個其實是大多數場景下都必須的。因為複製可以實現備份、高可用、負載均衡。就算嫌麻煩不做負載均衡,那備份下總是要的吧?既然已經備份了,何不加個LVS+HAProxy做下HA?順便稍微修改下應用,讀寫分離也就成了。
How: 節點少的情況下,主備。前面加Keepalived+HAProxy等元件,失效自動切換。讀寫分離可能需要修改下應用。
節點多的情況下,一是考慮多級備份,減輕主的壓力。其次可以引入第三方元件,接管主節點的備份工作。
主主不是很推薦。一是需要考慮資料衝突的情況,比如錯開id,同時操作資料後衝突解決。其次如果強一致會導致延遲增加,如果有節點掛了,需要等到超時才返回。
When: 主備幾乎大多數場景。甚至不論資料大小。高可用對應用透明,為啥不用?主主麻煩,建議先用切分。
4. 切分
包括垂直切分和水平切分,實現方式上又包括分庫、分表。
雖然有些難度,但還是推薦常用的。
Why: 垂直切分保證業務的獨立性,防止不同業務爭搶資源,畢竟業務是有優先順序的。
水平切分主要用於突破單機瓶頸。除了主主外,只有切分能真正做到將負載分配下去。
切分後也可對不同片資料進行不同優化。如按時間切分,超過一定時間資料不允許修改,就可以引入壓縮了,資料傳輸及讀取減少很多。
How: 根據業務垂直切分。業務內部分庫、分表。一般都需要修改應用。除分表外,其餘實現不是很複雜。有第三方元件可用,但通用高效又靈活的方式,還是自己寫client。
When: 垂直切分一般都要做,只不過業務粒度大小而已。
分庫有是經常用的,就算當前壓力小,也儘量分出幾個邏輯庫出來。等規模上去了,很方便就遷移擴充套件。
水平拆分有一定難度,但如果將來一定會到這個規模,又可能用到,建議越早做越好。因為對應用的改動較大,而且遷移成本高。

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