日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

MySQL高级 —— 高性能索引

發布時間:2025/3/12 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MySQL高级 —— 高性能索引 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

引言

最近一直在抱著《高性能MySQL(第三版)》研究MySQL相關熱點問題,諸如索引、查詢優化等,這階段的學習是前一段時間MySQL基礎與官方的“閱讀理解”的進一步延伸。

書中第五章詳細闡述了如何設計高性能的索引,以及索引的諸多注意事項。

之前關于索引的學習,包括《MySQL 高級 —— 復合索引簡介(多列索引)》、《MySQL 高級 —— 索引實現的思考》、《MySQL 優化 —— MySQL 如何使用索引》這三篇文章,著實費了一番功夫,但隨著對書中第五章內容的逐步學習,也越發覺得自己關于索引的理解和學習還不夠充分,遂以此篇《高性能索引》作以總結,本篇博客可能會與之前的這三篇有部分內容重合,重合的部分,我會一筆帶過或做一些必要的總結,多數內容都是新領悟到的知識。未來如果還有索引的新認識,應該不會再開新的文章了,而是會在這些文章的內容之上,進行更新,以免造成整體博客的質量下降。

一、索引的常識

索引是存儲引擎層實現的一種數據結構,因此,索引的實現非常依賴于具體的存儲引擎。

索引的三大優點

1、大大減少了服務器需要掃描的數據量
2、幫助服務器避免排序和臨時表
3、索引可以將隨機IO變為順序IO

評價索引優劣的三星標準

1、索引將相關記錄放到一起可以獲得一星
2、索引中的數據順序可以滿足排序需求則獲得二星
3、索引中的列包含了查詢中需要的全部列可獲得三星。能夠獲得此三星的索引,也叫"三星索引"。

二、B樹索引

大多數場景的索引,都是指 InnoDB 存儲引擎中的 B+Tree索引,這是最常見的索引。

詳細的B樹結構分析可以參考《MySQL 高級 —— 索引實現的思考》

在學習B-Tree 索引的時候,重點是要理解 B+Tree這種數據存儲結構,它的很多特性都是由這種特定的存儲結構決定的。為什么會有最左前綴原則?為什么索引可以直接排序數據?這些特性都要求我們深入了解 B+樹詳細存儲結構,明白非葉子節點和葉子節點的差別,明白葉子節點中存儲的內容等等,只有深入了解了B+Tree的存儲結構,才能更容易地記住各種復雜的索引特性,和索引使用限制。

B-Tree對索引列是順序存儲的即索引的節點是有序的。它的另一個重要特點是,或者說與基本的B樹索引的區別是,每個葉子節點都包含指向下一個葉子節點的指針(實際上是一個雙向指針),葉子節點之間可以看做是一個有序鏈表的結構,這是為了方便葉子節點的范圍遍歷。

三、復合索引(或叫多列索引)

參考《MySQL 高級 —— 復合索引簡介(多列索引)》

多列索引支持的查詢類型:

1、全列匹配
2、最左前綴列匹配
3、最左前綴列的最左前綴匹配:比如,idx_name_birth(last_name, first_name, birthday) 可以幫助查找last_name的前綴字符,比如last_name以'A'開頭。
4、最左前綴列的范圍匹配。
5、索引覆蓋查詢:只訪問索引的查詢,而無需訪問數據行。

前四點,主要是圍繞B+Tree索引的"最左前綴原則"

另外,索引的另一個重要用途是排序。如果 ORDER BY 子句滿足前面列出的幾種查詢類型,那么索引也是可以直接排序數據的。

對于最左前綴原則,有一個小技巧,因為某些原因,查詢中可能不會涉及到索引中靠前的列,那么可以使用 IN()函數,將左前綴的列變為多個等值條件,以符合最左前綴原則,避免讓索引失效。但這種方法不適合可選列表太大的情況。

四、哈希索引

哈希索引是基于哈希表實現,只能精確匹配索引所有列的查詢才有效。因此使用范圍非常有限,但如果確定適合哈希索引,那么性能將會有質的飛躍。存儲引擎會為每行數據對應的索引列計算一個哈希碼,哈希碼是一個較小的值,哈希索引保存哈希碼和指向每個數據行的指針。

在MySQL中,只有Memory存儲引擎顯式支持哈希索引,是默認索引類型。

Memory引擎支持非唯一哈希索引,如果多個列的哈希值相同,索引會以鏈表的方式存放多個記錄指針到同一個哈希碼下。

