数据库索引的实现原理及查询优化
MySQL官方對(duì)索引的定義為:索引(Index)是幫助MySQL高效獲取數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。
使用索引的目的在于提高查詢(xún)效率,這篇文章梳理一下索引的實(shí)現(xiàn)原理和應(yīng)用。
?
不同的存儲(chǔ)引擎索引實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)不同
MySQL支持諸多存儲(chǔ)引擎,而各種存儲(chǔ)引擎對(duì)索引的支持也各不相同,因此MySQL數(shù)據(jù)庫(kù)支持多種索引類(lèi)型,如B-Tree索引,哈希索引,全文索引等,
主要存儲(chǔ)引擎有MyISAM、InnoDB、MEMORY和MERGE等,在創(chuàng)建表到時(shí)候通過(guò)engine=或type=來(lái)指定所要使用到引擎,
來(lái)查看指定表的引擎。
MyISAM不支持事務(wù),也不支持外鍵,訪(fǎng)問(wèn)速度快,對(duì)事務(wù)完整性沒(méi)有要求或者以SELECT、INSERT為主的應(yīng)用可以使用這個(gè)引擎來(lái)創(chuàng)建表,memory數(shù)據(jù)表使用散列索引,
InnoDB存儲(chǔ)引擎則提供了具有提交、回滾和崩潰恢復(fù)能力的事務(wù)安全。但是對(duì)比MyISAM的存儲(chǔ)引擎,InnoDB寫(xiě)的處理效率差一些并且會(huì)占用更多的磁盤(pán)空間以保留數(shù)據(jù)和索引。
這里只關(guān)注應(yīng)用最廣泛的InnoDB的B-Tree索引。
?
索引實(shí)現(xiàn)的B-Tree結(jié)構(gòu)
數(shù)據(jù)庫(kù)索引是通過(guò)B-Tree實(shí)現(xiàn)的,但是為什么使用B-Tree而不是使用紅黑樹(shù)或者其他的查找數(shù)據(jù)結(jié)構(gòu)呢,
通過(guò)對(duì)樹(shù)結(jié)構(gòu)的回顧分析一下。
紅黑樹(shù)(Red-Black Tree)是二叉搜索樹(shù)(Binary Search Tree)的一種改進(jìn)。我們知道二叉搜索樹(shù)在最壞的情況下可能會(huì)變成一個(gè)鏈表(當(dāng)所有節(jié)點(diǎn)按從小到大的順序依次插入后)。而紅黑樹(shù)在每一次插入或刪除節(jié)點(diǎn)之后都會(huì)花O(log N)的時(shí)間來(lái)對(duì)樹(shù)的結(jié)構(gòu)作修改,以保持樹(shù)的平衡。也就是說(shuō),紅黑樹(shù)的查找方法與二叉搜索樹(shù)完全一樣;插入和刪除節(jié)點(diǎn)的的方法前半部分節(jié)與二叉搜索樹(shù)完全一樣,而后半部分添加了一些修改樹(shù)的結(jié)構(gòu)的操作。
紅黑樹(shù)的每個(gè)節(jié)點(diǎn)上的屬性除了有一個(gè)key、3個(gè)指針:parent、lchild、rchild以外,還多了一個(gè)屬性:color。它只能是兩種顏色:紅或黑。而紅黑樹(shù)除了具有二叉搜索樹(shù)的所有性質(zhì)之外,還具有以下4點(diǎn)性質(zhì):
1. 根節(jié)點(diǎn)是黑色的。
2. 空節(jié)點(diǎn)是黑色的(紅黑樹(shù)中,根節(jié)點(diǎn)的parent以及所有葉節(jié)點(diǎn)lchild、rchild都不指向NULL,而是指向一個(gè)定義好的空節(jié)點(diǎn))。
3. 紅色節(jié)點(diǎn)的父、左子、右子節(jié)點(diǎn)都是黑色。
4. 在任何一棵子樹(shù)中,每一條從根節(jié)點(diǎn)向下走到空節(jié)點(diǎn)的路徑上包含的黑色節(jié)點(diǎn)數(shù)量都相同。
B-Tree又叫平衡多路查找樹(shù)。B-樹(shù)是為了磁盤(pán)或其它存儲(chǔ)設(shè)備而設(shè)計(jì)的一種多叉平衡查找樹(shù),相對(duì)于二叉,B樹(shù)每個(gè)內(nèi)結(jié)點(diǎn)有多個(gè)分支,即多叉。與紅黑樹(shù)很相似,但在降低磁盤(pán)I/0操作方面要更好一些。
為什么使用B-Tree而不是使用紅黑樹(shù)或者其他的查找數(shù)據(jù)結(jié)構(gòu),紅黑樹(shù)多用在內(nèi)部排序,即全部在內(nèi)存中的,C++的STL中map和set的內(nèi)部實(shí)現(xiàn)就是紅黑樹(shù),Java集合框架里HashSet和HashTree也是使用紅黑樹(shù)實(shí)現(xiàn),
B樹(shù)多用在內(nèi)存里放不下,大部分?jǐn)?shù)據(jù)存儲(chǔ)在外存上時(shí)。因?yàn)锽樹(shù)層數(shù)少,因此可以確保每次操作,讀取磁盤(pán)的次數(shù)盡可能的少。
?
索引創(chuàng)建的幾個(gè)原則
(1)適合索引的列是出現(xiàn)在WHERE 子句中的列
最適合索引的列是出現(xiàn)在WHERE 子句中的列,或連接子句中指定的列,而不是出現(xiàn)在SELECT 關(guān)鍵字后的選擇列表中的列。
(2)使用惟一索引
考慮某列中值的分布。對(duì)于惟一值的列,索引的效果最好,而具有多個(gè)重復(fù)值的列,其索引效果最差。例如,存放年齡的列具有不同值,很容易區(qū)分 各行。而用來(lái)記錄性別的列,只含有“ M”和“F”,則對(duì)此列進(jìn)行索引沒(méi)有多大用處。
(3)使用短索引
如果對(duì)串列進(jìn)行索引,應(yīng)該指定一個(gè)前綴長(zhǎng)度,只要有可能就應(yīng)該這樣做。例如,如果有一個(gè)CHAR(200) 列,如果在前10 個(gè)或20 個(gè)字符內(nèi),多數(shù)值是惟一的,那么就不要對(duì)整個(gè)列進(jìn)行索引。對(duì)前10 個(gè)或20 個(gè)字符進(jìn)行索引能夠節(jié)省大量索引空間,也可能會(huì)使查詢(xún)更快。較小的索引涉及的磁盤(pán)I/O 較少,較短的值比較起來(lái)更快。更為重要的是,對(duì)于較短的鍵值,索引高速緩存中的塊能容納更多的鍵值,因此,MySQL也可以在內(nèi)存中容納更多的值。這增加 了找到行而不用讀取索引中較多塊的可能性。(當(dāng)然,應(yīng)該利用一些常識(shí)。如僅用列值的第一個(gè)字符進(jìn)行索引是不可能有多大好處的,因?yàn)檫@個(gè)索引中不會(huì)有許多不 同的值。)
(4)利用最左前綴
在創(chuàng)建一個(gè)n 列的索引時(shí),實(shí)際是創(chuàng)建了MySQL可利用的n 個(gè)索引。多列索引可起幾個(gè)索引的作用,因?yàn)榭衫盟饕凶钭筮叺牧屑瘉?lái)匹配行。這樣的列集稱(chēng)為最左前綴。(這與索引一個(gè)列的前綴不同,索引一個(gè)列的前綴是利用該的前n 個(gè)字符作為索引值。)
(5)不要過(guò)度索引
不要以為索引“越多越好”,什么東西都用索引是錯(cuò)的。每個(gè)額外的索引都要占用額外的磁盤(pán)空間,并降低寫(xiě)操作的性能,這一點(diǎn)我們前面已經(jīng)介紹 過(guò)。在修改表的內(nèi)容時(shí),索引必須進(jìn)行更新,有時(shí)可能需要重構(gòu),因此,索引越多,所花的時(shí)間越長(zhǎng)。如果有一個(gè)索引很少利用或從不使用,那么會(huì)不必要地減緩表 的修改速度。此外,MySQL在生成一個(gè)執(zhí)行計(jì)劃時(shí),要考慮各個(gè)索引,這也要費(fèi)時(shí)間。創(chuàng)建多余的索引給查詢(xún)優(yōu)化帶來(lái)了更多的工作。索引太多,也可能會(huì)使 MySQL選擇不到所要使用的最好索引。只保持所需的索引有利于查詢(xún)優(yōu)化。如果想給已索引的表增加索引,應(yīng)該考慮所要增加的索引是否是現(xiàn)有多列索引的最左 索引。如果是,則就不要費(fèi)力去增加這個(gè)索引了,因?yàn)橐呀?jīng)有了。
(6)考慮在列上進(jìn)行的比較類(lèi)型。
索引可用于“ <”、“ < = ”、“ = ”、“ > =”、“ > ”和BETWEEN 運(yùn)算。在模式具有一個(gè)直接量前綴時(shí),索引也用于LIKE 運(yùn)算。如果只將某個(gè)列用于其他類(lèi)型的運(yùn)算時(shí)(如STRCMP( )),對(duì)其進(jìn)行索引沒(méi)有價(jià)值。
索引并不是越多越好,索引固然可以提高相應(yīng)的 select 的效率,但同時(shí)也降低了 insert 及 update 的效率,因?yàn)?insert 或 update 時(shí)有可能會(huì)重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個(gè)表的索引數(shù)最好不要超過(guò)6個(gè),若太多則應(yīng)考慮一些不常使用到的列上建的索引是否有必要。
?
?
創(chuàng)建索引的時(shí)機(jī)
(1)什么時(shí)候創(chuàng)建索引
較頻繁地作為查詢(xún)條件的字段,也就是說(shuō)最適合索引的列是出現(xiàn)在WHERE 子句中的列,或連接子句中指定的列,而不是出現(xiàn)在SELECT 關(guān)鍵字后的選擇列表中的列。
(2)什么時(shí)候不創(chuàng)建索引
表記錄太少:
如果一個(gè)表只有5條記錄,采用索引去訪(fǎng)問(wèn)記錄的話(huà),那首先需訪(fǎng)問(wèn)索引表,再通過(guò)索引表訪(fǎng)問(wèn)數(shù)據(jù)表,一般索引表與數(shù)據(jù)表不在同一個(gè)數(shù)據(jù)塊,這種情況下ORACLE至少要往返讀取數(shù)據(jù)塊兩次。而不用索引的情況下ORACLE會(huì)將所有的數(shù)據(jù)一次讀出,處理速度顯然會(huì)比用索引快。
唯一性太差的字段:如狀態(tài)字段、類(lèi)型字段。那些只存儲(chǔ)固定幾個(gè)值的字段,例如用戶(hù)登錄狀態(tài)、消息的status等。
這個(gè)涉及到了索引掃描的特性。例如:通過(guò)索引查找鍵值為A和B的某些數(shù)據(jù),通過(guò)A找到某條相符合的數(shù)據(jù),這條數(shù)據(jù)在X頁(yè)上面,然后繼續(xù)掃描,又發(fā)現(xiàn)符合A的數(shù)據(jù)出現(xiàn)在了Y頁(yè)上面,那么存儲(chǔ)引擎就會(huì)丟棄X頁(yè)面的數(shù)據(jù),然后存儲(chǔ)Y頁(yè)面上的數(shù)據(jù),一直到查找完所有對(duì)應(yīng)A的數(shù)據(jù),然后查找B字段,發(fā)現(xiàn)X頁(yè)面上面又有對(duì)應(yīng)B字段的數(shù)據(jù),那么他就會(huì)再次掃描X頁(yè)面,等于X頁(yè)面就會(huì)被掃描2次甚至多次。以此類(lèi)推,所以同一個(gè)數(shù)據(jù)頁(yè)可能會(huì)被多次重復(fù)的讀取,丟棄,在讀取,這無(wú)疑給存儲(chǔ)引擎極大地增加了IO的負(fù)擔(dān)。
更新太頻繁地字段不適合創(chuàng)建索引:
當(dāng)你為這個(gè)字段創(chuàng)建索引時(shí)候,當(dāng)你再次更新這個(gè)字段數(shù)據(jù)時(shí),數(shù)據(jù)庫(kù)會(huì)自動(dòng)更新他的索引,所以當(dāng)這個(gè)字段更新太頻繁地時(shí)候那么就是不斷的更新索引。
如果一個(gè)字段同一個(gè)時(shí)間段內(nèi)被更新多次,那么不能為他建立索引。
?
索引失效的幾種情況
(1)盡量避免在 where 子句中對(duì)字段進(jìn)行 null 值判斷,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,如:
select id from t where num is null
可以在num上設(shè)置默認(rèn)值0,確保表中num列沒(méi)有null值,然后這樣查詢(xún):
select id from t where num=0
(2)盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進(jìn)行全表掃描。
(3)盡量避免在 where 子句中使用 or 來(lái)連接條件,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,
可以這樣查詢(xún):
select id from t where num=10 union all select id from t where num=20
(4)in 和 not in 也要慎用,否則會(huì)導(dǎo)致全表掃描,如:
select id from t where num in(1,2,3)對(duì)于連續(xù)的數(shù)值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3下面的查詢(xún)也將導(dǎo)致全表掃描:
select id from t where name like '%abc%'
若要提高效率,可以考慮全文檢索。
(5)盡量避免在where子句中對(duì)字段進(jìn)行函數(shù)操作,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描。如:
select id from t where substring(name,1,3)='abc'--name以abc開(kāi)頭的id select id from t where datediff(day,createdate,'2005-11-30')=0--‘2005-11-30’生成的id應(yīng)改為:
select id from t where name like 'abc%' select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'索引失效的情況還有很多,其他的還有使用<>或者單獨(dú)的單獨(dú)的>,<;當(dāng)變量采用的是times變量,而表的字段采用的是date變量時(shí)等
?
使用explains優(yōu)化慢查詢(xún)
MySQL的Explain命令用于查看執(zhí)行效果,顯示了mysql如何使用索引來(lái)處理select語(yǔ)句以及連接表。
可以幫助選擇更好的索引和寫(xiě)出更優(yōu)化的查詢(xún)語(yǔ)句。
explain的語(yǔ)法如下,在select語(yǔ)句前加上explain就可以:
EXPLAIN列的解釋:
table:顯示這一行的數(shù)據(jù)是關(guān)于哪張表的
type:這是重要的列,顯示連接使用了何種類(lèi)型。從最好到最差的連接類(lèi)型為const、eq_reg、ref、range、index和ALL
possible_keys:顯示可能應(yīng)用在這張表中的索引。如果為空,沒(méi)有可能的索引。可以為相關(guān)的域從WHERE語(yǔ)句中選擇一個(gè)合適的語(yǔ)句
key: 實(shí)際使用的索引。如果為NULL,則沒(méi)有使用索引。很少的情況下,MYSQL會(huì)選擇優(yōu)化不足的索引。這種情況下,可以在SELECT語(yǔ)句中使用USE INDEX(indexname)來(lái)強(qiáng)制使用一個(gè)索引或者用IGNORE INDEX(indexname)來(lái)強(qiáng)制MYSQL忽略索引
key_len:使用的索引的長(zhǎng)度。在不損失精確性的情況下,長(zhǎng)度越短越好
ref:顯示索引的哪一列被使用了,如果可能的話(huà),是一個(gè)常數(shù)
rows:MYSQL認(rèn)為必須檢查的用來(lái)返回請(qǐng)求數(shù)據(jù)的行數(shù)
Extra:關(guān)于MYSQL如何解析查詢(xún)的額外信息。
?
好文書(shū)簽?
MySQL索引及查詢(xún)優(yōu)化
SQL優(yōu)化避免索引失效
總結(jié)
以上是生活随笔為你收集整理的数据库索引的实现原理及查询优化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Ajax学习心得
- 下一篇: mysql作hive元数据库