《高效能MySQL》——讀書筆記
高效能mysql讀書筆記
Mysql的架構可以在多種場景中應用,併發揮好的作用。
Mysql最與眾不同的特性是:將資料的儲存和提取相分離。
- Mysql架構與歷史
1.1、Mysql邏輯架構
邏輯架構圖:
第二層架構是mysql的核心服務功能,包括查詢解析、分析、優化、快取以及所有的內建函式都在這一層實現。
第三層包含了儲存引擎,儲存引擎負責Mysql中資料的儲存和提取。伺服器通過API與儲存引擎進行通訊,這些介面屏了不同儲存引擎的差異,使得這些差異對查詢過程透明。
-
-
- 連線管理與安全性
-
客戶端連線都會在伺服器程序中擁有一個執行緒,連線的查詢只會在這個單獨的執行緒中執行,該執行緒只能輪流在某個CPU核心或者CPU中執行。
-
-
- 優化與執行
-
Mysql會解析查詢,並建立內部資料結構(解析樹),然後對其進行各種優化。(重新查詢、決定表的讀取順序、選擇合適的索引)。
1.2、併發控制
當多個查詢需要在同一時刻修改資料,都會出現併發控制問題。
1.2.1、讀寫鎖
實現兩種型別的鎖組成的鎖系統來解決有讀也有寫(改)的情況。
讀鎖(read lock) = 共享鎖(shared lock)
寫鎖(write lock) = 排他鎖(exclusive lock)
讀鎖是共享的,互不阻塞的。
寫鎖是排他的,會阻塞其他的寫鎖和讀鎖。
1.2.2、鎖粒度
提高共享資源併發性的方式就是讓鎖定物件更有選擇性,最好:只鎖定需要修改的部分資料,而不是所用的資源。
鎖定的資料量越少,系統的併發程度越高。
注意:加鎖,也是消耗資源的。
鎖策略:鎖在開銷和資料的安全性之間尋求平衡。
大部分資料庫系統,都是在表上,施加行級鎖(row level lock)。
而mysql提供多種選擇:
表鎖:(table lock)
是Mysql最基本的鎖策略,並且是開銷最小的策略,
使用者對錶進行寫操作(cud),需要獲得寫鎖,這會阻塞其他使用者對錶進行讀、寫。
並且寫鎖比讀鎖有更高的優先順序。
行級鎖:(row lock)
是最大程度支援併發處理(同時也是最大的鎖開銷)。
InnoDB、XtraDB都實現了行級鎖。行級鎖只在儲存引擎層實現,而服務層沒有實現。
1.3、事務
事務是一組原子性的SQL查詢,或者說一個獨立的工作單元。
系統要經過嚴格的ACID測試,否則空談事務的概念是不夠的。
原子性:
一致性:
隔離性
永續性:
對事務處理過程中額外的安全性,需要資料庫做更多的額外工作。(和鎖粒度升級會增加系統開銷一樣)
因此Mysql的儲存引擎架構就可以發揮優勢了,可以根據當前業務,來選擇合適的儲存引擎。
1.3.1、隔離級別
SQL標準定義了四種隔離級別:
每一種級別規定了一個事物所做的修改,哪些在事務內和事務間是可見的,哪些是不可見的。
較低級別的隔離,通常可以執行更高的併發,系統開銷也更低。
- READ UNCOMMITTED(未提交讀)
事務可以讀取未提交的資料,這也稱為髒讀(Dirty Read)。
- READ COMMITTED(提交讀)(大多數資料庫系統預設的隔離級別,但Mysql不是)
事務開始時,只能“看見”已經提交的事務所做的修改。
換句話說:一個事務從開始到提交之前,所做的任何修改對其他的事務都是不可見的。該級別叫做:不可重複讀(nonrepeatable read)。因為執行兩次同樣的查詢,會得到不一樣的結果。
- REPEATABLE READ(可重複讀)(Mysql預設的隔離級別)
解決了髒讀的問題。保證了同一個事務多次讀取同樣記錄的結果是一致的。
理論上,該級別會產生幻讀(Phantom Read)問題:某個事務在讀取某個範圍內的記錄時,另外一二事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄時,會產生幻行(Phantom Row)
- SERIALIZABLE(可序列化)
最高的隔離級別,可以解決幻讀問題。它會給讀取的每一行資料加鎖,因此可能會導致大量的超時和鎖爭用的問題。
1.3.2、死鎖
指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而導致惡性迴圈的現象。
解決這個問題,資料庫系統實現了各種死鎖檢測和死鎖超時機制。
InnoDB目前處理死鎖的方法是:將持有最少行級排他鎖的事務進行回滾。
1.3.3、事務日誌
目的:提高事務的效率。使用事務日誌,修改資料時的步驟:
- 修改其記憶體拷貝。
- 將修改行為持久化到事務日誌中。(事務日誌採用追加方式,因此寫日誌的操作是在磁碟一小塊區域內的順序I/O)
- 後臺慢慢刷回到磁碟。
因此,修改資料需要寫兩次磁碟。
1.3.4、Mysql中的事務
自動提交(AUTOCOMMIT)
Mysql預設採用自動提交模式。若不是顯式開始一個事務,則每個查詢都被當作一個事務執行提交操作。
隱式鎖定:InnoDB採用的是兩階段鎖定協議(tow-phase locking protocol)。在事務執行過程中,隨時都可以執行鎖定,鎖只有在COMMIT或者ROLLBACK的時候才會釋放,並且所有的鎖是在同一時刻被釋放。
顯示鎖定:InnoDB也支援特定的語句進行顯式鎖定:LOCK TABLES 和UNLOCK TABLES,這是在伺服器層實現的,和儲存引擎無關。
1.4、多版本併發控制(MVCC)
InnoDB的MVCC:通過每行記錄後面儲存兩個隱藏的列來實現的。
一個儲存了行的建立時間,一個是儲存了行的過期時間(或刪除時間)。(注:儲存的不是具體的時間值,而是系統版本號)。
每開始一個新的事務,系統版本號會自動遞增。
例子:在REPEATABLE READ隔離級別下,MVCC具體是如何操作的。
SELECT:
- InnoDB只查詢早於當前事務版本號的資料行。(行版本號<=系統版本號),這確保了事務讀取的行,要麼是事務開始前已經存在,要麼是事務自身插入或修改過的。
- 行刪除版本號要麼未定義,要麼大於當前事務版本號。這可以確保事務讀取到的行,在事務開始之前未被刪除。
INSERT:
InnoDB為新插入的每一行儲存當前系統版本號作為行版本號。
DELETE:
InnoDB為刪除的每一行儲存當前版本號作為行刪除標記。
UPDATE:
InnoDB為插入一行新紀錄,儲存當前系統版本號作為行版本號,同時儲存當前系統版本號到原來的行作為行刪除標識。
儲存這兩個額外的系統版本號,是大多數讀操作都可以不用加鎖。使得讀資料操作很簡單,效能很好,並且也能保證只會讀取到符合標準的行。
這是典型的:用空間換時間
MVCC只在REPEATABLE READ和READ COMMITTED兩個隔離級別下工作。
READ UNCOMMITTED 總是讀取最新的資料行,而不是符合當前事務版本的資料行。
SERIALIZABLE對所有讀取的資料行加鎖。
1.5、Mysql的儲存引擎
檔案系統將Mysql的每個資料庫(也稱為schema)儲存在資料目錄下的一個子目錄下,建立表時,Mysql會在資料庫的子目錄下建立一個和表同名的.frm檔案儲存表的定義。
1.5.1、InnoDB儲存引擎
Mysql預設的事務型引擎。使用最多、使用最廣。
InnoDB概覽:InnoDB的資料儲存在表空間(tablespace)中,表空間是由InnoDB管理的一個黑盒子,由一系列資料檔案組成。
Mysql4.1後,InnoDB可以將每個表的資料和索引存放在單獨的檔案中。
InnoDB表是基於聚簇索引建立的。它對主鍵查詢有很高的效能。但:它的二級索引(secondary index,非主鍵索引)包含主鍵列,所以如果主鍵列很大的話,其他索引都會很大。,因此若表中索引很多的話,主鍵應該儘可能的小。
1.5.2、MyISAM儲存引擎
使用場景:只讀資料、表較小、可以忍受修復(repair)操作。
缺點:不支援事務和行級鎖,崩潰後無法完全恢復。
儲存:
MyISAM:會將表儲存在兩個檔案中:資料檔案和索引檔案,分別以:.MYD和.MYI為副檔名。
1.5.5、選擇合適的引擎
日誌型應用:對插入速度有很高的要求,資料庫不能成為瓶頸。MyISAM或者Archive儲存引擎對這類應用比較合適,因為他們開銷低,而且插入速度很快。
如果對記錄日誌做分析報表,有兩種高效的方案:
- 主庫寫,從庫讀。利用Mysql內建的複製方案將資料複製一份到備庫,然後在備庫上執行比較耗時和耗CPU的查詢。
- 分表。
只讀或者大部分情況只讀的表:用InnoDB。因為MyISAM不會保證將資料安全地寫入磁碟中。
- Mysql基準測試
基準測試是針對系統設計的一種壓力設計。
2.2、基準測試的策略
整合式(full-stack):針對整個系統的整體測試。
單元件式(single-component):單獨測試Mysql
2.2.1、測試何種指標
吞吐量:單位時間內事務處理數。測試單位:TPS
響應時間或者延遲:測試任務所需的整體時間。通常可以用百分比響應時間來替代最大響應時間。
併發性:任意時間有多少同時發生的併發請求。
可擴充套件性:
注意:測試應該測對使用者來說最重要的指標,應該多瞭解一下需求,比如什麼樣的響應時間是可以接受的,期待多少併發性。
- 伺服器效能剖析
3.1、效能優化簡介
效能:完成某件任務所需要的時間度量。效能即響應時間。
無法測量就無法有效地優化。
優化的方法:用90%的時間測量響應時間花在哪裡。而不是花時間去調參!
3.2、對應用程式進行效能剖析
實際上,剖析程式程式比資料庫伺服器容易,並且回報更多。
3.3、剖析Mysql查詢
3.3.1、剖析伺服器負載
捕獲Mysql的查詢到日誌檔案中。
3.3.2、剖析單條查詢
使用 show profile
3.4、診斷間隙性問題
儘量不要用試錯的方式解決問題,這種方式有很大風險,並且是一種令人沮喪且低效的方式。
如果一時無法定位問題,可能是測量方式不對,測量點有問題,或者使用的工具不合適。
3.4.1、單條查詢問題還是伺服器問題
有三個方法可以排查:
- 一秒執行一次 show global status來捕獲資料。
其中觀察:Threads_running,Threads_connected、Queries的值,來檢視
- 使用SHOW PROCESSLIST
觀察執行緒是否有大量處於不正常狀態或者其他不正常的特徵。
通過以下語句,檢視某個列值出現的次數:
Mysql -e ‘show processlist\G’ | grep state: |sort|uniq -c |sort -rn
- 使用查詢日誌
將long_query_time設為0,這樣使所有的查詢都記錄到日誌中。
日誌分析的魔法工具:sort|uniq|sort
推薦一個監控伺服器的工具:Percona Toolkit。有了它就不用自己寫指令碼監控了。
3.6、總結
1、定義效能最有效的方法是響應時間。
2、無法測量就無法有效優化,效能優化工作,需要基於高質量、全方位及完整的響應時間測量。
3、有兩種消耗時間的操作:工作或等待。大多數剖析器只能測量因為工作而消耗的時間。
4、優化和提升是兩回事,當繼續提升的成本超過收益時,應當停止優化。
5、注意你的直覺,應該根據你的直覺來指導解決問題的思路,而不是用於確定系統問題。
決策應當儘量基於資料而不是直覺。
- Schema與資料型別優化
4.1、選擇優化的資料型別
不管選擇哪種型別的資料,以下原則有助於做出更好的選擇。
1)更小的通常更好
一般情況下,儘量使用可以正確儲存資料的最小資料型別。選擇一個認為不會超過最小型別的資料型別。
2)簡單就好
整形比字元操作代價更低。
應該使用Mysql的內建的型別而不是字串來儲存日期和時間。
用整形儲存IP地址。
3)儘量避免NULL
通常情況下,最好指定列為NOT NULL。若計劃在列上建索引,就應該儘量避免設計成為NULL的列。
選擇具體的型別:
DATETIME和TIMESTAMP都可以儲存相同型別的資料,時間和日期,精確到秒。然而:TIMESTAMP只使用DATETIME一半的儲存空間。
4.1.1、整數型別
整數:TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分別使用8,16,24,32,64位儲存空間。可以儲存的值的範圍為:,N為儲存空間位數。
整數型別還有UNSIGNED屬性,表示不允許負值,這可使整數的上限提高一倍。
Mysql可以為整數型別指定寬度,例如:INT(11),對大多數應用這是沒有意義的,它不會限制值的合法範圍,只是規定了Mysql的一些互動工具(如:Mysql命令列客戶端,用來顯示字元的個數)。對儲存和計算來說,INT(11)和INT(20)是相同的。
4.1.2、實數型別
實數是帶有小數部分的數字。
FLOAT和DOUBLE型別支援使用標準的浮點運算進行近似計算。
DECIMAL型別使用者儲存精確的小數。
浮點型別在儲存同樣的範圍的值時,通常比DECIMAI使用更小的空間。
FLOAT使用4個位元組儲存,DOUBLE使用8個位元組。
Mysql使用DOUBLE作為內部浮點計算的型別。
浮點儲存計算會帶來不精確。DECIMAL精確計算的代價高。
4.1.3、字串型別
1)varchar
使用者儲存可變長字串。
Varchar使用1或2個額外的位元組記錄字串長度,如果類最大長度<=255位元組則用1個位元組表示,否則用兩個。
使用場景:
-
- 字串列的最大長度比平均長度大很多。
- 列的更新很少,所以碎片不是問題。
- 使用了像UTF-8這樣複雜的字符集,每個字元都使用不同的位元組數進行儲存。
2)char
Char型別是定長的:Mysql總是根據定義的字串長度,分配足夠的空間。
使用場景:儲存短的,或者所有值都接近同一個長度的。
慷慨是不明智的
使用varchar(5)和varchar(200)儲存的空間是一樣的,那使用短的列,有什麼優勢?
事實證明,更長的列消耗更多的記憶體,因為Mysql通常會分配固定大小的記憶體塊來儲存內部值。尤其是使用內部臨時表進行排序或操作時會特別糟糕。
所以:最好是隻分配真正需要的空間。
3)BLOB和TEXT型別
它們是為了儲存很大的資料而設計的字串資料型別,分別採用二進位制和字串方式儲存。
4)使用列舉(ENUM)代替字串型別
可以用列舉型別代替常用的字串型別。
- 日期和時間型別
Mysql能儲存的最小時間粒度為秒(MariaDB支援微秒級別)。
日期型別有如下兩種:
- DATETIME
能儲存大範圍的值,從1001到9999,精度為秒。使用8位元組儲存。它把時間封裝到格式為YYYYMMDDHHMMSS整數中,與時區無關。
- TIMESTAMP
儲存了從1970年1月1日午夜以來的秒數,和UNIX時間戳相同。使用4位元組儲存空間。只能表示從1970到2038年。
Mysql提供FROM_UNIXTIME()函式把時間戳轉換為日期。並提供UNIX_TIMESTAMP()函式把日期轉換為UNIX時間戳。
除特殊行為外,通常應該儘量使用TIMESTAMP,因為它比DATETIME空間效率更高。
有時人們會將UNIX時間戳儲存為整數值,但這並不會帶來任何收益。用整數儲存時間戳的格式通常不方便處理,所以不推薦這樣做。
4.1.6、選擇識別符號
使用自增整數最好。
4.1.7、特殊型別資料
應該用無符號整數儲存IP地址。用小數點將地址分成四段的表示方法,只是讓人們容易閱讀。用整形儲存能減少空間。但是可讀性低。Mysql提供了兩個函式進行裝換:
Ip->整形:inet_aton()
整形->IP:inet_ntoa()
演算法是:國際上對各國IP地址的區分中使用的ip number
a.b.c.d 的ip number是:
a * 256的3次方 + b * 256的2次方 + c * 256的1次方 + d * 256的0次方。
Mysql schema 設計中的陷阱
- 太多的列
- 太多的關聯。Mysql每個關聯操作最多隻能有61張表。建議單個查詢最好在12個表以內做關聯。
- 全能列舉。過度使用列舉,如下:
- 變相列舉。
列舉列允許列中儲存一組定義值中的單個列,集合(SET)列則允許在列中儲存一組定義值中的一個或多個值。
如下反例:
這種情況該使用列舉列代替集合列。
- 非此發明(Not Invent Here)的NULL
列中避免使用NULL,可以用0,某個特殊值或空字串作為代替。
但是當確實需要表示未知值時也不要害怕使用NULL。
注:Mysql的索引可以儲存NULL值,而Oracle則不會。
4.3、正規化和反正規化
正規化資料中,每個事實資料出現並且只出現一次。
反正規化化的資料庫中,資訊是冗餘的,可能會儲存在多個地方。
4.3.1、正規化的優點和缺點
優點:
- 更新操作比反正規化快。資料正規化化後,就只有很少的或者沒有重複資料,所以需要修改更少的資料。
- 正規化化的表通常更小,可以更好地放在記憶體中,所以執行操作會更快。
- 很少的冗餘,意味著檢索列表資料時更少需要DISTINCT或者GROUP BY語句。
缺點:
- 查詢時通常需要關聯。稍微複雜一點的查詢,可能會更多的關聯,這不但代價昂貴,可能會使一些索引失效。
4.3.2、反正規化的優點和缺點
優點:
- 資料都在一張表中,所以可以很好地避免關聯。
- 因為不需要關聯表,則大部分查詢最差的情況——即使表沒有使用索引,是全表掃描,也可能比關聯要快得多,因為這樣避免隨機I/O。
- 單獨的表也能使用更高效的索引策略。
4.3.3、混用正規化化和反正規化化
完全的正規化化和完全的反正規化化是實驗室裡才有的東西。
4.4、快取表和彙總表
快取表:表示儲存那些可以比較簡單地從schema其他表獲取(但是每次獲取的速度比較慢)資料的表。
彙總表:儲存的是使用GROUP BY語句聚合的表。
當重建彙總表和快取表時,通常保證資料在操作時依然可用。這就需要通過使用“影子表”來實現。它是指一張在真實表“背後”建立的表,當完成建表操作後,可以通過一個原子的重新命名操作切換影子表和原表。如下:
將my_summary這個名字分配給新建的表之前將原始的my_summary表重新命名為my_summary_old,就可以在下一次重建之前一直保留舊版本的資料,如果新表有問題,可以很容易地進行快速回滾。
4.4.1、物化檢視
Mysql並不原生支援物化檢視。
可以使用開源工具Flexviews實現物化檢視。
4.4.2、計數器表
更新計數器表會存在併發問題。
如舊錶:
更新操作:
對更新一行的事務來說,這條記錄上都有一個全域性的互斥鎖(mutex),導致這些事務只能序列執行。
要獲得更高的併發更新效能,可以將計數器儲存在多行中,每次選擇一行進行更新。
新表:
預先在新表增加100行資料,然後隨機選擇一個槽(slot)進行更新
還有一個需求,每天開始一個新的計數器。表設計如下:
這個場景就不能用前面的例子那樣預生成行,而要用on duplicate key update
4.5、加快ALTER TABLE 操作的速度
技巧:
- 先在一臺不提供服務的機器上執行alter table 操作,然後和提供服務的主庫進行切換。
- “影子拷貝”:用要求的表結構建立一張和源表無關的新表,然後通過重新命名和刪表操作交換兩張表。
有一些工具可以完成影子拷貝工作:“online schema change”、Percona Toolkit、Flexviews
不是所有的alter table 操作都會引起表重建。
如修改欄位預設值:
慢方法:
理論上,Mysql可以跳過建立新表的步驟。列的預設值實際上存在表的.frm檔案中,所以可以直接修改這個檔案而不需要改動表本身。
然而,所有MODIFY COLUMN 操作都將導致表重建。(沒驗證過)
快方法:
這個語句直接修改.frm檔案而不涉及表資料,所以這個操作非常快。
- 建立高效能的索引
索引(在Mysql也叫做“鍵(key)”)。
5.1、索引基礎
索引可以包含一個或多個列的值。列的順序很重要,因為Mysql只能高效地使用索引的最左字首列。
主鍵是非空的唯一索引
5.1.1、索引型別
索引是儲存引擎層實現的。所以沒有統一的索引標準。
Mysql支援的索引型別如下:
1)B-Tree索引
儲存引擎以不同的方式使用B-Tree索引,效能各有不同。(InnoDB叫B+Tree)
MyISAM使用字首壓縮技術使得索引更小,但InnoDB則按照原資料格式進行儲存。
MyISAM索引通過資料的物理位置引用被索引的行,而InnoDB則根據主鍵引用被索引的行。
B-Tree通常意味這所有的值都是按順序儲存的,並且每個葉子頁到根的距離相同。
InnoDB的索引結構如下:
流程:
- 從索引的根節點開始搜尋,根節點的槽中存放了指向子節點的指標。
- 通過比較節點頁的值和要查詢的值,可以找到合適的指標(定義了子節點頁中值的上限和下限)進入下層子節點。
- 葉子節點的指標指向的是被索引的資料,而不是其他的節點頁,這樣就獲取到所需的資料。
B-Tree對索引列式順序組織儲存的,很適合查詢範圍資料。
假設有如下資料表:
下圖顯示了索引是如何組織資料的儲存的;
因為索引樹的節點是有序的,所以索引還可以用於查詢中的ORDER BY 操作。
B-Tree索引的限制:
- 如果不是按照索引的最左類開始查詢,則無法使用索引。
- 不能跳過索引,索引無法用於查詢姓為:Smith且在特定日期出生的人,如果不指定名(first_name),則Mysql只能使用索引的第一列。
- 如果查詢有某個列的範圍查詢,則右邊所有列都無法使用索引優化查詢。
如:where last_name = ‘Smith’ and first_name like ‘J%’ and job = ‘1976-12-23’。只能用到索引的前兩列,因為like是個範圍條件。
5.2、索引的優點
- 減少伺服器需要掃描的資料量。
- 幫助伺服器避免排序和臨時表。
- 將隨機I/O變為順序I/O.。
最常見的B-Tree索引,按照順序儲存資料,所以Mysql可以用來做ORDER BY 和GROUP BY操作。
三星系統(three-star system):
一星:索引將相關的記錄放到一起。
二星:索引中的資料順序和查詢中的排列順序一致。
三星:索引中的列包含了查詢中需要的全部列。
索引是最好的解決方案嗎?
對於非常小的表,簡單的全表掃描更高效。
對於中到大型表:索引非常有效。
對於特大型表;需要一種技術可以區分出查詢需要的一組資料。(如分割槽、使用元資料表)。
5.3、高效能的索引策略
5.3.1、獨立的列
如果查詢中的列不是獨立的,則Mysql不會使用索引。
我們應該養成簡化Where條件的習慣,始終將索引列,放在單獨放在比較符號的一側。
如:這個查詢無法使用actor_id列的索引
這也是一個:
5.3.2、字首索引和索引選擇性
當需要索引很長的字元列時,就要考慮字首索引,否則索引變得大且慢,特別是對BLOB、TEXT或者很長的VARCHAR型別的類,必須要用字首索引,因為Mysql不允許索引這些列的完整長度。
索引的選擇性:不重複的索引值(也稱基數,cardinality)和資料表的記錄總數(#T)的比值,範圍從1/#T到1之間。索引選擇性越高,查詢效率越高,因為選擇性高的索引能在查詢時過濾更多的行。
5.3.3、多列索引
當查詢的where條件中有or 時或多個and時,會執行索引合併策略(or條件的聯合(union)、and條件的相交(intersection),組合前兩種情況的聯合及相交)。
當出現索引的合併策略時,說明索引建得糟糕:
- 伺服器出現多個索引做相交操作時(通常有多個and條件),通常意味著需要一個包含所有相關列的多列索引,而不是多個獨立的單列索引。
- 當伺服器需要多個索引做聯合操作時(通常有多個or條件),通常要消耗大量的CPU和記憶體資源在演算法的快取、排序和合並操作上。
- 優化器不會把這些計算到“查詢成本(cost)”中,優化器只關心隨機頁面讀取。使得查詢成本被低估。
解決方案:
- 通過引數optimizer_switch來關閉索引合併功能。
- 也可以使用IGNORE INDEX提示讓優化器忽略掉某些索引。
5.3.4、選擇合適的索引列順序
在一個多列索引中,索引列的順序意味著索引首先按照最左列進行排序,其次是第二列,等等。所以,索引可以按照升序或者降序進行掃描,以滿足精確符合列順序的ORDER BY、GROUP BY和DISTINCT等子句的查詢需求。
儘管選擇性和基數的經驗法則(選擇性高的列放在最左邊)值得去研究和分析,但一定要記住別忘了where子句的排序、分組和範圍條件等其他因素。
5.3.5、聚簇索引
不是一種單獨的索引型別,而是一種資料儲存方式。
InnoDB的聚簇索引實際上在同一個結構中儲存了B-Tree索引和資料行。
聚簇:表示資料行和相鄰的鍵值緊湊地儲存在一起。
聚簇索引中的葉子頁(left page)包含了行的全部資料,但是節點頁只包含了索引列。如圖所示:
InnoDB通過主鍵聚集資料,圖中被索引的列就是主鍵列。
如果沒有定義主鍵,InnoDB會選擇一個唯一的非空索引代替。
聚集資料的優點:
- 把相關的資料儲存在一起。
- 資料訪問更快。聚簇索引將索引和資料儲存在同一個B-Tree中,因此從聚簇索引中獲取資料比非聚簇索引要快。
- 使用覆蓋索引掃描的查詢可以直接使用頁節點中的主鍵值。
缺點:
- 插入速度嚴重依賴於插入順序。按照主鍵的順序插入是載入資料到InnoDB表中速度最快的方式。
- 更新聚簇索引列的代價高。會強制將每個被更新的行移動到新的位置。
- 基於聚簇索引的表在插入新行,或者主鍵被更新導致需要移動行的時候,可能面臨“頁分裂”的問題。
- 二級索引(非聚簇索引)可能比想象的要更大,因為二級索引的葉子節點包含了引用行的主鍵列。
- 二級索引訪問需要兩次索引查詢,而不是一次。(二級索引葉子節點儲存的不是指向行的物理位置的指標,而是行的主鍵值)
InnoDB和MyISAM的資料分佈對比
InnoDB和MyISAM對該表的儲存
MyISAM的資料分佈:按資料插入順序儲存在磁碟上。
MyISAM在主鍵索引和其他索引在結構上是一樣的:
MyISAM需要獨立地對行進行儲存。
所以說:主鍵索引是一個名為PRIMAAY的唯一非空索引。
InnoDB的資料分佈,因為InnoDB支援聚簇索引,所以使用非常不同的方式儲存同樣的資料。如下圖:
在InnoDB中,聚簇索引就是表,它不僅存索引,還存了整個表的資訊(col2)。
InnoDB的二級索引和聚簇索引很不同,InnoDB二級索引的葉子節點儲存的不是“行指標”,而是主鍵值,並以此作為指向行的“指標”。該策略會減少當出現行移動或者資料頁分裂時二級索引的維護工作,但會讓二級索引佔用更多的空間。
下圖顯示二級索引的分佈結構圖:
用一張圖進行對比如下:
主鍵順序最好使用:自增主鍵,避免用UUID。
順序的主鍵什麼時候造成更壞的結果?
高併發工作負載時,間隙鎖的競爭、AUTO_INCREAMENT鎖機制、主鍵的上界會成為“熱點”。
5.3.6、覆蓋索引
如果一個索引包含(或者說覆蓋)所需要查詢的欄位的值,我們就稱為“覆蓋索引”。
優點:
- 索引條目遠小於資料行大小,如果只需要讀取索引,那Mysql會極大地減少資料訪問量。
- 索引按順序儲存的(至少在單頁內如此),所以對於I/O密集型的訪問查詢會比隨機從磁碟讀取每一行資料的I/O要少得多。
- 由於InnoDB的聚簇索引,覆蓋索引對InnoDB表特別有用。
- 儲存引擎MyISAM在記憶體中只快取索引,資料則依賴於作業系統來快取,因此要訪問資料需要一次系統呼叫。覆蓋索引就能避免查詢系統中的資料。
覆蓋索引必須儲存索引列的值,因此Mysql只能使用B-Tree索引做覆蓋索引。
當發起以被索引覆蓋的查詢時,在Explain的Extra列可以看到“Using index”的資訊。
5.3.7、使用索引掃描來做排序
有兩種方式生成有序的結果:1、通過排序操作。2、按索引順序掃描(如果Explain 出來的type列的值為“index”,說明使用了索引掃描來做排序)。
只有ORDER BY子句滿足索引的最左字首要求,Mysql才會利用索引排序。(例外,order by子句不滿足索引的最左字首的要求,但是多列索引的最左列為常量時,會利用索引排序)。
5.3.8、壓縮(字首壓縮)索引
MyISAM壓縮索引塊的方法是:
如:索引塊第一個值是“perform”,第二個值是:“performance”,那麼第二個值的字首壓縮後儲存的是類似“7,ance”這樣的形式。
5.3.9、冗餘和重複索引
1、建立了索引(A,B),再建立索引(A)就是冗餘索引,因為這只是前一個索引的字首索引。(這種冗餘是對B-Tree索引來說)
2、索引擴充套件為(A,ID),其中ID是主鍵,對於InnoDB來說,主鍵列包含在二級索引中了,所以這也是冗餘的。
大多數情況都不需要冗餘索引,應該儘量擴充套件已有的索引而不是建立新索引。
但也有出於效能方便的考慮需要冗餘索引,因為擴充套件已有的索引會導致其變得太大,從而影響其他使用該索引的查詢效能。
一般來說:增加新索引將會導致Insert、update、delete等操作的速度變慢。
5.3.10、未使用的索引
有些伺服器永遠不用的索引,這些事累贅,建議刪除。
5.3.11、索引和鎖
索引可以讓查詢鎖定更少的行。InnoDB只有在訪問行的時候才會對其加鎖,而索引能夠減少InnoDB的訪問行數,從而減少鎖的數量。
5.5、總結
- 單行訪問時很慢的。
- 按順序訪問訪問資料是很快的。
- 索引覆蓋查詢是很快的。
- 查詢效能優化
高效能:需要庫表結構優化、查詢優化、索引優化齊頭並進。
6.1、為什麼查詢速度會慢
查詢的生命週期:
客戶端——伺服器——伺服器解析——生成執行計劃——執行——返回結果給客戶端
執行是最重要的階段:包括大量為了檢索資料到儲存引擎的呼叫以及呼叫後的資料處理(排序、分組)
6.2、慢查詢基礎:優化資料訪問
對於慢查詢,可以通過下面兩個步驟來分析:
- 確認應用程式是否在檢索大量超過需要的資料。
- 確認伺服器層是否在分析大量超過需要的資料行。
6.2.1、是否向資料庫請求了不需要的資料
- 查詢不需要的記錄
常見錯誤:以為Mysql只會返回需要的資料,實際上Mysql卻是先返回全部結果集再進行計算。
最簡單的解決方法:在查詢後面加limit。
- 多表關聯時返回全部列
如:你想查詢所有在電影academy Dinosaur中出現的演員,千萬不要這樣寫,它會返回三個表的全部資料。
正確的方式,只取需要的列:
- 總是取出全部列
每次看到select * 的時候都需要用懷疑的眼光審視,是不是需要返回全部列。取全部列會讓優化器無法完成索引覆蓋掃描這類優化。
但是返回超過需要的列,會簡化開發。
所以自己要權衡。
- 重複查詢相同的資料,可以自己做快取。
6.2.2、Mysql是否在掃描額外的記錄
查詢查詢開銷的三個指標
- 響應時間
- 掃描的行數
- 返回的行數
- 訪問型別:由慢到快分別是:全表、索引、範圍、唯一索引、常數引用
Mysql使用如下三種方式應用where條件,從好到壞依次是:
- 索引中使用where條件過濾不需要的行。在儲存引擎完成。
- 使用索引覆蓋掃描(Extra列出現:Using index)來返回記錄,在mysql伺服器層完成。
- 從資料表返回資料,然後過濾不滿足條件的過濾(在Extra列中出現Using Where)。在Mysql服務層完成。
6.3、重構查詢的方式
- 一個複雜的查詢還是多個簡單的查詢
- 切分查詢
- 分解關聯查詢
上面的思路是:分而治之
6.4、查詢執行的基礎
查詢的執行路徑如下:
6.4.1、Mysql客戶端/伺服器通訊協議
C/S的通訊協議是:半雙工的。
需要了解Mysql的查詢狀態。
6.4.2、查詢快取
查詢快取時一個對大小寫敏感的雜湊查詢實現,查詢和快取中的查詢及時只有一個位元組不同,也不會匹配快取結果。
6.4.3、查詢優化處理
通過下圖,展示Mysql如何進行關聯:
- 檔案排序
當不能用索引進行排序時,Mysql需要自己進行排序,如果資料量小,則在記憶體中進行,如果資料量大則需要你使用磁碟,不過mysql統一稱為:檔案排序(fiiesort)
兩種排序演算法:(舊)
兩次傳輸排序:讀取行指標和需要排序的欄位,排序後,再根據排序結果讀取所需要的資料行。
單詞傳輸排序:(新)
先讀取所需要的所有列,然後再根據給定列進行排序,然後直接返回排序結果。
在關聯查詢時,如果需要排序,Msyql會分兩種情況處理:
- 當order by的所有列都來自關聯的第一個表,那麼在關聯處理第一個表時,就進行檔案排序。Extra欄位顯示:using filesort
- 除此之外,會將關聯的結果放到一個臨時表中,然後所有的關聯處理後,再進行檔案排序。Extra欄位顯示:Using temporary;Using filesort。
6.4.4、查詢執行引擎
6.4.5、返回結果給客戶端
6.5、MySQL查詢優化器的侷限性
6.5.1、關聯子查詢
In 加子查詢,效能通常非常糟糕。建議使用join或exists()等效的改寫查詢來獲取更好的效率。
注:需要通過測試來驗證對子查詢的執行計劃和響應時間的假設,而不要聽信什麼“絕對真理”
6.5.2、UNION的限制
6.6、查詢優化器的提示
通過顯示配置sql語句,實現對優化器的提示。
常用:use index、ignore index 、force index
6.7、優化特定型別查詢
6.7.1、優化count()查詢
- Count()作用
- 統計某個列值的數量。在()裡指定了列和列的表示式,就統計這個表示是有值的結果數。
- 統計行數。當確定()內不可能為NULL,就統計行數。
Count(*)就是統計行數的標準寫法。
- MyISAM神話
MyISAM引擎使用count(*),且沒有任何where條件時,速度會很快。
- 簡單的優化
例子1:
例子2:
- 更復雜的優化
熟悉的困境:“快速、精確和實現簡單”永遠只能滿足三選二。
6.7.2、優化關聯查詢
- 確保on或者using子句中的列上有索引。
- 確保任何GROUP BY和ORDER BY中的表示式只涉及到一個表中列。
6.7.3、優化子查詢
6.7.4、優化GROUP BY 和DISTINCT
6.7.5、優化LIMIT分頁
若表很大,需要分頁時,優化方法是:儘可能使用索引覆蓋掃描,而不是查詢所有列,然後根據需要做一次關聯操作再返回所需的列。
一定要加Order by
如:select * from payment_logs order by id limit 1000000,20
可改寫為:
select * from payment_logs a inner JOIN
(
select id from payment_logs order by id limit 1000000,20
) b
on a.id = b.id
6.7.7、優化union查詢
6.9、總結
優化需要三管齊下:不做、少做、快速地做。
- Mysql高階特性
7.1、分割槽表
對使用者來說,分割槽表是一個獨立的邏輯表,底層由多個物理子表組成。
分割槽實際上是對一組底層表的控制代碼物件(Handler Object)的封裝。對分割槽表的請求,都會通過控制代碼物件轉化成對儲存引擎的介面呼叫。
目的:將資料按照一個較粗的粒度分在不同的表上。
使用場景:
- 表非常大,以至於無法全部放在記憶體中,或者表的最後部分有熱點資料,其他均是歷史資料。
- 分割槽表的資料更容易維護。想批量刪除大量的資料,可以使用清除整個分割槽的方式。
- 可分佈在不同的物理裝置上。
- 備份和恢復獨立的分割槽。
分割槽的限制:
- 一個表最多隻能有1024個分割槽。
- 分割槽表示式必須是整數,或返回整數的表示式。MySQL5.5中,可以直接使用列進行分割槽。
- 分割槽欄位有主鍵或唯一索引的列,那他們要包含進來。
- 分割槽表無法使用外來鍵約束。
7.1.1、分割槽表的原理
分割槽表由多個相關底層表實現,這些底層表也是由控制代碼物件(Handler Object)表示。儲存引擎管理分割槽表和其他表都一樣,從儲存引擎來看,分割槽底層表和普通表沒區別。
分割槽表的索引只是在各個底層表上各自加上一個完全相同的索引。
- Select查詢
先鎖住所有底層表,再判斷是否可以過濾部分分割槽,在訪問各區資料。
- Insert操作
先鎖住——再確定——最後寫入
- Delete操作
先鎖住——再確定——最後刪除
- Update操作
先鎖住——確定更新記錄放哪個分割槽——取數更新——判斷更新後的資料在哪個分割槽——對底層表進行寫入——對原資料底層表進行刪除
雖說每個操作都會“先開啟並鎖住所有的底層表”,但並不一定是鎖住全表,如果儲存引擎能夠自己實現行級鎖,如InnoDB,就不會鎖全表,這個加鎖和解鎖的過程與查詢類似。
7.1.2、分割槽表的型別
一般用按範圍分割槽。
7.1.3、如何使用分割槽表
資料量很大時,B-Tree索引就不起作用了,除非是索引覆蓋查詢,否則資料庫伺服器需要根據索引掃描的結果回表。
分割槽可理解為索引的最初形態。
7.1.4、什麼情況下會出問題
- Null值會使分割槽過濾無效。經測試,mysql5.6已處理這個問題
- 分割槽列和索引列不匹配
- 選擇分割槽的成本很高
- 開啟並鎖住所有底層表的成本可能很高
- 維護分割槽可能成本很高。如重組分割槽。
7.1.5、查詢優化
使用explain partition可以觀察到優化器使用了哪些分割槽。
7.2、檢視
檢視本身是一個虛擬表、不存放任何資料。
方法一:實現檢視有一個簡單方法,將select結果放在一個臨時表中。
Temporary table:只在當前連線可見,當關閉連線,Mysql會刪除表並釋放空間。
方法二:講檢視定義的Sql合併進查詢SQL。
該兩種演算法的實現如下,建議使用合併演算法。
7.3、外來鍵約束
InnoDB是目前Mysql中唯一支援外來鍵的內建索引。
使用外來鍵是有成本的,外來鍵通常要求在修改資料時都要在另外一張表中多執行一次查詢操作。
7.4、Mysql內部儲存程式碼
7.5、遊標
Mysql在服務端提供只讀的、單向的遊標,而且只能在儲存過程或者更底層的客戶端API使用。
7.6、繫結變數
7.10、全文索引
使用場景:通過關鍵字的匹配來進行查詢過濾,需要基於相似度的查詢,而不是精確查詢。
支援各種字元內容搜尋:char、varchar、text
MATCH AGAINST
7.10.1、自然語言的全文索引
使用語法:match(列,【列】) against(需要查詢的內容)
如下:
7.10.2、布林全文索引
7.12、查詢快取
Mysql查詢快取儲存查詢返回的完整結果。當查詢命中該快取,Mysql會立刻返回結果,跳過解析、優化和執行階段。
查詢快取對應用程式是完全透明的。
7.12.1、Mysql如何判斷快取命中
Mysql判斷快取命中的方法很簡單:快取存放在一個引用表中,通過一個雜湊值引用。
當查詢語句中有一些不確定的資料時,則不會被快取。如函式now()、currrent_date()的查詢不會被快取。
注意:查詢快取對讀寫帶來額外消耗:
- 讀之前必須先檢查是否命中快取。
- 如果這個讀查詢可以被快取,當執行完後,Mysql若發現查詢快取中沒有這個查詢,會將其結果存入查詢快取,這會帶來額外的系統消耗。
- 當向某個表寫入資料時,Mysql必須將對應表的所有快取都設定失效。如果查詢快取非常大或者碎片很多,這個操作可能帶來很大的系統消耗。
7.12.3、什麼情況下查詢快取能發揮作用
查詢快取可以由如下公式計算:Qcache_hits/(Qcache_hits+Com_select)
7.12.4、如何配置和維護查詢快取
7.12.7、查詢快取的替代方案
查詢快取工作的原則是:執行查詢最快的方式是不去執行。
- 優化伺服器設定
Mysql有大量的引數可以修改——但不應該隨便去修改。通常只需要把基本配置項配置正確(大部分情況下只有很少一些引數是真正正確的),應該花更多時間在schema的優化、索引、以及查詢設計上。
8.1、MySQL配置的工作原理
在類unix系統上:配置檔案一般在/etc/my.cnf或者/etc/msyql/my.cnf中。
找到配置檔案的路徑;
Which mysqld
**/**/mysqld –verbose –help|grep -A 1 ‘Default options’
8.3、建立MySQL配置檔案
InnoDB在大多數情況下執行得很好,配置大小合適的緩衝池(Buffer Pool)和日誌檔案(Log File)是必須的。
8.4、配置記憶體使用
8.4.5、InnoDB緩衝池(Buffer Pool)
如果大部分都是InnoDB表,InnoDB緩衝池或許比其他任何東西更需要記憶體。
InnoDB緩衝池並不僅僅快取索引,還快取行的資料、自適應雜湊索引、插入緩衝、鎖,以及其他內部資料結構。
8.5、配置Mysql的I/O行為
8.5.1、InnoDB I/O配置
InnoDB事務日誌
InnoDB使用日誌來減少提交事務時的開銷。因為日誌中已經記錄了事務,就無需在每個事務提交時吧緩衝池的髒塊重新整理到磁碟中。
InnoDB用日誌把隨機I/O變成順序I/O。一旦日誌安全寫到磁碟,事務就持久化了,即使變更還沒寫到資料檔案,如果發生了糟糕的情況,InnoDB可以重放日誌並且恢復已經提交的事務。
8.7、基於工作負載的配置
8.7.2、優化排序
- 作業系統和硬體優化
9.1、什麼限制了MySQL的效能
最常見的兩個瓶頸:CPU 和 I/O。
當找到一個限制系統的因素時,應該問問自己:“是這個部分本身的問題,還是系統中其他不合理的壓力轉移到這裡所導致的?”
如:記憶體不足時,MySQL可能必須刷出快取來騰出空間給需要的資料,然後過一會,再讀回剛剛重新整理的資料,本來是記憶體不足,去導致出現了I/O容量不足。
- 複製
10.1、複製概述
複製解決的基本問題是:讓一臺伺服器的資料與其他伺服器保持同步。一臺主庫的資料可以同步到多臺備庫上,備庫本身也可以被配置成另外一臺伺服器的主庫。
MySQL支援兩種複製方式:基於行的複製、基於語句的複製。
實現方式:通過在主庫上記錄二進位制日誌,在備庫重放日誌的方式來實現非同步的資料複製。
Mysql複製大部分是向後相容的。
10.1.1、複製解決的問題
10.1.1、複製如何工作
三個步驟:
- 在主庫上把資料更改記錄到二進位制日誌(Binary log)中。(這些記錄被稱為二進位制日誌事件)。
- 備庫將主庫上的日誌複製到自己的中繼日誌(Relay Log)中。
- 備庫讀取中繼日誌的事件,將其重放到備庫資料上。
邏輯圖如下:
複製用到三個執行緒來處理:
- 備庫的I/O執行緒,它更主庫建立一個普通的客戶端連線。
- 主庫的二進位制轉儲執行緒,它會讀取主庫上二進位制日誌中的事件。
- 備庫的SQl執行緒,它從中繼日誌中讀取事件並在備庫執行。
10.2、配置複製
三個步驟:
- 在每臺伺服器上建立複製賬號
- 配置主庫和備庫
- 通知備庫連線到主庫並從主庫