哈希索引的結構:

槽(slot) 值(value)
2323 ? ? 指向第1行指針
2458 ? ? 指向第4行指針
7437 ? ? 指向第100行指針
... ? ? ?...

哈希索引的結構本身也是一種key-value 結構,key就是索引列的哈希碼(哈希碼通過一個哈希函數來生成),值就是指針。在哈希索引中,保存key的結構成為slot——槽,由于哈希碼是整數,所以槽是有序的,但指針是無序的

4.1 哈希索引在查詢時的工作過程

假設下面的查詢中 fname 列上有哈希索引:

SELECT ... FROM testhash WHERE fname = 'Peter';

那么上面的查詢在執行時,MySQL會先計算'Peter'的哈希碼,然后在哈希索引中找到對應的哈希碼,以及其對應的行指針,最后比較行中的索引值是否等于'Peter'。

4.2 哈希索引為什么快?

哈希索引的工作原理非常簡單,因為本身只存儲整數型的哈希值,其索引結構非常緊湊,因此命中目標的速度非常快。

4.3 哈希索引的缺點(限制)

1、哈希索引本身并不存儲行數據,所以不可避免地需要讀取行數據。不過這點對性能影響不大。
2、哈希索引無法用于排序
3、哈希索引不支持部分索引列匹配查找。記住,哈希索引始終是使用索引列的全部內容來計算哈希值的。
4、哈希索引只支持等值比較。提示,IN()也是等值比較。
5、哈希沖突,性能下降。哈希沖突是影響哈希索引性能的關鍵因素,發生哈希沖突的行的指針,會以鏈表的形式存儲于同一個哈希碼之下,因此存儲
引擎需要逐行比較才能最終確定結果,因此,哈希沖突越多,哈希索引性能越低。
6、哈希沖突,維護成本更高。當刪除數據行或者增加數據行時,如果發生哈希沖突,那么就需要遍歷沖突行指針鏈表,增加維護成本。

4.4 InnoDB與哈希索引

InnoDB引擎有一個叫做"自適應哈希索引"的特殊功能。當InnoDB發現某個索引值使用非常頻繁時,會在內存中基于B-Tree索引之上再創建一個哈希索引。不過這個功能是完全自動的,用戶無法配置或控制,但可以關閉。

4.5 自定義哈希索引

我們可以自定義一種"偽哈希"索引。

其主要應用場景是:為長字符串(如url)創建很小的哈希索引,既節省索引存儲空間,又能更快查詢。如果有針對URL進行搜索的需求,那么非常適合建立這種"偽哈希索引"。

經典案例

某InnoDB表有一個url字段,如果正常對該列索引,并不是一個很明智的選擇,因為url一般都很長,且毫無順序。

改造方法是,在表中建立一個 url_crc 列,該列用于存儲 url 列的由MySQL內建函數 CRC32()生成的哈希碼,那么查詢的時候只需:

SELECT ... FROM tburl WHERE url = "http://www.mysql.com" AND url_crc = CRC32('http://www.mysql.com');

這樣做性能會非常高。但其實仔細觀察可以發現這種偽哈希索引的本質,其實就是在表中保存url的哈希碼,用B-Tree再去索引哈希碼。

但注意,查詢需要同時指定哈希碼和原列值,這是為了偽哈希索引模擬哈希索引內部解決哈希沖突的操作

另外,哈希列做索引還需要注意維護工作,一般可以使用觸發器插入或更新時自動維護哈希列值

哈希值避免使用SHA1()或MD5()作為哈希函數。因為這兩個函數計算出來的哈希值是非常長的字符串。它們都是強加密函數,設計目的就是最大限度消除沖突,但這里并不需要這么高的要求。簡單哈希函數的沖突在一個可接受的范圍內就可以。

五、高性能索引策略

5.1 過濾條件中要使用獨立的索引列

索引列不能是表達式的一部分,也不能是函數的參數,諸如:WHERE act_id + 1 = 5;這樣的形式,act_id 是無法使用索引。要養成簡化WHERE子句的習慣,始終將索引列單獨放在比較符的一側。

5.2 前綴索引

當需要索引很長的字符串時,可以考慮前綴索引、偽哈希索引的設計組合。這樣做的時候,需要考慮索引空間以及索引選擇性的問題。具有較好選擇性的前綴索引,可以在存儲空間和性能之間尋找平衡點,簡言之,要選擇足夠長的前綴以保證較高的選擇性,同時又不能太長(節約空間)

