《高性能Mysql》-第五章-創建高性能的索引

1.b-樹索引

索引首先要回顧一下b樹b+樹的特點和區別,數據庫引擎用b+樹的好處有查詢時間比較穩定,b+樹比較適合范圍查詢,b+樹比較矮胖,b樹的高度代表隨機io的次數,相對的查詢時間會比較短。

如圖是索引的一般設計

mysql索引

非葉子也擁有指向葉子頁的指針,葉子頁之間也存在指針。

文中說道:邏輯頁依賴于不同的存儲引擎,對于InnoDB為16k。對此“頁”的概念做一些資料查找。

頁是innodb存儲引擎的最小存儲單位,有數據頁,undo頁,系統頁,事務處理頁。默認的頁是16kb,每個頁上至少有2條以上的記錄。

b-樹適合的查詢類型

  • 全鍵值
  • 鍵值范圍
  • 鍵值前綴(最左前綴)

其中具體包含如下(索引為lastname,firstname,birthday)

  • 全部鍵值 查找lastname=Allen,firstname=Cuba,birthday='1960-01-01'
  • 匹配最左前綴 查找lastname=Allen
  • 匹配列前綴 查找lastname like"A%"
  • 匹配范圍值 查找lastname>'A' and lastname<'D'
  • 精確匹配某一列并范圍匹配另一列 lastname='Allen' and firstname like "f%"
  • 覆蓋索引的查詢

orderBy是否能用到索引的條件和查詢是否能用到索引的條件一致

不能使用索引的限制

  • 不是從最左開始 查找 firstname='Cuba'
  • 不能跳過索引中的列 查找 lastname='Allen' and birthday='2019-01-01'
  • 查詢中某一列用了范圍查詢,右邊的所有列無法用索引再優化 查找 lastname='A' and firstname like "A%" and birthday = '2019-01-01'

2.哈希索引

哈希索引基于哈希表實現,只有精確匹配索引所有列才有效。對于每一行數據,對所有索引列計算哈希碼,哈希索引將哈希碼存儲在索引中,同時在哈希表中保存指向每個數據行的指針。

Memory引擎顯示支持哈希索引,同時也支持B-tree索引。而且Memory引擎支持非唯一哈希索引,索引會以鏈表的方式存在同一條哈希條目中。

哈希索引速度非常快,但是限制如下:

  • 哈希索引只包含哈希值和行指針,不能在索引中直接查到數據行(對速度影響較小)。
  • 哈希索引不是按照索引值順序排序的,無法用于排序。
  • 不支持單個索引列查找,只有部分列是無法使用索引的
  • 只支持等值查詢,不支持范圍查詢
  • 哈希沖突時查詢速度會變慢

innodb有個特殊功能叫"自適應哈希索引(adaptive hash index)",當Innodb注意到某些索引值使用非常頻繁的時候,會在內存基于B-tree索引之上創建一個哈希索引,讓B-Tree索引具有哈希索引的優點,這個功能是完全自動的,內部行為,用戶無法控制或者配置。

這里有個擴展知識就是在平時使用Mysql時我們對一個字段添加了hash索引的話,重新show INDEXES from tableA 會發現的index_type還是BTREE,這個就是上面所說的自適應哈希,其實還是在BTREE的基礎上做了哈希索引。

在InnoDB中如何優化如下查詢
select id from url where url = "http://www.mysql.com";
可以在該數據庫手動建立一個哈希索引列,基于這一列來查詢會更快
select id from url where url = "http://www.mysql.com" and url_crc=CRC32("http://www.mysql.com")

這種方案麻煩的地方在于需要手動維護url_crc

全文索引

更類似于搜索引擎做的事情,不是簡單的where匹配,全文索引適用于match against操作。

索引的優點

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

如何才能使用到索引

1.獨立的列

索引列不能是表達式的一部分,也不能是函數的參數

select * from where To_DAYS(current_date) < 10

2.前綴索引和索引選擇性

索引選擇性就是說用了這個索引可以篩選多少行,可以過濾多少行,不重復的索引值越多,選擇性越大。

文中的例子是對city這個地段做索引,可以用這個公式來測算索引選擇性

select count(distinct city)/count(*) from city_demo

計算前綴索引長度的一個建議就是計算完整列的選擇性,使得前綴的選擇接近于完整列。

select count(distinct left(city,3))/count(*) as sel3,
count(distinct left(city,4))/count(*) as sel4 from city_demo

當索引字節增加,索引選擇性沒有太大變化時,即認為當前長度足夠了。

alter table city_demo add key (city(7));

3.多列索引

4.B—tree索引選擇合適的索引列順序

