MySQL海量數據優化(理論+實戰) 吊打面試官

前言

朋友們,又見面了,上篇文章咱們講到MySQL分庫分表的方法,這篇文章咱們就針對上一篇文章模擬在MySQL中海量數據的優化方法,文章乾貨較多,建議你點贊、評論、收藏、關注起來慢慢看

提示:以下是本篇文章正文內容,案例僅供參考

一、準備表數據

咱們建一張用戶表,表中的字段有用戶ID、用戶名、地址、記錄創建時間,如圖所示

OK,接下來準備寫一個存儲過程插入一百萬條數據

插入完後咱們看看數據條數

二、優化方式

1.分頁查詢優化

OK,咱們看下分頁limit到一定值時的耗時是多少

limit 1000時

limit 10000時

limit 100000時

limit 1000000時

可以看到limit值越大,耗時越長,這還只是一百萬數據,要是千萬級、億級呢?

OK不廢話,咱們馬上進行分頁優化

子查詢優化

可以看到比起之前 limit 1000000時的0.218s 效率提高了很多

使用JOIN分頁

可以看到比起之前 limit 1000000時的0.218s 效率也同樣提高了很多

使用前一次查詢的最大ID

可以看到這種方法效率最高,但依賴於需要知道最大ID,這種適合點擊下一頁查詢(類似於滾動加載數據)的場景

通過僞列對ID進行分頁

然後可以開啓多個線程去進行最高效率查詢語句的批量查詢操作

0~10000,10001-20000....

這樣子的話可以快速把全量數據查詢出來同步至緩存中。

分頁優化總結:使用前一次查詢的最大ID進行查詢優化是效率最高的方法,但這種方法只適用於下一頁點擊的這種操作,對於同步全量數據來說建議的方式使用僞列對ID進行分頁,然後開啓多個線程同時查詢,把全量數據加載到緩存,以後面試官問你如何快速獲取海量數據並加載到緩存你該知道怎麼回答了吧。

2.普通索引優化

先來看沒索引優化的情況下的查詢效率

可以看到這時沒用索引的情況,用了0.305S接下來看看加了索引後的結果

普通索引優化

只需要0.024S,我們可以EXPLAIN看下

可以看到使用了普通索引後查詢效率明顯增加

3.複合索引優化

複合索引什麼時候用?爲什麼要用?

圍繞着這兩問題,咱們先來說說複合索引什麼時候用

單表中查詢、條件語句中具有較多個字段

使用索引會影響寫的效率,需要研究建立最優秀的索引

我們這裏建議一個複合索引

MySQL建立複合索引時實際建立了(user_name)、(user_name,address)、(user_name,address,create_time)三個索引,我們都知道每多一個索引,都會增加寫操作的開銷和磁盤空間的開銷,對於海量數據的表,這可是不小的開銷,所以你會發現我們在這裏使用複合索引一個頂三個,又能減少寫操作的開銷和磁盤空間的開銷

當我們select user_name,address,create_time from t_user where user_name=xx and address = xxx時,MySQL可以直接通過遍歷索引取得數據,無需回表,這減少了很多的隨機IO操作。所以,在真正的實際應用中,這就是覆蓋索引,是複合索引中主要的提升性能的優化手段之一。

4.SQL查詢優化

避免使用OR,看看例子

可以看到這條語句沒有使用到索引,是因爲當or左右查詢字段只有一個是索引,該索引失效,只有當or左右查詢字段均爲索引時,纔會生效。

不要使用like '%xx'  %在左邊時索引失效

3. 使用複合索引時沒有遵循最左匹配原則

ref:這個連接類型只有在查詢使用了不是惟一或主鍵的鍵或者是這些類型的部分(比如,利用最左邊前綴)時發生。沒有值說明沒有利用最左前綴原則

再來看個使用了最左前綴的例子

4. 不要讓數據類型出現隱式轉化

可以看以下兩個例子

5. 不要在索引字段上使用not,<>,!=,一樣會導致索引失效

6. 分解關聯查詢例如這條語句

可以分解成

7.小表驅動大表

即小的數據集驅動大的數據集。如:以t_user,t_order兩表爲例,兩表通過 t_user的id字段進行關聯。

5.事務優化

首先了解下事務的隔離級別,數據庫共定義了四種隔離級別:

Serializable:可避免髒讀、不可重複讀、虛讀情況的發生。(串行化)

Repeatable read:可避免髒讀、不可重複讀情況的發生。(可重複讀)

Read committed:可避免髒讀情況發生(讀已提交)。

Read uncommitted:最低級別,以上情況均無法保證。(讀未提交)

可以通過 set transaction isolation level 設置事務隔離級別來提高性能

6.數據庫性能優化

開啓查詢緩存

在解析一個查詢語句前,如果查詢緩存是打開的,那麼MySQL會檢查這個查詢語句是否命中查詢緩存中的數據。如果當前查詢恰好命中查詢緩存,在檢查一次用戶權限後直接返回緩存中的結果。這種情況下,查詢不會被解析,也不會生成執行計劃,更不會執行。

MySQL將緩存存放在一個引用表(不要理解成table,可以認爲是類似於HashMap的數據結構),通過一個哈希值索引,這個哈希值通過查詢本身、當前要查詢的數據庫、客戶端協議版本號等一些可能影響結果的信息計算得來。所以兩個查詢在任何字符上的不同(例如:空格、註釋),都會導致緩存不會命中。