什么是索引的選擇性

基數(即可選值)和數據表記錄總數的比值。范圍是0到1,唯一索引的選擇性是1,性能最好。

前綴的長度選擇(平衡點),有兩種考量方式:

1、前綴重復次數,接近于全字符串重復次數的時候。

2、直接計算選擇性(平均選擇性)。如:

SELECT ?COUNT(DISTINCT city)/COUNT(*) AS origin,COUNT(DISTINCT LEFT(city, 3))/COUNT(*) AS sel3,COUNT(DISTINCT LEFT(city, 4))/COUNT(*) AS sel4,COUNT(DISTINCT LEFT(city, 5))/COUNT(*) AS sel5,COUNT(DISTINCT LEFT(city, 6))/COUNT(*) AS sel6,COUNT(DISTINCT LEFT(city, 7))/COUNT(*) AS sel7 FROM city;

但注意,第二種方法計算的是平均選擇性,必須要在幾個候選項上,結合第一種方法,觀察具體重復的頻率是否真的接近原字符串。

另外,前綴索引無法用于排序或分組,也無法做索引覆蓋掃描。這都是因為索引中存儲的是原字符串的前綴而不是原字符串。前綴索引的常見應用是針對十六進制唯一ID,一般采用長度為8的前綴索引通常能夠顯著提升性能。

5.3 優先使用多列索引

大多數情況,多列索引(復合索引)的性能要優于單列索引。

5.4 合適的索引順序

定義索引(B-Tree索引)時索引列的先后順序很重要。一般的法則是:將列值選擇性高的索引列放在靠前位置。這個法則非常適用于優化WHERE 條件的查找。但也需要結合值的分布(即具體值)進一步考量。這和前綴索引需要考慮的問題類似。可能還需要根據那些頻率最高的查詢來調整索引列的順序。

具體操作的方法是統計一下指定常量對應的數量:

SELECT SUM(stuff_id = 2), SUM(customer_id = 584) FROM tb;

當觀察到對應的重復數量后,應該將重復更少的列放到多列索引靠前的位置,但由于上面的統計很依賴于具體的值,所以在此之前還需要考察一件事,即平均選擇性(參考前面的總結),例如:

SELECT?COUNT(DISTINCT staff_id) / COUNT(*) AS staff_id_selectivity,COUNT(DISTINCT customer_id) / COUNT(*) AS customer_id_selectivity,COUNT(*) FROM tb;

只要結合這兩方面考量,大多數情況下的查詢,索引列的順序都是比較合適的。

5.5 聚簇索引

這是一種數據存儲方式。用B-Tree結構同時保存索引和數據行。聚簇索引中,數據行保存在索引的葉子節點中。
因為聚簇索引會直接用于存儲數據行全部數據,所以一個表只能有一個聚簇索引(但覆蓋索引可以模擬多個聚簇索引)。葉子節點包含數據行全部數據,普通節點只包含索引。

在MySQL中只支持主鍵的聚簇索引。因此InnoDB會建議用戶定義主鍵來做聚簇索引,如果表中沒有主鍵,InnoDB會選擇一個非空且唯一索引代替,如果也沒有,InnoDB會隱式地定義一個主鍵來做聚簇索引。

聚簇索引的優點:

1、數據訪問更快。

2、使用覆蓋索引掃描的查詢可以直接使用葉子節點中的主鍵值。?

聚簇索引的缺點

1、插入速度嚴重依賴于插入順序。按照主鍵順序插入是加載數據到InnoDB表中速度最快的方式。

2、更新聚簇索引列的代價很高。因為會強制InnoDB將每個被更新的行移動到新的位置。

3、基于聚簇索引的表在插入新行,或者主鍵被更新需要移動行的時候,可能會出現"頁分裂"的問題。當行的主鍵值要求必須將這一行插入到某個已滿的頁中時,存儲引擎會將該頁分裂成兩個頁面來容納該行,也分裂會導致表占用更多的磁盤空間。

4、聚簇索引可能導致全表掃描變慢,尤其是行比較稀疏的時候,或者由于頁分裂導致數據存儲不連續的時候。

5、二級索引(聚簇索引表的非聚簇索引)可能比想象的要更大,因為在二級索引的葉子節點中包含了引用行的主鍵列。

6、二級索引需要兩次索引查找。二級索引中保存的"行指針"的本質,是主鍵值,而不是數據行的物理地址指針。對于InnoDB,自適應哈希索引能夠減少這樣的重復工作。