確定索引列的順序的其中一個原則是將選擇性最高的列放在最前面。

以下面查詢為例

select * from payment where staff_id = 2 and customer_id = 584;

是應該創建(staff_id,customer_id)的索引還是顛倒下順序。
實際上如果staff_id和customer_id的組合數接近總行數,那就說明數據沒什么區分度

select count(distinct staff_id,customer_id)/count(*) 比值非常小,說明staff_id,customer_id的組合重復非常多的時候

那么這個聯合索引就沒啥用,反而如果customer_id的區分度很高,只用customer_id做索引會是查詢效率提高很多

聚簇索引

聚簇索引是一種數據存儲方式,當表有聚簇索引時,數據行實際存放在索引的葉子頁。屬于聚簇表示數據行和相鄰的鍵值緊湊地存儲在一起。因為無法把數據行放在兩個不同的地方,所以一個表只能有一個聚簇索引。

聚簇索引實際上是b-tree+數據行

image1

Mysql的內建存儲引擎都不支持服務器選擇哪個索引作為聚簇索引,InnoDB通過主鍵聚集數據,即上圖中被索引的列是主鍵列。

聚簇索引的優點:

  • 可以把相關數據保存在一起,例如根據用戶id來查找用戶的郵件,如果沒有使用聚簇索引,可能每封郵件都需要一次IO。(讀到這里我是有疑問的,前面說innodb聚簇索引是主鍵,那主鍵做索引查郵件只可能是一次IO,一個用戶id就一行數據。這里我認為應該是我的誤解,這個說法是針對所有聚簇索引的,而不是單指innodb,而innodb是基于主鍵的)
  • 數據訪問更快,索引中直接包含數據,比普通索引更快
  • 使用覆蓋索引掃描的查詢可以直接使用頁節點的值

聚簇索引的缺點

  • 聚簇索引是最大限度提高了IO密集型應用的性能,但是如果數據都在內存里,訪問順序就不重要了,聚簇索引也沒什么優勢。
  • 插入速度依賴于插入順序,innodb是主鍵做聚簇索引,索引按照主鍵順序插入是最快的
  • 更新聚簇索引列代價比較高。 比如Innodb中的主鍵
  • 基于聚簇索引插入行,會面臨"頁分裂"問題,當主鍵插入一個已經滿了的頁中,存儲引擎會將該頁分為兩個頁面,會造成占用更多的磁盤空間。
  • 聚簇索引導致全表掃描變慢,由于頁存儲不連續的時候。
  • 非聚簇索引會比較大,因為二級索引包含引用行的主鍵列
  • 二級索引訪問需要兩次索引查找(innodb一次b-tree,找到主鍵后查找聚簇索引的b-tree,對于innodb,自適應哈希能夠減少這種重復工作)

下面看下MyISAM和Innodb對于索引實現的區別,創建如下的數據表,插入10000行數據

create table layout_test(
    col1 int not null,
    clo2 int not null,
    PRIMARY KEY(col1),
    KEY(col2)
)

對于MyISAM引擎,如下:

數據列如下


image1

主鍵索引如下


image2

col2索引如下


image3

可以看到主鍵索引和普通索引沒有什么區別,葉子頁都存放的是行號,用來索引到具體的數據行。

對于Innodb引擎,聚簇索引如下:


image4

可以看到innodb主鍵索引的葉子節點存放的就是行數據。每個葉子節點都包含了主鍵值,事務ID,用于事務和MVCC的回滾的指針,以及剩余的列。

還有一個MyISAM和Innodb的二級索引和聚簇索引很不相同。Innodb的二級索引的葉子節點不是行指針,而是主鍵值,并用主鍵值當做行指針,這樣就減少了當前行移動或者數據頁分裂時二級索引的維護工作。innodb在移動行時不需要更新二級索引上的指針。

二級索引列的數據如下:

image5

下圖可以清楚看出MyISAM和Innodb的區別

image6

一個問題:在InnoDB表中按主鍵順序插入行的兩種方案,一種主鍵是遞增的id,一種主鍵是uuid,哪種插入速度會比較快?

兩者的區別如下
順序:


image7

uuid:


image7
  • 寫入的目標頁可能已經刷到磁盤并從緩存刪除,Innodb在插入前不得不找到并且從目標頁存到內存中,這會導致大量隨機IO。
  • 插入是亂序的,Innodb會不得不頻繁地做頁分裂操作,為新的行分配空間,一次插入最少修改三個頁而不是一個頁。
  • 頻繁的頁分離會造成頁變得稀疏,最終會有碎片。

所以當隨機值加入后,需要做一次OPTIMIZE TABLE來重新建表并優化頁的填充

