此文已由做者劉超受權網易雲社區發佈。html
歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。mysql
數據庫永遠是應用最關鍵的一環,同時越到高併發階段,數據庫每每成爲瓶頸,若是數據庫表和索引不在一開始就進行良好的設計,則後期數據庫橫向擴展,分庫分表都會遇到困難。
sql
對於互聯網公司來說,通常都會使用Mysql數據庫。數據庫
1、數據庫的整體架構設計模式
咱們首先來看Mysql數據的整體架構以下:緩存
這是一張很是經典的Mysql的系統架構圖,經過這個圖能夠看出Mysql各個部分的功能。
性能優化
當客戶端鏈接數據庫的時候,首先面對的是鏈接池,用於管理用戶的鏈接,並會作必定的認證和鑑權。
數據結構
鏈接了數據庫以後,客戶端會發送SQL語句,而SQL接口這個模塊就是來接受用戶的SQL語句的。
架構
SQL語句每每須要符合嚴格的語法規則,於是要有語法解析器對語句進行語法解析,解析語法的原理如同編譯原理中的學到的那樣,從語句變成語法樹。
併發
對於用戶屬於的查詢能夠進行優化,從而能夠選擇最快的查詢路徑,這就是優化器的做用。
爲了加快查詢速度,會有查詢緩存模塊,若是查詢緩存有命中的查詢結果,查詢語句就能夠直接去查詢緩存中取數據。
上面的全部的組件都是數據庫服務層,接下來是數據庫引擎層,當前主流的數據庫引擎就是InnoDB。
對於數據庫有任何的修改,數據庫服務層會有binary log記錄下來,這是主備複製的基礎。
對於數據庫引擎層,一個著名的圖以下:
在存儲引擎層,也有緩存,也有日誌,最終數據是落到盤上的。
存儲引擎層的緩存也是用於提升性能的,可是同數據庫服務層的緩存不一樣,數據庫服務層的緩存是查詢緩存,而數據庫引擎層的緩存讀寫都緩存。數據庫服務層的緩存是基於查詢邏輯的,而數據庫引擎引擎的緩存是基於數據頁的,能夠說是物理的。
哪怕是數據的寫入僅僅寫入到了數據庫引擎層中的緩存,對於數據庫服務層來說,就算是已經持久化了,固然這個時候會形成緩存頁和硬盤上的頁的數據的不一致,這種不一致由數據庫引擎層的日誌來保證完整性。
因此數據庫引擎層的日誌和數據庫服務層的也不一樣,服務層的日誌記錄的是一個個的修改邏輯,而引擎層的日誌記錄的是緩存頁和數據頁的物理差別。
2、數據庫的工做流程
在收到一個查詢的時候,Mysql的架構中的各個組件是如此工做的:
客戶端同數據庫服務層創建TCP鏈接,鏈接管理模塊會創建鏈接,並請求一個鏈接線程。若是鏈接池中有空閒的鏈接線程,則分配給這個鏈接,若是沒有,在沒有超過最大鏈接數的狀況下,建立新的鏈接線程負責這個客戶端。
在真正的操做以前,還須要調用用戶模塊進行受權檢查,來驗證用戶是否有權限。經過後,方纔提供服務,鏈接線程開始接收並處理來自客戶端的SQL語句。
鏈接線程接收到SQL語句以後,將語句交給SQL語句解析模塊進行語法分析和語義分析。
若是是一個查詢語句,則能夠先看查詢緩存中是否有結果,若是有結果能夠直接返回給客戶端。
若是查詢緩存中沒有結果,就須要真的查詢數據庫引擎層了,因而發給SQL優化器,進行查詢的優化。若是是表變動,則分別交給insert, update, delete, create,alter處理模塊進行處理。
接下來就是請求數據庫引擎層,打開表,若是須要的話獲取相應的鎖。
接下來的處理過程就到了數據庫引擎層,例如InnoDB。
在數據庫引擎層,要先查詢緩存頁中有沒有相應的數據,若是有則能夠直接返回,若是沒有就要從磁盤上去讀取。
當在磁盤中找到相應的數據以後,則會加載到緩存中來,從而使得後面的查詢更加高效,因爲內存有限,多采用變通的LRU表來管理緩存頁,保證緩存的都是常常訪問的數據。
獲取數據後返回給客戶端,關閉鏈接,釋放鏈接線程,過程結束。
3、數據庫索引的原理
在整個過程當中,最容易稱爲瓶頸點的是數據的讀寫,每每意味着要順序或者隨機讀寫磁盤,而讀寫磁盤的速度每每是比較慢的。
若是加快這個過程呢?相信你們都猜到了就是創建索引。
爲何索引可以加快這個過程呢?
相信你們都逛過美食城,裏面衆多家餐館琳琅滿目,若是你不着急呢,肚子不餓,對搜索的性能沒有要求,就能夠在商場裏面慢慢逛,逛一家看一家,知道找到本身想吃的餐館。可是當你餓了,或者大家約好了餐館,你必定想直奔那個餐館,這個時候,你每每會去看樓層的索引圖,快速的查找你目標餐館的位置,找到後,直奔主題,就會大大節約時間,這就是索引的做用。
因此索引就是經過值,快速的找到它的位置,從而能夠快速的訪問。
索引的另一個做用就是不用真正的查看數據,就可以作一些判斷,例如商場裏面有沒有某個餐館,你看一下索引就知道了,沒必要真的到商場裏面逛一圈,再如找出全部的川菜館,也是隻要看索引就能夠了,不用一家一家川菜館跑。
那麼在Mysql中,索引是如何工做的呢?
Mysql的索引結構,每每是一棵B+樹。
一棵m階B+樹具備以下的性質:
節點分索引節點和數據節點。索引節點至關於B樹的內部節點,全部的索引節點組成一棵B樹,具備B樹的全部的特性。在索引節點中,存放着Key和指針,並不存放具體的元素。數據節點至關與B樹的外部節點,B樹的外部節點爲空,在B+樹中被利用了起來,用於存放真正的數據元素,裏面包含了Key和元素的其餘信息,可是沒有指針。
整棵索引節點組成的B樹僅僅用來查找具備某個Key的數據元素位於哪一個外部節點。在索引節點中找到了Key,事情沒有結束,要繼續找到數據節點,而後將數據節點中的元素讀出來,或者二分查找,或者順序掃描來尋找真正的數據元素。
M這個階數僅僅用來控制索引節點部分的度,至於每一個數據節點包含多少元素,與m無關。
另外有一個鏈表,將全部的數據節點串起來,能夠順序訪問。
這個定義的比較抽象,咱們來看一個具體的例子。
從圖中咱們能夠看出,這是一個3階B+樹,而一個外部數據節點最多包含5項。若是插入的數據在數據節點,若是不引發分裂和合並,則索引節點組成的B樹就不會變。
若是在71到75的外部節點插入一項76,則引發分裂,71,72,73成爲一個數據節點,74,75,76成爲一個數據節點,而對於索引節點來說至關於插入一個Key爲74的過程。
若是在41到43的外部節點中刪除43,則引發合併,41,42,61,62,63合併成一個節點,對於索引節點來說,至關於刪除Key爲60的過程。
查找的時候,因爲B+樹層高很小,因此可以比較快速的定位,例如咱們要查找值62,在根節點發現大於40則訪問右面,小於70則訪問左面,大於60則訪問右面,在葉子節點的第二個,就找到了62,成功定位。
在Mysql的InnoDB中,有兩種類型的B+樹索引,一種稱爲聚簇索引,一種稱爲二級索引。
聚簇索引的葉子節點就是數據節點,每每是主鍵做爲聚簇索引,二級索引的葉子節點存放的是KEY字段加主鍵值。於是經過二級索引訪問數據,要訪問兩次索引。
還有一種索引的形式稱爲組合索引,或者複合索引,能夠在多個列上創建索引。
這種索引的排序規則爲,先比較第一列,在第一列相等的狀況下,比較第二列,以此類推。
4、數據庫索引的優缺點
數據庫索引的優點最明顯的就是減小I/O,下面分析幾種場景。
對於=條件的字段,能夠直接經過查找B+樹的方式,經過不多的硬盤讀取次數(至關於B+樹層高),就可以到達葉子節點,而後直接定位到數據的位置。
對於範圍的字段,因爲B+樹裏面都是排好序的,範圍能夠很快的經過樹進行定位。
同理對於orderby/group by/distinct/max/min,因爲B+樹是排好序的,也是可以很快的獲得結果的。
還有一個常見的場景稱爲索引覆蓋數據。例如A, B兩個字段做爲條件字段,常出現A=a AND B=b,同時select C, D時候,每每會建聯合索引(A, B),是一個二級索引,因此搜索的時候,經過二級索引的B+樹可以很快的找到相應的葉子節點和記錄,可是記錄中有的是聚簇索引的ID,因此還須要查找一次聚簇索引的B+樹,找到真正的表中的記錄,而後在記錄中,將C,D讀取出來。若是創建聯合索引的時候爲(A, B, C, D),則在二級索引的B+樹中就有了全部的數據,能夠直接返回了,減小了一次搜索樹的過程。
固然索引確定是有代價的,天下沒有免費的午飯。
索引帶來的好處可能是讀的效率的提升,而索引帶來的代價就是寫的效率的下降。
插入和修改數據,都有可能意味着索引的改變。
插入的時候,每每會在主鍵上建設聚簇索引,於是主鍵最好使用自增加,這樣插入的數據就老是在最後,並且是順序的,效率比較高。主鍵不要使用UUID,這樣順序比較隨機,會帶來隨機的寫入,效率比較差。主鍵不要使用和業務有關,由於與業務相關意味着會被更新,將面臨着一次刪除和從新插入,效率會比較差。
經過上面對於B+樹的原理的介紹,咱們能夠看出B+樹的分裂代價仍是比較大的,而分裂每每就產生於插入的過程當中。
而對於數據的修改,則基本至關於刪除再插入,代價也比較大。
對於一些字符串的列的二級索引,每每會形成隨機的寫入和讀取,對I/O的壓力也比較大。
5、解讀數據庫軍規背後的原理
瞭解了這兩種索引的原理,咱們就可以解釋爲何不少所謂的數據庫的軍規長這個樣子了。下面咱們來一一解釋。
什麼狀況下應該使用組合索引而非單獨索引呢?
假設有條件語句A=a AND B=b,若是A和B是兩個單獨的索引,在AND條件下只有一個索引發做用,對於B則要逐個判斷,而若是使用組合索引(A, B),只要遍歷一棵樹就能夠了,大大增長了效率。可是對於A=a OR B=b,因爲是或的關係,於是組合索引是不起做用的,於是可使用單獨索引,這個時候,兩個索引能夠同時起做用。
爲何索引要有區分度,組合索引中應該講有區分度的放在前面?
若是沒有區分度,例如用性別,至關於把整個大表分紅兩部分,查找數據仍是須要遍歷半個表才能找到,使得索引失去了意義。
若是有組合索引,還須要單列索引嗎?
若是組合索引是(A, B),則對於條件A=a,是能夠用上這個組合索引的,由於組合索引是先按照第一列進行排序的,因此不必對於A單獨創建一個索引,可是對於B=b就用不上了,由於只有在第一列相同的狀況下,才比較第二列,於是第二列相同的,能夠分佈在不一樣的節點上,沒辦法快速定位。
索引是越多越好嗎?
固然不是,只有在必要的地方添加索引,索引不但會使得插入和修改的效率下降,並且在查詢的時候,有一個查詢優化器,太多的索引會讓優化器困惑,可能沒有辦法找到正確的查詢路徑,從而選擇了慢的索引。
爲何要使用自增主鍵
由於字符串主鍵和隨機主鍵會使得數據隨機插入,效率比較差,主鍵應該少更新,避免B+樹和頻繁合併和分裂。
爲何儘可能不使用NULL
NULL在B+樹裏面比較難以處理,每每須要特殊的邏輯進行處理,反而下降了效率。
爲何不要在更新頻繁的字段上創建索引
更新一個字段意味着相應的索引也要更新,更新每每意味着刪除而後再插入,索引原本是一種事先在寫的階段造成必定的數據結構,從而使得在讀的階段效率較高的方式,可是若是一個字段是寫多讀少,則不建議使用索引。
爲何在查詢條件裏面不要使用函數
例如ID+1=10這種條件,索引是事先寫入的時候生成好的,ID+1這種操做在查詢階段,索引無能爲例,沒辦法把全部的索引都先作一個計算,而後再比較吧,代價太大了,於是應該使用ID=10-1。
爲何不要使用NOT等負向查詢條件
你能夠想象一下,對於一棵B+樹,跟節點是40,若是你的條件是等於20,就去左面查,你的條件等於50,就去右面查,可是你的條件是不等於66,索引應該咋辦?還不是遍歷一遍才知道。
爲何模糊查詢不要以通配符開頭
對於一棵B+樹來說,若是根是字符def,若是通配符在後面,例如abc%,則應該搜索左面,例如efg%,則應該搜索右面,若是通配符在前面%abc,則不知道應該走哪一面,仍是都掃描一遍吧。
爲何OR要改爲IN,或者使用Union
OR查詢條件的優化每每比較難找到最佳的路徑,尤爲是OR的條件比較多的時候,尤爲如此,對於同一個字段,使用IN就好一些,數據庫會對IN裏面的條件進行排序,並統一經過二分搜索的方法處理。對於不一樣的字段,使用Union,則可讓每個子查詢都使用索引。
爲何數據類型應該儘可能小,經常使用整型來代替字符型,長字符類型能夠考慮使用前綴索引?
由於數據庫是按照頁存放的,每一頁的大小是同樣的,若是數據類型比較大,則頁數會比較多,每一頁放的數據會比較少,樹的高度會比較高,於是搜索數據要讀取的I/O數目會比較多,插入的時候節點也容易分裂,效率會下降。使用整型來代替字符型可能是這個考慮,整型對於索引有更高的效率,例如IP地址等。若是有長字符類型須要使用索引進行查詢,爲了避免要使得索引太大,能夠考慮將字段的前綴進行索引,而非整個字段。
6、查詢優化的方法論
要找到須要優化的SQL語句,首先要收集有問題的SQL語句。
MySQL 數據庫提供了慢SQL日誌功能,經過參數slow_query_log,獲取執行時間超過必定閾值的SQL語錄列表。
沒有使用索引的SQL語句,能夠經過long_queries_not_using_indexes參數開啓。
min_examined_row_limit,掃描記錄數大於該值的SQL語句纔會被記入慢SQL日誌。
找到有問題的語句,接下來就是經過explainSQL,獲取SQL的執行計劃,是否經過索引掃描記錄,能夠經過建立索引來優化執行效率。是否掃描記錄數過多。是否持鎖時間過長,是否存在鎖衝突。返回的記錄數是否較多。
接下來能夠定製化的優化。沒有被索引覆蓋的過濾條件涉及的字段,在區分度較大的字段上建立索引,若是涉及多個字段,儘可能建立聯合索引。
掃描記錄數很是多,返回記錄數很少,區分度較差,從新評估SQL語句涉及的字段,選擇區分度高的多個字段建立索引
掃描記錄數很是多,返回記錄數也很是多,過濾條件不強,增長SQL過濾條件
schema_redundant_indexes查看有哪些冗餘索引。
若是多個索引涉及字段順序一致,則能夠組成一個聯合索引schema_unused_indexes查看哪些索引從沒有被使用。
7、讀寫分離的原理
數據庫每每寫少讀多,因此性能優化的第一步就是讀寫分離。
主從複製基於主節點上的服務層的日誌實現的,而從節點上有一個IO線程讀取這個日誌,而後寫入本地。另有一個線程從本地日誌讀取後在從節點從新執行。
如圖是主從異步複製的流程圖。在主實例寫入引擎後就返回成功,而後將事件發給從實例,在從實例上執行。這種同步方式速度較快,可是在主掛了的時候,若是尚未複製,則可能存在數據丟失問題。
數據庫同步複製也不一樣,是當從節點落盤後再返回客戶端,固然這樣會使得性能有所下降,網易數據庫團隊是經過組提交,並行複製等技術將性能提上來。
有了主從複製,在數據庫DAO層能夠設置讀寫分離策略,也有經過數據庫中間件作這個事情的。
其實數據庫日誌還有不少其餘用處,如使用canal(阿里巴巴開源項目: 基於mysql數據庫binlog的增量訂閱&消費)訂閱數據庫的binlog,能夠用於更新緩存等。
網易雲計算基礎服務深度整合了 IaaS、PaaS 及容器技術,提供彈性計算、DevOps 工具鏈及微服務基礎設施等服務,幫助企業解決 IT、架構及運維等問題,使企業更聚焦於業務,是新一代的雲計算平臺,點擊可免費試用。
相關文章:
【推薦】 用上帝視角來看待組件的設計模式
【推薦】 淺談js拖拽
【推薦】 iOS 安裝包瘦身(下篇)