5.6 覆蓋索引

MySQL可以通過索引來獲取列的數據。覆蓋索引就是旨在直接從索引中獲取數據,從而避免讀取行,極大地提升查詢的性能

如果一個索引包含(或者說覆蓋)所有需要查詢的字段值,我們就稱之為——覆蓋索引。SELECT 和 WHERE條件中的字段都出現在索引中,即為覆蓋索引

覆蓋索引的優點:

1、索引條目通常遠小于數據行大小。更容易全部放入內存中。

2、索引是按列值順序存儲的,對于IO密集型的范圍查找會比隨機從磁盤讀取每一行數據的IO要少得多。

3、一些存儲引擎如 MyISAM 在內存中只緩存索引,數據則依賴于操作系統來緩存,因此要訪問數據需要一次系統調用。

4、覆蓋索引對于支持聚簇索引的InnoDB表非常有用。InnoDB的二級索引在葉子節點中保存了行的主鍵值,所以如果二級索引能夠覆蓋查詢,則可以避免對主鍵的二次查詢。

注意,不是所有類型的索引都可以成為覆蓋索引。覆蓋索引必須要存儲索引列的值,而哈希索引、空間索引和全文索引等都不存儲索引列的值,所以MySQL只能使用B-Tree索引做覆蓋索引

當發起一個被索引覆蓋的查詢(也叫做索引覆蓋查詢)時,在EXPLAIN 的 Extra 列可以看到“Using index”的信息

有時候,很容易把Extra 列的“Using index”和type列的“index”混淆。其實兩者完全不同,type列和覆蓋索引毫無關系。它只是表示這個查詢訪問數據的方式,或者說是MySQL查詢行的方式,MySQL手冊中稱之為“連接方式(join type)”

MySQL查詢優化器會在執行查詢之前判斷是否有一個索引能進行覆蓋。

思考下面這條上SQL能否覆蓋索引:

SELECT * FROM products WHERE actor = 'SEAN CARREY' AND title LIKE '%APOLLO%';

兩個原因該條SQL無法實現覆蓋索引,第一點就是 “SELECT * ”,不可能有某個索引覆蓋所有的列值。

第二點是 LIKE,MySQL不能在索引中執行 LIKE 操作。這是底層存儲引擎API的限制。MySQL能在索引中做最左前綴匹配的LIKE比較,因為該操作可以轉換為簡單的比較操作。但如果是通配符開頭的LIKE 查詢,存儲引擎就無法做比較匹配。這種情況下,MySQL只能提取數據行的值而不是索引值來做比較

改進辦法是,先將索引擴展覆蓋至三個列:(actor, title, prod_id),然后按照如下方式重寫SQL:

SELECT * FROM products JOIN (SELECT prod_idFROM productsWHERE actor = 'SEAN CARREY' AND title LIKE '%APOLLO%' ) AS t1 ON t1.prod_id = products.prod_id;

這種查詢方式叫做——延遲關聯,因為延遲了對列的訪問。子查詢中只涉及了三個索引列,因此可以使用覆蓋索引完成查詢,外層的查詢利用覆蓋索引查詢得出的 prod_id 去完成其余列的查詢。雖然無法使用索引覆蓋整個查詢,但性能已經提升很多。

另外,由于二級索引的葉子節點也包含主鍵值,因此,有時候查詢中如果包含主鍵,即便主鍵并沒有定義在索引中,也可以完成覆蓋索引查詢。

還有一種有意思的現象就是,有時候 EXPLAIN 的 possible_key 為 NULL,但是key卻有索引,這是為什么?例如:

