Indexes(索引):html
索引可使mysql快速的找到和檢索一張包含百萬甚至億萬數據的表中的一組紀錄。若是你曾使用過mysql,無論是多久,你可能爲了更輕快的獲得查詢的結果而使用過索引。你也可能發現mysql的索引有時候並不想你想像的那樣工做。mysql
對不少使用者來講,索引就像黑色的藝術。有時候奇蹟快速地工做,有時候卻像是緩慢或者阻礙數據插入。程序員
在本章中,咱們將介紹一些索引的概念和mysql提供的各類不一樣索引。sql
Indexing Basics(索引基礎):數據庫
理解mysql如何使用索引,最好首先理解索引的基礎工做和特徵。一旦你對索引的特徵有基本的理解,你就可以更加合理的正確使用它們了。緩存
Index Concepts(索引的概念):安全
要理解索引讓mysql作什麼,思考mysql是如何工做才獲得查詢的結果。想象電話本是一個含有California州大約35000000條電話的電話本集合。無序地記錄在大腦裏,考慮這樣查詢:服務器
select * from phone_book where last_name = 'Zawodny';ide
沒有任何類型的索引來諮詢,數據庫必須讀在phone_book這張表裏的全部記錄,而後比較last_name這個字段是否與字符串"Zawodny"匹配上。固然了,這種方法是低效的。一旦電話的記錄增長了,就會須要去找對應給予的記錄。在計算機科學中,咱們稱之爲O(n)時間複雜度問題。性能
然而給定的真實電話本,咱們都知道如何快速定位到名字是Zawodny的地方:翻到書的後面,以Z開頭的地方。由於第二個單詞是a,咱們知道全部匹配的會在名單列表以Z開頭的未知附近。這個方法是基於數據排序的知識的。
這是做弊,不是嗎?並非。你能夠快速找到Zawodnys的緣由是它們的姓被按字母排序了。固然了,如此簡單的找到Zawodny是由於你知道ABC字母。。。
多數教科書(好比這本)都會在數的背面提供索引。由於這些索引按順序排列在相應的頁碼,使你常常快速的找到書中的術語和概念。想要知道數據庫熱拷貝在哪裏有討論嗎?查看索引頁久知道了。
數據庫索引也是相似的。書的做者或出版者將會在書中選擇重要的概念和術語做爲索引。你能夠在數據庫的表中選擇特定的字段建立索引。用前面的例子,你會建立姓爲索引來快速查找電話號碼:
ALTER TABLE phone_book ADD INDEX (last_name)
在這樣作的過程當中,你會在phone_book表中向數據庫要一個按姓排列的順序列表。每一個名字都有本身匹配記錄的位置--就像這本書後面索引中列的各個條目的頁碼。
從數據庫服務器的角度來看,當在執行一個查詢時索引的存在能夠從固定的結果中快速的刪除可能的行。在沒有任何索引時,MYSQL(好比一個數據庫服務器)會檢查表中的每一行數據。這不只僅是時間上的浪費,也會佔用大量磁盤輸入輸出從而嚴重弄髒磁盤緩存。
在真實世界中,不多能找到剛剛被排序和已經排序的動態的數據。書是一種特例,它們趨向於保持靜止。
由於數據庫須要爲索引值維護一個單獨的列表,並在數據更新同時使它們保持更新。你千萬不能但願一個表中的全部字段都做爲索引。索引是一種空間和時間的折中。在作每一個insert,update,delete查詢時使你的大部分(不是所有)查詢更快時,你將在磁盤空間和CPU上犧牲一些額外的空間。
大部分數據庫的說明書使用了術語索引和鍵可互換。說last_name是phone_book表中的一個鍵等同於last_name字段是phone_book表的索引。
Partial indexes(部分索引):
索引是空間和性能的交換。可是有時候你並不但願用空間來交換性能。幸好,MYSQL爲你提供了不少關於經過索引控制空間的方法。當你有一張phone_book表,裏面有二十億數據。在last_name上增長一個索引將須要不少空間,若是每一個last_name佔8位,你會發現這個數據的索引大約須要16G的空間。不管你要作什麼,行指針都要對每條記錄增長額外的4-8位。
你能夠只將前4位做爲索引,而不是將整個last name做爲索引。
ALERT TABLE phone_book ADD INDEX (last_name(4))
當你這樣作的時候,已經減小了索引所須要部分數據約一半的空間。這個折中的是MYSQL不能消除太多行使用這個索引。好比像下面這樣的查詢:
SELECT * FROM phone_book WHERE last_name = 'Smith';
取出全部以Smit開頭的字段,包括全部名字是Smith,Smitty等人。這個查詢會在以後丟棄Smitty等不想關的行。
Multicolumn indexes(多列):
像不少關係數據庫引擎同樣,MySQL容許你建立的索引由多列組成。
ALERT TABLE phone_book ADD INDEX (last_name, first_name)
若是你常常在where條件中用簡短或單一沒有足夠的種類的列但查詢結果是全部列,這種索引能夠提升查詢速度。固然你也可使用部分索引減小佔用空間:
ALERT TABLE phone_table ADD INDEX (last_name(4), first_name(4))
在任一狀況下,查找Josh Woodward的快速執行:
select * from phone_book where last_name = 'Woodward' and first_name = 'Josh'
擁有last_name和first_name兩個索引意味着MYSQL能夠基於兩個字段消除行。因而更大程度上減小須要考慮的行。畢竟,在電話本上姓氏以Wood開頭的人遠遠多於姓氏以Wood開頭而且名以Josh開頭的人。
在討論多列索引時,你可能會看到單獨的索引列被稱爲鍵部分或「部分鍵」。多列索引也被稱爲綜合索引或混合索引。
那麼爲何不直接建立兩個索引,一個建在last_name上,另外一個建在first_name上?你也能夠那麼作,可是MYSQL不會同時使用它們。事實上,MYSQL在每次查詢的時候只會在每張表中使用一個索引--除了UNIONs以外[3].這個事實足以說明MySQL對於每張表的每次查詢將永遠只使用一個索引。
對於拆分索引上的first_name和last_name,MySQL會選擇其中一個。這個選擇是經過一個頗有根據的猜測獲得結果的,即判斷哪一個索引匹配到的行比較少。咱們之因此說這是一個頗有根據的猜測,是由於MySQL會對這個索引的蹤影進行統計來支撐他以爲哪一個索引更好的推斷。固然,這個統計是歸納性的。雖然他們常常讓MySQL作出明智的決定,可是若是你有很是團結的數據,MySQL可能會作出關於索引使用的次優選擇。在某些地方若是索引的鍵是稀疏的(好比以X開頭的名字)且其餘地方是高度集中的(好比名字是Smith的以英語爲母語的國家),咱們把這種數據稱爲次優數據。這是一個重要的點,咱們在書的稍後內容中將會提到。
Index order(索引的順序):
MySQL是如何在索引中對值排序的?若是你使用過其餘RDBMS,你可能但願MySQL對索引有按升序、降序或其餘順序排序的指定句法。MySQL提供了一個內部沒有控制排序方法的索引值。這是有緣由的,在4.0版本中,這個特性很好的優化了致使其餘數據庫性能下降的問題。
例如,有些數據庫可能會執行這個快速查詢:
SELECT * FROM phone_book WHERE last_name = 'Zawodny' ORDER BY first_name DESC
而後這個查詢變慢:
SELECT * FROM phone_book WHERE last_name = 'Zawodny' ORDER BY first_name ASC
爲什呢?由於有些數據庫按降序排序存儲索引,並對按這個順序讀取索引進行了優化。在第一個例子中,數據庫使用了多列索引定位到全部匹配的記錄,由於這些記錄已經按降序被存儲了,已經沒有必要再對他們進行排序了。可是在第二個例子中,服務器找到全部匹配的記錄,而後對這些行執行第二遍,以對它們進行排序。
在必要時,MySQL可以向後遍歷索引。這也能使查詢變得很是快,他能實如今任何狀況下都不須要對記錄進行排序。
Indexes as constraints(索引做爲約束):
索引並不全是用於查詢定位匹配行的。惟一索引指定特定值只能在給定列中出現一次。在電話本的例子中,你能夠對電話號碼建立一個惟一索引,以確保每一個電話號碼只出現一次。
ALERT TABLE phone_book ADD UNIQUE (phone_number)
惟一索引具備雙重目的,當你根據一個電話號查詢的時候他的功能和其餘索引同樣。
SELECT * FROM phone_book WHERE phone_number = '555-7271'
不過,他在插入和更新每條記錄的同時還能檢查以確保這條值是否已經存在。惟一索引以這種方式進行約束。
惟一索引和非惟一索引使用的空間同樣多。記錄每一個字段的值和位置。若是使用惟一索引做爲約束而從不做爲索引那就是一種浪費。換句話說,你能夠依靠惟一索引強制執行惟一性,可是不要寫使用惟一鍵的查詢。在這個例子中,MySQL沒有必要將每一個記錄的位置存儲在索引中:由於你永遠不會使用他們。
不幸的,沒有辦法向MySQL表示你的意圖。將來,咱們將特別介紹這個具體的例子。MyISAM存儲引擎已經支持了不帶索引的惟一字段(它使用基於散列的系統),可是該機制還沒有在SQL級別公開。
Clustered and secondary indexes(集羣和二級索引):
使用MyISAM表,索引保持在一個徹底隔離的文件中,包含了主鍵(也可能有二級鍵)列表和表示記錄的字節偏移量的值。這些確保了MySQL能夠找到而且快速跳到在數據庫中的那個點來查找記錄。MySQL必須用這種方法存儲索引,由於記錄基本上是以亂序進行存儲的。
集羣索引,主鍵和記錄自己彙集在一塊兒,而且記錄都以主鍵順序存儲。InnoDB使用匯集的索引。在Oracle世界,彙集的索引被稱爲索引組織表。它將幫助你記錄主鍵和行順序的關係。
當你的數據幾乎都經過他的主鍵查找時,彙集的索引可使查找快得難以置信。使用標準MyISAM索引,有兩個查找,一個到索引,另外一個到表自身,經過索引指定得位置。使用匯集的索引,有一個直接指向相關記錄的查找。
有些操做使用集羣索引是低效的。例如,當使用一個二級索引時,回到電話本的例子,當你須要將last_name做爲主索引,phone_number做爲二級索引,你作如下查詢:
SELECT * FROM phone_book WHERE phone_number = '555-7271'
MySQL遍歷了phone_number索引去查找555-7271項,包含了主鍵項Zawodny,由於電話本的主鍵是last name,MySQL會在數據庫自身中跳到相關項。
換句話說,基於主鍵的查詢將會很是快,基於二級索引的查找速度基本上和MyISAM索引是一致的。
可是在正確(或者說錯誤)的狀況下,彙集索引事實上可能會下降性能。當你和二級索引一塊兒使用時,你必須考慮這種結合對存儲的影響。二級索引指向主鍵而不是特定的行。因而,若是你在一個很大的值上設索引,而且同時有一些二級索引,你將最終得到不少主索引的許多重複副本,首先做爲與記錄一塊兒存儲的彙集索引,而後再次以相同的次數與次級索引指向這些彙集索引。使用小的值做爲主鍵,可能就不會這麼差了,可是若是你使用某些個別數據將特別長的東西做爲索引,例如URL,主鍵在磁盤上的重複存儲將會致使存儲問題。
另外一個不常見的可是一樣會發生問題的條件是:當數據在主鍵插入時主鍵在記錄中被修改。這是彙集索引最昂貴的功能。一些事情的發生可使這樣的操做有更多的性能衝擊。
根據查詢的結果在問題中插入記錄
基於插入數據的記錄,爲記錄決定新的主鍵
搬遷存儲的記錄一次這個問題中的記錄將在表空間中被移動到正確的位置
更新指向主鍵的全部二級索引
你可能在想,若是你爲一些記錄插入了主鍵,在執行UPDATE命令時將會花費至關一部分時間,特別是在更大的表中。更明智地選擇主鍵。儘可能使用幾乎不會改變的值,例如使用社會安全帳號而不是姓,使用序列號而不是產品名等等。
Unique indexes versus primary keys(惟一索引與主鍵):
若是你用過其餘關係型數據庫,你可能會好奇在MySQL中主鍵和惟一索引之間有什麼不一樣。一般這取決於,在MyISAM 表這並無什麼區別。主鍵中惟一不一樣的是它的值不能是NULL的。主鍵只是一個不爲空的惟一索引(NOT NULL UNIQUE INDEX)命名爲鍵(PRIMARY)。MyISAM表不要求你聲明一個主鍵。
InnoDB和BDB要求每一個表都有主鍵,不過沒有要求你指定其中某一個。若是你不指定,存儲引擎將自動爲你建立一個隱藏的主鍵。在這兩種例子下,主鍵只是簡單的增長數值,相似於一個自動增長列。若是你稍後要加一個主鍵,只須要使用ALTER TABLE添加一個。兩個存儲引擎將會丟棄它們本身內部的密鑰去使用你的密鑰。堆表不要求一個主鍵可是也會爲你建立一個。事實上你能夠建立一個無鎖的堆表。
Indexing NULLs:
SQL在執行邏輯操做時使用三態邏輯是很難記住的。除非一個字段聲明瞭NOT NUL,有三種可能的結果在邏輯比較中。這個比較可能因爲它們的值是等價的因此結果是對的;也有可能因爲它們的值是不等價的因此結果是錯誤的;又或者它會由於值是NULL而不會匹配。不管什麼時候比較的一個值是NULL那麼結果也是NULL。
程序員常常認爲NULL就至關於沒有被定義或未知的。這是一種告訴數據庫服務器「一個未知的數據來了」的方式。因此NULL值的數據怎麼能影響索引呢?
NULL值將會使用常規索引(即非惟一索引),全部數據庫服務器都是這樣。然而,不像狠多數據庫服務器,MySQL容許你在惟一索引上使用NULL值[6]。你能夠在惟一索引上存儲你想存儲儘量多的NULL值。這看起來感受有點違反直覺,但這就是NULL的本質。由於NULL表明了一個沒有被定義的值。若是它在惟一索引只容許單個值,MySQL須要宣稱全部的NULL值都是同樣的。
去作一些更有意思的事,一個NULL值在主鍵中可能值出現一次。爲何呢?由於SQL標準主宰了這一行爲。這是在MySQL少數方法中惟一不一樣於惟一鍵的主鍵。並且若是你想知道,在索引中容許NULL值並不會影響性能。
[1]這個有點誤差。數據庫不僅是存儲匹配記錄的位置,咱們很快久知道爲何。
[2]這也是一種過分簡化,MySQL使用一些策略來減小索引的大小,同時付出一些代價。
[3]在UNION中,每一個邏輯是單獨運行而後進行合併的。
[4]除了NULL,固然了,NULL老是特殊的狀況。
[5]然而在現實世界中,這或許是一個不好的例子,任何人均可以和幾個室友共用一個電話和你打電話。
[6]MySQL版本3.23及之前的版本不支持這個功能,4.0及之後的版本支持。
原文來自:https://www.safaribooksonline.com/library/view/high-performance-mysql/0596003064/ch04.html#ftn.hpmysql-CHP-4-FNOTE-4
另外的問題:
1.多列索引如:ALERT TABLE phone_table ADD INDEX (last_name(4), first_name(4)),下面各類狀況都可以利用這個多列索引進行快速查找嗎
(a)select * from phone_book where last_name = 'Woodward' and first_name = 'Josh';
此時這個查詢能夠利用多列索引進行快速查找。
(b)select * from phone_book where last_name = 'Woodward';
此時這個查詢能夠利用給出的多列索引進行快速查找
(c)select * from phone_book where first_name = 'Josh';
此時該查詢不能利用給出的多列索引進行快速查詢,由於這個多列索引是根據last_name找到它對應的first_name的。
2.數據庫的索引是怎麼工做的。。。這個問題就比較複雜啦,目前只瞭解了個大概,我須要時間研究一下