馬上就要到國慶節了,好是期待呀。最近一直忙成狗,急需一個長假調整一下自己的心境和狀態
今天我們要說的是索引相關的知識,這也是數據庫的一個重點章節。趕緊準備好你的筆,跟著我一起勾畫重點吧,聽說這里要考哦~~~
索引的作用
- 可以快速根據索引查找指定的記錄
- 可以根據索引對記錄進行排序,可以用來order by 和group by
- 可以將隨機IO轉變為順序IO,索引是有順序的,先根據索引順序查詢,然后根據查找到的關鍵值定位記錄
三星系統
- 索引將相關的記錄放在一起,獲得一星
- 如果索引中的數據順序和查找中的排列順序一致則獲得兩星
- 如果索引中的列包含查詢中全部列則獲得三星
需要注意的是索引并不總是最好的工具,只有當索引幫助存儲引擎快速查找到記錄帶來的好處大于其帶來的額外工作時,索引才是有效的
一般情況下:
小表:全表掃描
中表(數據量還不是很大):索引優化
大表(數據量超級大):高級技術比如分區等
索引類型
mysql中索引類型有很多,索引的實現方式是通過存儲引擎實現的,而不是服務器層實現的
B-TREE索引
一般我們沒有特別指明索引類型的時候,說的索引應該就是B-TREE索引,它使用B-TREE數據結構來存儲數據的
因為索引是由存儲引擎實現的,所以不同的存儲引擎會通過不同的方式來使用B-TREE索引,MYISAM使用前綴壓縮技術使得索引更小,同時采用物理位置引用被索引的行(也就是說,通過索引直接就可以找到對應的數據行記錄)INNODB則按照原數據格式進行存儲,同時根據主鍵引用被所以的行(也就是說通過索引首先會找到行的主鍵索引,然后通過主鍵索引找到具體的行)
B-TREE索引意味著所有存儲的數據記錄都是有順序的
根據表的數據大小,B-TREE樹層級深度也將不同,其中每一個節點頁都包含了一個值以及左邊小于該值的子節點頁指針和大于該值的右節點頁指針,也就是規定了該值的上線和下限,而葉子頁的指針指向的是具體的數據,而不是其他的節點頁
在索引中,順序是非常重要的一個因素,索引對多個值進行排序的依據就是按照create table語句中定義索引時列的順序來實現的
B-TREE索引能使用的類型
全值匹配:所有列進行匹配
匹配最左前綴:匹配索引的第一列
匹配列前綴:匹配某一列的值開頭的部分
匹配范圍值:索引第一列范圍查找
精確匹配第一列,范圍匹配另外一列
因為索引樹中的節點是有順序的,所以除了按值查找之外,還可以對數據進行order by排序操作,但是使用B-TREE索引也有一定的限制:
如果不是按照索引的最左列開始查找,將無法使用索引
不能跳過索引中的列
如果查詢中有某個列的范圍查詢,則其后面的列都將無法使用索引進行查詢
hash索引
mysql索引是在存儲引擎層實現的,并沒有統一的標準,不同的存儲引擎實現的索引方式是不同的
對于hash索引,只能精確匹配所有列的值,因為存儲引擎將會把生成hash索引的所有列的值用來構建hash code
在mysql中,只有memory引擎顯示支持hash索引,這也是它默認的索引類型,memory引擎同時也是支持非唯一hash索引的,當出現hash沖突時,通過鏈表的方式解決沖突問題
hash索引基于hash表實現的,在它其中并不保存實際的值,而是保存hashcode->行的指針的鍵值對方式
因此使用hash索引能快速的定位到某一行記錄,但是它也存在某些限制:
hash索引只包含hash值與行指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行
hash索引數據并不是按照索引值順序存儲的,也就無法使用排序
hash索引也不支持部分索引列匹配查找,因為hashcode是通過所有hash列生成出來的
hash值只支持等值比較查詢,包括=,in(),<=>(通過a <=> null,可以得出a為null的記錄) 不支持任何范圍查詢
訪問hash索引的數據非常快,除非有很多hash沖突,當出現沖突時,存儲引擎只能逐行進行查找
如果hash沖突很多時,維護起來代價也很高,應該避免在選擇性比較低的列上建立hash索引
innodb引擎有一個特殊的功能叫做“自適應hash索引”,當innodb注意到某些索引值被頻繁的引用,它會在內存中基于B-TREE索引之上再建立一個hash索引
如果某些存儲引擎不支持hash索引,我們需要創建自定義的hash索引,創建一個偽hash索引列,通過CRC32()對需要hash的列值計算hash,并在該列上創建索引
對于hash索引查找,需要在where條件語句中加上hashcode比較和列值比較,這樣是為了解決hash索引帶來的沖突
select url from t_urls where url_code = crc32(‘http://www.baidu.com’) and url = ‘http://www.baidu.com’;
這里如果發生了hash沖突,則根據url列值進行查找
上面創建偽hashcode索引列采用的是crc32算法,生成一個32位的數字,但是通常64位數字hash沖突會更少,可以自己定義一個算法:
select conv(right(md5('http://www.baidu.com'), 16), 16, 10);
如果語句中的索引列不是獨立的,那么這條語句就不能使用該列索引,也就是說索引列不能作為表達式的一部分或者不能作為函數的參數
select acter_id from actor where acter_id +1 = 5;
select ... where to_days(current_date) – to_days(date_col)<= 10
對于長度很長的列,創建索引時可以采用類似hash索引那樣的,自己建一個偽hashcode列,手動維護這個列,通過列值計算該列對應的數字值并作為hash索引
以url列舉例,如果直接使用url,則整個列字段的字符串太長,占據太多空間,我們選擇為url創建一個url_code,用來計算crc32(url)得到的數字
create table urls {
id int unsigned not null auto_increment,
url varchar(255) not null,
url_code int unsigned not null default 0
primary key(id)
}
在插入或者更新url時,通過觸發器重新計算url_code的值
delimiter //
create trigger urls_insert_trigger before insert on urls for each row begin
set new.url_code = crc32(new.url);
end;
//
create trigger urls_update_trigger before update on urls for each row begin
set new.url_code = crc32(new.url);
end;
//
delimiter;
通過偽hashcode列與該列值來精確查詢某一條記錄
select * from urls where url_code = crc32(‘http://www.baidu.com’) and url = ‘http://www.baidu.com’;
全文索引
全文索引是一種特殊類型的索引,它查找的是文本中的關鍵字,而不是直接比較索引中的值,它與其他幾種類型的索引匹配方式完全不一樣,它存在許多需要注意的細節:如停用詞、詞干、復數、布爾搜索等,更加類似于搜索引擎要干的事情
前綴索引
通過比較列選擇性和索引選擇性來決定前綴的長度,對于mysql來說,不允許對text/blob列全值進行索引,但是我們可以通過在查詢時指定使用前綴來優化此類查詢,比如排序時,避免磁盤臨時表排序
選擇性:不重復的索引值和數據表記錄總數的比值
select count(*) as count, city as city from t_city group by city order by city desc limit 10;
上面這條語句記錄了每一個城市出現的重復次數
select count(*) as count, left(city, 3) as pref from t_city group by pref order by pref desc limit 10;
還有一種選擇方式:計算列平均選擇性,并使前綴選擇性接近列選擇性
select count(distinct city) / count(*) from t_city;
select count(distinct left(city, 3)) / count(*) from t_city
前綴索引的創建方式
alter table sakila.city_demo add key city(7)
這樣就在sakila.city_demo表中創建了一個city前綴索引,索引長度為7個字符,
使用前綴索引的缺點是:前綴索引不能用來做order by 和group by操作,也無法用于作覆蓋掃描
后綴索引
還有一種是“反向索引”,針對像url這種類型的字符串列而言的,使用后綴來進行索引效果更佳,但是mysql本身并不支持后綴索引這種方式,所以我們可以通過將保存的url字符串反向存入數據庫并創建前綴索引的方式來實現所謂的后綴索引
選擇合適的索引順序
在B-TREE索引中,索引列的順序意味著索引從最左列進行排序,經驗法則告訴我們可以將選擇性高的放在前面,當不需要考慮排序和分組時,將選擇性高的索引列放在前面通常是非常好的
我們需要對多個列計算每個列對應的選擇性,然后做出決策
select count(distinct staff_id) / count(*) as staff_id_selectivity,
count(distinct custom_id) /count(*) as custom_id_selectivity, count(*) from payment \G;
根據查詢結果來看,應該將custom_id放在索引列staff_id前面
順序的索引會造成的潛在問題:
在高并發工作時,innoDB按主鍵順序插入可能會引起明顯的間隙鎖爭用
聚簇索引
聚簇索引其實是一種數據結構,保存了B-TREE索引和數據行,數據表中的數據記錄都保存在葉子頁上,但是節點頁只包含了索引列
聚簇表示數據行與相鄰的鍵值緊湊的存儲在一起,
在innoDB數據庫中,通過主鍵索引列來聚簇數據記錄,也就是說,在innoDB聚簇索引中,節點頁上保存的是行主鍵,如果沒有主鍵列,innoDB會選擇一個非空索引代替,如果也沒有這樣的索引,innoDB會創建一個隱式的主鍵來進行聚簇
在innodb中,沒有被用來做聚簇的索引,被稱為是二級索引,在索引中保存的并不是物理行的位置,而是行記錄的主鍵,需要根據二級索引找到行主鍵之后再到聚簇B-TREE中查找指定的行記錄
myisam引擎主鍵與其他索引實現相同,主鍵只是一個名稱為PRIMARY的非空索引。
myisam存儲數據就是按照數據的插入順序保存的,表存儲結構的葉子節點上保存了當前索引列值和物理行所在的位置
innodb通過B-TREE結構保存數據表行的所有列記錄,二級索引通過保存主鍵值,在根據主鍵值在B-TREE結構中查找物理行數據信息
聚集的數據有哪些優點
- 可以把相關的數據保存在一起,這樣在查找記錄時可以從磁盤上讀取少量的頁就能查到結果
- 訪問數據更快,聚簇索引將索引和數據都保存在同一個B-TREE中,因此從聚簇索引獲取數據比非聚簇索引獲取數據要快
- 使用覆蓋索引掃描的查詢,可以直接使用頁節點的主鍵值,無需再根據主鍵查找數據
聚簇索引的缺點
聚簇索引最大限度的提高了I/O密集型應用的性能,但如果數據全部都放在內存中,那么訪問的順序就沒那么重要了 - 插入速度嚴重依賴于插入順序,按照主鍵的順序插入是加載數據到INNODB表中速度最快的方式
- 更新聚簇索引列的代價很高,因為會強制每一個被更新的行移動到新的位置
4. 基于聚簇索引的表在插入新行,或者主鍵被更新導致需要移動行的時候,可能面臨頁分裂的問題 - 聚簇索引可能導致全表掃描變慢,尤其是行比較稀疏的時候,或者頁分裂導致數據存儲不連續的時候
- 二級索引可能比想象的大,因為二級索引的葉子節點包含了引用行的主鍵列
- 二級索引訪問需要兩次索引查找,找主鍵、找數據
延遲查詢
對于某些查詢,可以通過延遲查詢來優化
explain select * from products where actor = ‘sean carrey’ and title like ‘%apollo%’\G
其中actor 與title 列建立了索引
這里無法對查詢進行索引覆蓋,因為查詢的列為全部列,不存在任何一個索引可以覆蓋所有列
改為延遲加載,添加索引覆蓋列(actor, title, prod_id)
explain select * from products inner join (
select prod_id from products where actor = ‘sean carrey’ and title like ‘%apollo%’)as t1 on (t1.prod_id = products.prod_id)
上面子查詢采用索引覆蓋,過濾prod_id,然后根據prod_id再到記錄中查找
覆蓋索引
如果一個索引包含所有需要查詢的字段的值,那么我們就稱之為覆蓋索引
覆蓋索引的好處
- 索引條目通常遠小于數據行大小,所以如果只需要讀取索引,那mysql就會極大的減少數據訪問量
- 因為索引是按照列值順序存儲的,所以順序查詢會比隨機從磁盤讀取數據的I/O要少的多
- 一些存儲引擎如MYISAM在內存中只緩存索引,數據則依賴于具體OS來緩存,因此訪問數據意味著還需要一次系統調用,采用覆蓋索引則減少了這樣的系統調用
- 針對INNODB的聚簇索引,覆蓋索引可以杜絕二級索引根據主鍵值查找數據行記錄
覆蓋索引必須要存儲索引列的值,而hash索引、空間索引、全文索引都不存儲索引列的值,所以mysql只能使用b-tree索引做覆蓋索引
當發起一個索引覆蓋查詢時,通過explain分析語句會看到extra Using index,這里的extra表示的是檢索數據的方式,需要與type進行區分,type index表示在對結果集進行排序時使用到了索引
如果查詢的列沒有被索引覆蓋,也就是無法使用索引覆蓋查詢時,explain查詢分析出來extra Using where
對于下面這條語句:
explain select * from products where actor=’seny carrey’ and title like ‘%apollo%’\G
存在兩個問題導致它無法使用覆蓋索引:
- 沒有任何一個索引能夠覆蓋這個查詢,因為從表中選擇了所有列,而沒有任何索引覆蓋了所有列
- mysql不能在索引中執行like操作,只是允許使用左前綴匹配的方式和一些簡單的值比較,上面的查詢語句可以通過延遲關聯來解決:
select * from product inner join(
select prod_id from product where actor=’seny carrey’ and title like ‘apollo%’
) as t1 on t1.prod_id = product.prod_id\G
使用索引掃描做排序
排序有兩種方式:直接通過排序、按索引順序掃描,如果explain出來的結果中的type為index,則表示使用到了索引掃描來做排序
orderby子句的列順序必須與索引列定義的順序完全一致(也就是說按照多個列進行排序,要么都升序,要么都降序),因為mysql是按照索引順序來組織記錄順序的,而order by 如果打破了這種規則那么就必須使用文件排序
如果查詢關聯多張表,則只有當order by子句引用的字段全部為第一個表,才能使用索引做排序
還有一種情況就是如果索引前導列(where語句或者join子句中包含的索引第一列)設置為常量時,就可以使用索引進行排序,比如:
(rental_date,inventory_id,customer_id)為一個組合索引,則語句
select rental_id,staff_id from sakila.rental where rental_date=’2005-05-25’ order by inventory_id,customer_id
可以使用索引進行排序,雖然order by 子句不滿足索引的最左前綴要求,也可以用于查詢排序,因為索引第一列被設置成為了常量
下面列出不能使用索引做排序的查詢
- 使用兩種不同的排序方向,但是索引列都是正序排列
where rental_date=2005-05-25’ order by inventory_id desc,customer_id asc; - 引用不存在與索引中的列
where rental_date=2005-05-25’ order by inventory_id,staff_id - where與order by中的列無法組合成索引的最左前綴
where rental_date=’2005-05-25’ order by customer_id - 查詢在索引列的第一列為范圍查詢條件,所以mysql無法使用其他的索引列
where rental_date > ‘2005-05-25’ order by inventory_id,customer_id - 索引列上存在多個等值條件,對于查詢來說其實就相當于范圍查詢
where rental_date = ‘2005-05-25’ and inventory_id in(1,2) order by customer_id
壓縮(前綴壓縮)索引
myisam使用前綴壓縮索引減少索引的大小,從而讓更多的索引能放入內存,默認只壓縮字符串,但是也可以配置壓縮整數
myisam壓縮每個索引塊的方法是,先完全保存索引塊的第一個值,然后將其他值和第一個值進行比較得到相同的前綴的字節數和不同的后綴,把這部分存儲起來即可,比如:索引塊中第一個值為perform,第二個值為performance,那么第二個值的前綴壓縮后存儲的是7,ance這樣的形式
前綴索引無法通過二分查找只能從頭開始掃描,正序的掃描速度還不錯,但反序就不是很好了
冗余索引和重復索引
重復索引,具有相同類型、按照相同順序的索引,應該避免,發現后立即刪除
冗余索引,(A,B)為索引,再創建索引(A)就是冗余索引,因為A索引只是AB索引的前綴索引,因此索引(AB)也可以當做(A)來算
默認情況下在創建innodb二級索引時,主鍵索引已經默認添加到該索引上了,例如(A, ID)其中id為主鍵索引
冗余索引必須是相同的類型,其他類型的索引,比如hash索引或者全文索引頁不會是B-TREE索引的冗余索引
索引和鎖
索引可以讓查詢鎖定更少的行,innodb只有在訪問行的時候才會對其加鎖,而索引能夠減少innodb訪問的行數,從而減少鎖的數量,但這只有在存儲引擎層過濾掉所有不需要的行時才有效
支持多種過濾條件
在有更多不同值的列上創建索引的選擇性會更好,在檢索時,我們可以將查詢用的多的列加入到索引中,對于索引前綴列不需要進行條件過濾時,通過in指定列值,IN的方式對查詢檢索是有效的,但是對order by則是無效的,比如存在(sex,country)這樣的索引,當我們需要使用到該索引時,但又不需要對性別做出限制,那么我們可以通過and sex in (‘m’,’f’)的方式讓mysql選擇該列索引
避免多個范圍條件
針對這兩種查詢語句:
select actor_id from actor where actor_id > 45;
select actor_id from actor where actor_id in (1,4,49);
這兩種查詢語句的執行效率是不同的,對于范圍查詢,mysql是無法使用范圍列后面的其他索引列了,但是對于多個等值條件查詢,則沒有這個限制
維護索引和表
找到并修復索引表
通過check table來檢查是否發生了表損壞,并通過repair table來修復表;但是如果存儲引擎不支持該命令,也可以通過alter table 重建表來達到修復目的
alter table innodb_tbl ENGINE=INNODB
更新索引統計信息
查詢優化器通過兩個API來了解存儲引擎的索引值分布,通過這兩個API的結果來決定使用哪個索引進行查詢優化
records_in_range();傳入兩個邊界值計算之間的記錄數
info();返回各種類型的數據包括索引基數(通過show index from table)
如果統計信息不準確,那么定會影響到查詢優化器的優化策略,通過analyze table重新生成統計信息
數據碎片類型
行碎片:數據行被存儲在多個地方的多個片段中
行間碎片:邏輯上順序的頁,在磁盤上不是順序的
剩余空間碎片:數據頁中大量的空余空間
通過optimize table 或者導出再導入的方式來重新整理數據,對于不支持該命令的存儲引擎,可以通過alter table tablename engine=<engine>
來進行優化
每種存儲引擎實現索引統計信息的方式不同,所以需要進行analyze table的頻率也不同:
- memory引擎根本不存儲索引統計信息
- myisam引擎將索引統計信息存儲在磁盤中,analyze table需要進行一次全索引掃描來計算索引基數
- 直到mysql5.5,innodb也不在磁盤存儲索引統計信息,而是通過隨機的索引訪問進行評估,并將估算結果存在內存中
mysql執行狀態
通過show full processlist來查看mysql當前處在哪一個狀態
sleep 線程正等待客戶端發起查詢請求
locked 在mysql服務層里,該線程正在等待表鎖
Analyzing and statistics 線程正在搜集存儲引擎的統計信息,并生成查詢執行計劃
query 線程正在查詢
Copying to tmp table [on disk],線程正在執行查詢,并將結果復制到一個臨時表中,這種狀態要么是在group by操作,要么是在文件排序操作,如果這個狀態后面還有on disk ,則表示mysql正在把一個內存臨時表放到磁盤
sorting result 線程正在進行排序
Sending data 這個狀態有多重可能,有可能是線程之間在進行數據傳輸,或者正在生成結果集,或者向客戶端返回數據