CREATE TABLE `teacher` (`teacher_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '教師id',...PRIMARY KEY (`teacher_id`),KEY `idx_name_salary_age` (`teacher_name`,`salary`,`age`) ) ENGINE=InnoDB

執行下面這條查詢,就可以出現這樣的現象:

EXPLAIN SELECT t.`teacher_name`,t.`age`, t.`salary` FROM teacher t WHERE t.`salary` > 4000

?這種情況就多發生在索引覆蓋查詢,possible_key 為 NULL 說明用不上索引的樹形查找。但如果二級索引包含了所有要查找的數據,就像之前說的,MySQL查詢優化器會在執行查詢之前判斷是否有一個索引能進行覆蓋。二級索引往往比聚簇索引小,所以MySQL就會選擇順序遍歷這個二級索引,然后直接返回。因此才會出現這種情況。

5.7 使用索引掃描來做排序

MySQL有兩種方式可以生成有序的結果:通過排序操作,或按照索引順序掃描。

如果EXPLAIN 的 type 列值為“index”,則說明 MySQL 使用了索引掃描來做排序。

索引掃描本身很快,但如果索引不能覆蓋查詢所需的全部列,那就不得不每掃描一條索引條目就得回表查詢一次對應行。這基本都是隨機IO,因此按照索引順序讀取數據的速度通常要比順序地全表掃描慢

MySQL?可以使用同一個索引既滿足排序,又用于查找行。因此,在設計索引時,應該盡可能同時滿足這兩種任務。

注意,只有當索引列的順序和 ORDER BY 子句的順序完全一致,并且所有列的排序方向(DESC或ASC)都一樣時,MySQL才能夠使用索引來對結果進行排序。如果查詢需要關聯多張表,則只有當 ORDER BY 子句引用的字段全部為第一張表時,才能使用索引進行排序。ORDER BY 子句和查找型查詢的限制是一樣的:需要滿足索引的最左前綴要求。否則,MySQL都需要執行排序操作,而無法利用索引排序。

有一種情況下 ORDER BY 子句可以不滿足索引的最左前綴要求,就是前導列為常量的時候。WHERE 或 JOIN 子句中對索引的前導列指定了常量,可以“彌補” ORDER BY 子句中不符合“最左前綴原則”的問題

例如,如下一張 租賃表 rental :

CREATE TABLE rental(...PRIMARY KEY (rental_id),UNIQUE KEY rental_date (rental_date, inventory_id, customer_id),KEY idx_fk_inventory_id (inventory_id),KEY idx_fk_customer_id (customer_id),KEY idx_fk_staff_id (staff_id),... )

那么如下SQL,即便ORDER BY 子句并沒有嚴格的使用“最左前綴”,但依然可以完成索引排序:

SELECT rental_id, staff_id FROM rental WHERE rental_date = '2020-05-24' ORDER BY inventory_id, customer_id;

這是因為在 WHERE 子句中,索引 rental_date 的前導列 rental_date 被指定了一個常量,因此,即使 ORDER BY 子句本身不滿足索引的最左前綴要求,但也可以用于查詢排序。JOIN 子句也是同樣的道理,它可以將常量的列進行傳遞。

下面的一些排序也是同樣可以索引排序的:

SELECT... WHERE rental_date = '2020-05-24' ORDER BY inventory_id DESC; SELECT... WHERE rental_date > '2020-05-24' ORDER BY rental_date, inventory_id;

下面是一些不能使用索引排序的例子

1、查詢使用了兩種不同的排序方向,但是索引列都是正序排序的

...WHERE rental_date = ‘2020-05-24’ ORDER BY inventory_id DESC, customer_id ASC;

2、查詢中的ORDER BY 子句引用了一個不在索引中的列

... WHERE rental_date = ‘2020-05-24’ ORDER BY inventory_id, staff_id;

3、查詢中的前導列有范圍查詢

... WHERE rental_date = ‘2020-05-24’ AND inventory_id IN(1, 2) ORDER BY customer_id ;

5.8 壓縮(前綴壓縮)索引

MyISAM使用前綴壓縮來減少索引的大小,從而讓更多的索引可以放入內存中,這在某些情況下能極大提升性能。

默認只壓縮字符串,但通過參數設置也可以對整數進行壓縮。

MyISAM壓縮每個索引塊的方法是,先完全保存索引塊中的第一個值,然后將其他值和第一個值進行比較得到相同前綴的字節數和剩余的不同后綴部分,把這部分存儲起來即可

例如,索引塊中的第一個值是“perform”,第二個值是“performance”,那么第二個值的前綴壓縮后存儲的是類似“7, ance”這樣的形式。

MyISAM對行指針也采用類似的前綴壓縮方式。

壓縮塊使用更少的空間,代價是某些操作可能更慢。

因為每個值的壓縮前綴都依賴前面的值,所以MyISAM查找時無法在索引塊使用二分查找而只能從頭開始掃描。正序的掃描速度還不錯,但是如果是倒序掃描——例如ORDER BY DESC 就不是很好。所有在塊中查找某一行的操作平均都需要掃描半個索引塊。

測試表明,對于CPU 密集型應用,因為掃描需要隨機查找,壓縮索引使得MyISAM在索引查找上要慢好幾倍,而壓縮索引的倒序掃描就更慢了。

壓縮索引需要在CPU內存資源和磁盤之間做權衡。可以在CREATE TABLE 語句中指定PACK_KEYS參數來控制索引壓縮的方式。

5.9 重復索引、冗余和未使用的索引

重復索引是指在相同列上按照相同順序創建的相同類型的索引

但如果索引類型不同,并不算重復索引,如 KEY(col) 和 FULLTEXT KEY(col) 。

MySQL允許在相同列上創建多個索引,無論是有意的還是無意的。但 MySQL 需要單獨維護重復的索引,并且優化器在優化查詢時也需要逐個進行考慮,這會影響性能。因此,應該避免這樣創建索引,發現之后應該立即移除。

有時候可能是由于不清楚各種索引的特點導致誤創建重復索引,例如下面,其實在ID 列創建了三個重復的索引,要避免這么做:

CREATE TABLE test(ID INT NOT NULL PRIMARY KEY,...UNIQUE(ID),INDEX(ID), ) ENGINE=InnoDB;

冗余索引和重復索引不同,如果創建了索引 (A, B) ,再創建索引 (A) 就是冗余索引。但是如果再創建索引 (B, A) ,則不是冗余索引,索引 (B) 也不是。另外,其他類型的索引,如 哈希、全文索引等,也不會是 B 樹索引的冗余索引。

大多數情況下,都不需要冗余索引。如果發現索引無法滿足查詢對性能的要求,那么只需要擴展對應索引,增加索引列即可。但有時候,如果擴展已有的索引會導致變化太大,從而影響其他使用該索引的查詢性能,可能就需要冗余索引,但無論如何這么做一定是會增加維護成本的。

如果決定了要刪除某個索引,一定要非常小心。二級索引的葉子節點包含主鍵值,所以剛剛的例子中,索引 (A) 相當于 (A, ID) 。如果像 WHERE A = 5 ORDER BY ID 這樣的查詢,這個索引會很有作用。但如果將索引擴展為 (A, B) ,則實際上就變成了 (A, B, ID),那么上面查詢的 ORDER BY 子句就無法使用該索引做排序了,只能使用文件排序。

未使用的索引完全是累贅,建議刪除。

有兩個工具可以幫助定位未使用的索引。

最簡單有效的辦法是在Percona Server或者MariaDB中先打開userstates服務變量(默認是關閉的),然后讓服務器正常運行一段時間,再通過查詢INFORMATION_SCHEMA.INDEX_STATISTICS就能查到每個索引的使用頻率。

另外,還可以使用Percona Toolkit 中的pt-index-usage,該工具可以讀取查詢日志,并對日志中的每條查詢進行EXPLAIN 操作,然后打印出相關索引和查詢報告。

5.10 索引和鎖

索引可以讓查詢鎖定更少的行。

如果你的查詢從不訪問那些不需要的行,那么就會鎖定更少的行,從兩個方面來看這對性能都有好處。首先,雖然InnoDB的行鎖效率很高,內存使用也很少,但是鎖定行仍然會帶來額外的開銷。其次,鎖定超過需要的行會怎增加鎖爭用,降低并發性。

InnoDB只有在訪問行的時候才會加鎖,而索引能夠減少InnoDB訪問的行數,從而減少鎖的數量。但這只有當InnoDB在存儲引擎層能夠過濾掉所有不需要的行時才有效。如果索引無法過濾掉無效的行,那么在InnoDB檢索到數據并返回給服務器層以后,MySQL服務器才能應用WHERE 子句。這時已經無法避免鎖定行了:InnoDB已經鎖住了這些行,到適當的時候才釋放。在MySQL5.1 和更新的版本中,InnoDB可以在服務器端過濾掉行后就釋放鎖,但是在舊的版本中,InnoDB只有在事務提交后才能釋放鎖。

六、索引案例

這一節分享書中介紹的一個典型索引設計案例——設計一個在線約會網站。

用戶信息表包含很多列,包括國家、地區、城市、性別、眼睛顏色等。網站必須支持上述這些特征的各種組合來搜索用戶。

第一件需要考慮的問題是需要用索引來排序,還是先檢索數據再排序?使用索引排序會嚴格限制索引和查詢的設計,換來的是排序性能的提升。

6.1 如何設計支持多種過濾條件的索引

在 “5.2 前綴索引” 一節引入了 “基數” 的概念,它描述的是數據列中不同值的多寡情況。為了設計一個可以支持多種過濾條件的索引,現在需要考慮的問題就是:哪些列擁有很多不同的值

一般來說,MySQL可以通過基數更大的列更有效地過濾掉不需要的行。

country 列的選擇性并不高,但很多查詢都會用到,sex 的選擇性肯定非常低,但也會在很多查詢中用到。所以這里就需要考慮真實場景中使用頻率的問題,所以,建議在創建不同組合索引的時候將(sex, country) 作為索引前綴。

但是為什么要在這兩個選擇性低的列上創建索引呢?

1、因為根據軟件的需要,可能絕大多數的查詢都需要用到sex列。所以使用頻率非常高,在sex列上建立索引不會有壞處。

2、即便查詢并沒有用到 sex 列,也可以通過這樣的訣竅繞開:如果查詢不限制性別,可以在查詢中增加 AND sex IN(‘m’, ‘f’)來讓MySQL選擇該索引,MySQL依然能夠匹配索引的最左前綴。

第二點,如果列有太多不同的值,就會讓IN()列表太長,這樣做就不是很好。

這個案例顯示了一個基本原則1:考慮表上所有的選項。當設計索引時,不要只為現有的查詢考慮需要哪些索引,還需要考慮對查詢進行優化。

如果發現某些查詢需要創建新的索引,但是這個索引又會降低另一些查詢的效率,那么應該想一下是否能優化原來的查詢,應該同時優化查詢和索引以找到最佳的平衡,而不是閉門造車去設計最完美的索引。

接著需要考慮的是,其他常見WHERE 條件的組合,并需要了解哪些組合在沒有合適索引的情況下會變慢。

(sex, country, age)上的索引就是一個明顯的選擇,另外很有可能還需要(sex, country, region, age) 和 (sex, country, region, city, age) 這樣的組合索引。

這樣就會需要大量的索引。如果想盡可能重用索引而不是建立大量的組合索引,可以使用前面提到的IN()的技巧來避免同時需要(sex, country, age)和(sex, country, region, age)的索引。像前面說的,如果IN()中的不同值較多,那么就需要定義一個全部國家列表,或者國家的全部地區列表,來確保索引前綴有同樣的約束(組合所有國家、地區、性別將會是一個非常大的條件)。

上述條件是比較常見的搜索條件,那如何為一些生僻的搜索條件(比如has_picture, eye_color, hair_color, education)來設計索引呢?這些列的選擇性高、使用也不頻繁,可以選擇忽略它們,讓MySQL多掃描一些額外的行即可。另一個辦法是在age前加上這些列,在不涉及這些篩選條件的時候,依然是使用 IN() 來處理查詢。

但是,為什么age 始終要放在最后?

因為,age列多半是范圍查詢。根據最左前綴原則——查詢只能使用索引的最左前綴,直到遇到第一個范圍條件列。因此,這里的基本原則2是:盡可能將需要做范圍查詢的列放到索引后面,以便優化器能使用盡可能多的索引列

關于 IN() 覆蓋那些不在WHERE 子句中的列,這種技巧也不能濫用。因為每額外增加一個 IN()條件,優化器需要做的組合都將以指數形式增加,最終可能極大的降低查詢性能。

6.2 避免多個范圍條件

什么是范圍條件?從EXPLAIN 的輸出很難區分 MySQL 是要查詢范圍值,還是查詢列表值。EXPLAIN 使用同樣的 “range” 來描述這兩種情況。例如,從 type 列來看,MySQL會把下面這種查詢當做 range 類型:

EXPLAIN SELECT actor_id FROM actor WHERE actor_id > 45;

而列表查詢的 type 也是 range:

EXPLAIN SELECT actor_id FROM actor WHERE actor_id IN(1, 4, 99) ;

EXPLAIN 無法區分兩者。但實際上,第二個查詢就是多個等值查詢,這兩種訪問效率是不同的。對于范圍條件查詢,MySQL無法再使用范圍列后面的其他索引列了,但對于“多個等值條件查詢”則沒有這個限制。

6.3 優化排序

使用文件排序對小數據集是很快的,但如果一個查詢匹配的結果有上百萬行,例如 WHERE 子句中只有 sex 列,該如何排序?

對于那些選擇性非常低的列,可以增加一些特殊的索引來做排序。例如,可以創建(sex, rating)索引用于下面的查詢:

SELECT ... FROM profiles WHERE sex = ‘M’ ORDER BY rating LIMIT 10;

這個查詢同時使用了 ORDER BY 和 LIMIT ,如果沒有索引的話會很慢。由于索引中增加了 rating (用戶等級) 這個適合排序的字段,當然也可以是其他適合排序的列,那么根據前面提到的,由于 WHERE 子句中給 sex 指定了常量,因此 ORDER BY 子句可以根據最左前綴原則來實現索引排序。

對于一些涉及到 LIMIT 子句的分頁場景,這種排序操作尤為重要,如果能使用索引來完成排序,性能將會是一個不錯的提升。

但是,即使有了索引,如果翻頁翻到比較靠后時查詢也可能非常慢。例如:

SELECT ... FROM profiles WHERE sex = ‘M’ ORDER BY rating LIMIT 100000, 10;

無論如何創建索引,這種查詢都是個嚴重的問題。因為隨著偏移量的增加,MySQL需要花費大量的時間來掃描需要丟棄的數據。

反范式化、預先計算和緩存可能是解決這類查詢的僅有策略。一個更好的辦法是限制用戶能夠翻頁的數量,實際上這對用戶體驗的影響不大,因為很少有用戶真正在乎搜索結果的第10000頁。

優化這類索引的另一個比較好的策略是使用延遲關聯(前面有提到),通過使用覆蓋索引查詢返回需要的主鍵,再根據這些主鍵關聯原表獲得需要的行。這可以減少MySQL掃描那些需要丟棄的行數。下面這個查詢顯示了如何高效地利用(sex, rating)索引進行排序和分頁:

SELECT ... FROM profiles INNER JOIN(SELECT <primary key cols> FROM profilesWHERE x.sex = ‘M’ ORDER BY rating LIMIT 100000, 10 ) AS x USING(<primary key cols>);

七、維護索引和表

有了表和合適的索引后,就需要我們具備維護它們知識,保證它們可以正常工作。

維護表的三個主要目的:

1、找到并修復損壞的表

2、維護準確的索引統計信息

3、減少碎片

7.1 找到并修復損壞的表

表損壞是最糟糕的事情。對于MyISAM存儲引擎,表損壞通常是系統奔潰導致的。

其他的存儲引擎也會由于硬件的問題、MySQL本身的缺陷或者操作系統導致損壞。損壞的索引會導致查詢返回錯誤的結果或者莫須有的主鍵沖突問題,嚴重時甚至還會導致數據庫的崩潰。

如果遇到古怪的問題,可以嘗試運行 CHECK TABLE 來檢查是否發生表損壞(注意有些存儲引擎不支持該命令。),它通常可以幫助找出大多數表和索引的錯誤。

可以使用 REPAIR TABLE 命令來修復損壞的表,但同樣不是所有存儲引擎都支持該命令。

如果存儲引擎不支持,也可以通過一個不做任何操作的ALTER操作來重建表。例如,修改表的存儲引擎為當前引擎:

ALTER TABLE innodb_tb1 ENGINE=INNODB;

如果是InnoDB引擎的表出現了損壞,那么一定是發生了嚴重的錯誤,需要立刻調查一下原因。InnoDB一般不會出現損壞。InnoDB的設計保證了它并不容易損壞。

如果發生損壞,一般要么是數據庫硬件問題例如內存或磁盤問題(有可能),要么是由于數據庫管理員的錯誤例如在MySQL外部操作了數據文件(有可能),亦或是InnoDB本身的缺陷(不太可能)。不存在什么查詢能夠讓InnoDB 表損壞,也不用擔心暗處有“陷阱”。如果某條查詢導致InnoDB數據的損壞,那一定是遇到了bug,而不是查詢的問題。

7.2?更新索引統計信息

MySQL的查詢優化器會通過兩個API 來了解存儲引擎的索引值的分布信息,以決定如何使用索引

第一個API 是records_in_range(),通過向存儲引擎傳入兩個邊界值獲取在這個范圍大概有多少條記錄。對于某些存儲引擎,該接口返回精確值,例如MyISAM,但對于另一些存儲引擎則是一個估算值,例如InnoDB。

第二個API是info(),該接口返回各種類型的數據,包括引擎的基數(每個鍵值有多少條記錄)。

如果存儲引擎向優化器提供的掃描行數信息是不準確的數據,或者執行計劃本身太復雜以致無法準確獲取各個階段匹配的行數,那么優化器會使用搜索統計信息來估算掃描行數。

?

總結

以上是生活随笔為你收集整理的MySQL高级 —— 高性能索引的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。