如果查詢中包含任何用戶自定義函數、存儲函數、用戶變量、臨時表、mysql庫中的系統表,其查詢結果都不會被緩存。比如函數NOW()或者CURRENT_DATE()會因爲不同的查詢時間,返回不同的查詢結果,再比如包含CURRENT_USER或者CONNECION_ID()的查詢語句會因爲不同的用戶而返回不同的結果,將這樣的查詢結果緩存起來沒有任何的意義。

既然是緩存,就會失效,那查詢緩存何時失效呢?MySQL的查詢緩存系統會跟蹤查詢中涉及的每個表,如果這些表(數據或結構)發生變化,那麼和這張表相關的所有緩存數據都將失效。正因爲如此,在任何的寫操作時,MySQL必須將對應表的所有緩存都設置爲失效。如果查詢緩存非常大或者碎片很多,這個操作就可能帶來很大的系統消耗,甚至導致系統僵死一會兒。而且查詢緩存對系統的額外消耗也不僅僅在寫操作,讀操作也不例外:

任何的查詢語句在開始之前都必須經過檢查,即使這條SQL語句永遠不會命中緩存   如果查詢結果可以被緩存,那麼執行完成後,會將結果存入緩存,也會帶來額外的系統消耗複製代碼

基於此,我們要知道並不是什麼情況下查詢緩存都會提高系統性能,緩存和失效都會帶來額外消耗,只有當緩存帶來的資源節約大於其本身消耗的資源時,纔會給系統帶來性能提升。但要如何評估打開緩存是否能夠帶來性能提升是一件非常困難的事情,也不在本文討論的範疇內。如果系統確實存在一些性能問題,可以嘗試打開查詢緩存,並在數據庫設計上做一些優化,比如:

. 批量插入代替循環單條插入  . 合理控制緩存空間大小,一般來說其大小設置爲幾十兆比較合適  . 可以通過SQL_CACHE和SQL_NO_CACHE來控制某個查詢語句是否需要進行緩存  最後的忠告是不要輕易打開查詢緩存,特別是寫密集型應用。如果你實在是忍不住,可以將query_cache_type設置爲DEMAND,這時只有加入SQL_CACHE的查詢纔會走緩存,其他查詢則不會,這樣可以非常自由地控制哪些查詢需要被緩存。  當然查詢緩存系統本身是非常複雜的,這裏討論的也只是很小的一部分,其他更深入的話題,比如:緩存是如何使用內存的?如何控制內存的碎片化?事務對查詢緩存有何影響等等,讀者可以自行閱讀相關資料,這裏權當拋磚引玉吧。語法解析和預處理

MySQL通過關鍵字將SQL語句進行解析,並生成一顆對應的解析樹。這個過程解析器主要通過語法規則來驗證和解析。比如SQL中是否使用了錯誤的關鍵字或者關鍵字的順序是否正確等等。預處理則會根據MySQL規則進一步檢查解析樹是否合法。比如檢查要查詢的數據表和數據列是否存在等等。

7.系統內核參數優化

這些參數可按照自己的實際服務器以及數據庫的大小進行適當調整,主要起參考作用

8.表字段優化

很多系統一開始並沒有考慮表字段拆分的問題,因爲拆分會帶來邏輯、部署、運維的各種複雜度,一般以整型值爲主的表在千萬級以下,字符串爲主的表在五百萬以下,而事實上很多時候MySQL單表的性能依然有不少優化空間,甚至能正常支撐千萬級以上的數據量:

下面直接看下如何去優化字段

儘量使用TINYINT、SMALLINT、MEDIUM_INT作爲整數類型而非INT,如果非負則加上UNSIGNED

單表不要有太多字段,建議在15以內

儘量使用TIMESTAMP而非DATETIME

使用枚舉或整數代替字符串類型

VARCHAR的長度只分配真正需要的空間

避免使用NULL字段,很難查詢優化且佔用額外索引空間

用整型來存IP

9.分佈式場景下常用優化手段

升級硬件

Scale up,這個不多說了,根據MySQL是CPU密集型還是I/O密集型,通過提升CPU和內存、使用SSD,都能顯著提升MySQL性能

讀寫分離

也是目前常用的優化,從庫讀主庫寫,一般不要採用雙主或多主引入很多複雜性,儘量採用文中的其他方案來提高性能。同時目前很多拆分的解決方案同時也兼顧考慮了讀寫分離

使用緩存

緩存可以發生在這些層次:

MySQL內部:在系統內核參數優化介紹了相關設置

數據訪問層:比如MyBatis針對SQL語句做緩存,而Hibernate可以精確到單個記錄,這裏緩存的對象主要是持久化對象Persistence Object

應用服務層:這裏可以通過編程手段對緩存做到更精準的控制和更多的實現策略,這裏緩存的對象是數據傳輸對象Data Transfer Object

Web層:針對web頁面做緩存

瀏覽器客戶端:用戶端的緩存

可以根據實際情況在一個層次或多個層次結合加入緩存。這裏重點介紹下服務層的緩存實現,目前主要有兩種方式:

直寫式(Write Through):在數據寫入數據庫後,同時更新緩存,維持數據庫與緩存的一致性。這也是當前大多數應用緩存框架如Spring Cache的工作方式。這種實現非常簡單,同步好,但效率一般。

回寫式(Write Back):當有數據要寫入數據庫時,只會更新緩存,然後異步批量的將緩存數據同步到數據庫上。這種實現比較複雜,需要較多的應用邏輯,同時可能會產生數據庫與緩存的不同步,但效率非常高。

水平拆分:我的上篇文章有講到,這裏不再贅述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章