覆蓋索引

如果索引的葉子節點就包含所有要查詢的數據,就稱改索引為"覆蓋索引"。例如對表中某兩個字段(A,B)做了聯合索引,則如果一個Sql語句是查詢A,B兩個字段,那就可以用到覆蓋索引。

覆蓋索引的好處:

  • 索引條目遠小于數據行大小,用覆蓋索引會極大減小數據訪問量。
  • 索引是順序存儲的,查詢的IO會比從磁盤讀小很多
  • MyISAM這種數據內存中只緩存索引,如果調用數據需要系統調用,會有很大的損耗,直接查索引可以減小很多開銷。
  • InnoDB是聚簇索引,覆蓋索引可以避免對主鍵索引的二次查詢。

覆蓋索引必須要存索引列的值,這個在MySQL只能用B-Tree,哈希全文空間都不行。

在使用覆蓋索引時,explain時會看到extra有"Using index"

image6

如果想查一個表的所有數據,但是又想用到覆蓋索引,可以用延遲關聯的方法

image6

innodb所有二級索引都包含主鍵,所以本來用到覆蓋索引的再增加主鍵值,依然可以用到覆蓋索引。

使用索引做排序

在rental表添加一個包含三個列的索引(rental_date,inventory_id,customer_id)

如下語句可以用到索引

select * from rental where rental_date='2019-01-01' order by inventory_id,customer_id 

下面這個也可以,滿足了最左前綴的要求

select * from rental where rental_date='2019-01-01' order by inventory_id desc

下面這個也可以,因為orderBy使用的也是最左前綴

select * from rental where rental_date>'2019-01-01' order by rental_date,inventory_id desc

下面這個不能用,因為orderBy的排序方向不同,索引列都是正向排序的

select * from rental where rental_date>'2019-01-01' order by inventory_id desc,customer_id ASC

這個也不行,用到了一個非索引的列

select * from rental where rental_date>'2019-01-01' order by inventory_id ,staff_id

這個也不行,無法組成最左前綴

select * from rental where rental_date>'2019-01-01' order by customer_id

這個也不行,第一列是范圍查詢

select * from rental where rental_date>'2019-01-01' order by inventory_id desc,customer_id

這個也不行,也是一種范圍查詢

select * from rental where rental_date='2019-01-01' and inventory_id in (1,2) order by inventory_id desc,customer_id

壓縮索引

MyIsam用前綴壓縮減小索引大小,從而讓更多索引放入內存

冗余和重復索引

Mysql可以對相同列創建多個索引,主鍵、唯一鍵也是通過索引實現的。

例如(A,B)和A這種索引就是冗余索引,因為對于(A,B)只查A也走索引

另外一種情況是(A,ID)也是冗余的,因為InnoDB主鍵列在二級索引中。

索引和鎖

InnoDB只有在訪問行的時候會對其加鎖,但是這個只有在InnoDB在存儲引擎層能過濾掉所有不需要的行時才有效。在InnoDB檢索到數據行返回給服務層,MySQL服務器才能應用Where子句。這時候很多行已經被鎖住,直到服務器過濾掉行之后才釋放。

set autocommit=0;
begin;
select actor_id from actor where actor_id<5 and actor_id <>1 for update

只返回3行記錄,但是實際上會鎖住1-4這四行。通過explain可以看出Extra有"UsingWhere",說明MysSQL是InnoDB將行返回再應用where條件過濾。

set autocommit=0;
begin;
select actor_id from actor where actor_id=1 for update

這個程序會掛起,直到第一個事務釋放了鎖。

如果說查詢根本沒用到索引,情況會更糟糕,Innodb會全表掃描并鎖住所有的行,這個在生產環境是很危險的。所以寫for update語句一定要看看有沒有用到索引。

一個很少人知道的細節:InnoDB在二級索引上使用讀鎖,在訪問主鍵索引需要排他鎖,這消除了使用覆蓋索引的可能性,并且使得select for update 比 lock in share mode 慢很多

總結

Mysql大多數情況下都會使用B-Tree索引,其他索引都只是適用于特殊目的。

選擇索引和編寫索引的查詢時,有以下三條原則:

  • 單行訪問是很慢的,如果訪問數據頁的某一塊只為獲得某一行,那就浪費了很多IO的工作。
  • 順序訪問數據很快,1不需要多次磁盤尋道,所以比隨機IO快,尤其機械硬盤2其次是無需額外的排序操作。
  • 索引覆蓋查詢非常快,避免了單行訪問,減少了IO的次數。

祝大家編碼愉快,工作愉快,歡迎關注我的公眾號,一起分享交流


Java技術小棧
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容