mysql5.7- InnoDB索引和優化

8.3 優化和索引

8.3.1 MySQL如何使用索引

  索引用於根據指定的列值快速找到行。如果沒有索引,MySQL不得不進行全表掃描。表越大,花費的代價就越大。如果表中的列具有索引,MySQL可以快速確定出要在數據文件中間尋找的位置,而不必查找所有數據。這比按順序讀取每行要快得多。

  MySQL中大部分索引(PRIMARY KEY,UNIQUE,INDEX,FULLTEXT)是存儲在B樹中的。不過也有例外,空間索引使用R樹;內存表也支持HASH索引;InnoDB引擎下,FULLTEXT索引使用反轉列表。

  一般來說,下述描述已經說明了MySQL如何使用索引。HASH索引的特性在8.3.8節“Comparison of B-Tree and Hash Indexes”中討論。

MySQL爲以下操作使用索引:
  • 爲了快速找到匹配WHERE子句的行。
  • 如果有多個索引,MySQL通常選擇過濾度最高的索引,換句話說,使用該索引可以查找出最少數量的行。
  • 如果一個表有組合索引,那麼組成組合索引的所有列中,優化器可以使用最左側的列作爲前綴來查找行。例如,一個索引擁有三列(col1、col2、col3),如下查詢都將使用索引:(col1、col2)、(col1、col3)、(col1、col2、col3)。有關更多信息,請參閱第8.3.5節“多列索引”。
  • 關聯查詢時從不同的表中檢索行。如果關聯字段被聲明爲相同的類型和大小,那麼MySQL使用索引將會有更好的效率。僅限於此處來說,如果VARCHAR和CHAR被聲明爲相同的長度,則認爲它們是相同的。例如VARCHAR(10) 和CHAR(10) 擁有相同的長度,但是VARCHAR(10)和CHAR(15)不相同。

    比較兩個非二進制的字符串列時,它們需要擁有相同的編碼。例如,比較編碼爲utf8的列和編碼爲latin1的列,這將使索引失效。

    比較不同數據類型的列(例如,一個字符串類型的列與時間或數字類型的列比較)可能使索引失效,因爲它們的值不能被直接地比較,除非做相應的轉換。對於數值列中的給定值(如1),它可以與字符串列中的任意數量的值進行比較,例如“1”、“1”、“00001”或“01”。這就排除了使用字符串列的任何索引。
  • 爲找到指定索引的key_col的MIN()或MAX()的值(此處討論的前提應該是組合索引)。這種情況會被編譯器優化:在索引定義中檢查key_part_N = 常亮的key_part_N是否在key_col之前出現。如果是,MySQL對每個MIN()和MAX()表達式進行單個key查找,並使用結果替換。如果所有的表達式都被替換,則立即返回此查詢語句。例如:
    SELECT MIN(key_part2), MAX(key_part2) FROM tbl_name
    WHERE key_part1 = 10;

    key_part2就是key_col,key_part_N就是key_part1
  • To sort or group a table if the sorting or grouping is done on a leftmost prefix of a usable index (for example, ORDER BY key_part1, key_part2). If all key parts are followed by DESC, the key is read in reverse order. See Section 8.2.1.13, “ORDER BY Optimization”, and Section 8.2.1.14, “GROUP BY Optimization”.
  • 某些情況,一個查詢可以被優化爲檢索值而不必檢索數據行(一個索引爲這個查詢提供了所有需要的結果,稱爲覆蓋索引)。如果一個查詢從一個表中僅查詢包含在某個索引的列,則可以從索引樹中檢索所需值以獲得更大的速度。例如:
    SELECT key_part3 FROM tbl_name WHERE key_part1=1

  對於小表查詢和報表查詢而言,索引的作用並不是那麼重要。當查詢需要訪問大部分行時,順序讀取比通過索引讀取更快。順序讀取花費較少的磁盤搜索時間,即使查詢出來你不需要的某些行。詳情請參閱8.2.1.19節“避免全表掃描”。



8.3.2 主鍵優化

  表的主鍵表示在最重要的查詢中使用的列或列集。它具有關聯索引,爲了更快的查詢性能。查詢性能得益於NOT NULL優化,因爲它不能包含任何NULL值。在InnoDB存儲引擎中,爲了基於主鍵列(列集)的快速查找和排序,表數據在物理存儲上做了特殊的組織。

  如果你的表很大且非常重要,但是卻沒有一個適合做爲主鍵的列或列集,則可以創建一個自增主鍵列。當你使用外鍵連接表時,可以使用這些唯一的IDs作爲指向其它表中相應行的指針。



8.3.3 外鍵優化

  如果表中有很多列,且你查詢多列的不同組合,那麼將較少使用的列拆分到單獨的多個表中可能會有更高的效率,並且通過數值類型的ID將這些表關聯到主表。這種方式中,每一個小表擁有一個主鍵,可以用來快速查詢本表的數據,並且可以使用關聯查詢。取決於數據如何被分配(即如何進行縱向拆分),這種查詢方式可能花費較少的IO和緩存,因爲在硬盤上相關的列被放在了一起。(爲了使性能最大化,查詢時請儘量從硬盤上讀取較少的數據塊;擁有較少列的表,可以在單個數據塊上存儲更多的行數據。)



8.3.4 列索引

  最常見的索引類型只包含一個單獨的列,它將索引列的值的拷貝存儲在某種數據結構上,支持根據列值快速查找相關行。B樹這種數據結構允許索引快速找到與WHERE子句中操作符(例如=,>,<=,BETWEEN,IN等等)對應的一個指定值,或值集合,或一系列值。
  每種存儲引擎定義了每個表的最大索引數和最大索引長度。參見第14章:InnoDB存儲引擎,和第15章,如何選擇存儲引擎。所有存儲引擎支持每個表至少16個索引和至少256個字節的總索引長度。大多數存儲引擎具有更高的限制。

  關於列索引更多的信息,請參見第13.1.14節,“創建索引語法”。

索引前綴

  在字符串類型列的索引規範中,使用col_name(N)語法,你可以創建一個僅包含該列前N個字符的索引。這種方式創建的索引文件非常小。當你爲BLOB或TEXT列添加索引時,你必須爲索引指定前綴長度。例如:

CREATE TABLE test (blob_col BLOB, INDEX(blob_col(10)));

  前綴可以長達1000字節(InnoDB表中前綴長度爲767,除非你設置了innodb_large_prefix參數)

注意:
  前綴限制以字節爲單位,而在CREATE TABLE、ALTER TABLE、和CREATE INDEX語句中,如果列類型爲非二進制的字符串類型(CHAR、VARCHAR、TEXT),則前綴長度轉義爲字符長度;如果列類型爲二進制字符串類型(BINARY, VARBINARY, BLOB),則前綴長度爲字節長度。當爲不同類型字符集的列指定前綴長度時,一定要特別注意這一點。


全文索引

  全文索引用於全文檢索。僅InnoDB和MyISAM存儲引擎支持在CHAR、VARCHAR、TEXT類型列上的全文索引。該索引總是在整個列上進行,不支持前綴索引。詳細信息請參見第12.9節,“全文搜索功能”;
  FULLTEXT Indexes

空間索引

  你可以在空間類型的數據上創建索引。MyISAM和InnoDB支持在空間類型的數據上創建R樹索引。別的存儲引擎使用B樹來索引空間類型的數據。(ARCHIVE引擎不支持空間索引)。

內存存儲引擎中的索引

  MEMORY 存儲引擎默認使用HASH 索引,但是它也支持B樹索引。



8.3.5 組合索引

  MySQL可以創建組合索引(即索引包含多個列)。一個索引最多包含16列。對於某些數據類型,你可以只索引該列的指定前綴(見8.3.4節,列索引

  MySQL中,如果你定義了一個組合索引,則可以使用組合索引的所有列,或者第一列、前兩列、前三列等來查詢。即一個組合索引可以加快同一個表上的若干查詢。

  一個包含多列的索引可以被認爲是一個排序的數組,行包含了由索引列的值連接起來形成的新值。

注意:
  作爲組合索引的替代,你可以根據其它列的相關信息引入一個新的具有類似hash特性的列。如果這個列是短的,具有合理的唯一性,並使用它作爲索引,那麼這可能比一個“寬”索引(組合索引)更快。在MySQL中,非常容易做到這些:

SELECT * FROM tbl_name
WHERE 
      hash_col = MD5(CONCAT(val1, val2))
  AND col1 = val1 AND col2 = val2;

假設一個表具有以下結構:

CREATE TABLE test (
    id         INT NOT NULL,
    last_name  CHAR(30) NOT NULL,
    first_name CHAR(30) NOT NULL,
    PRIMARY KEY (id),
    INDEX name (last_name,first_name)
);

  名稱叫做‘name’的索引包含‘last_name’和‘first_name’兩列。索引可以利用last_name和first_name組合出的一個已知範圍的值來查詢。也可以只使用last_name來查詢,因爲last_name是索引的左前綴(按照組合索引中列定義的順序,從左邊起第一個列,叫做左前綴)(如本節後面所述)。因此,叫做‘name’的索引可以被用於以下的查詢:

SELECT * FROM test WHERE last_name='Widenius';

SELECT * FROM test
  WHERE last_name='Widenius' AND first_name='Michael';

SELECT * FROM test
  WHERE last_name='Widenius'
  AND (first_name='Michael' OR first_name='Monty');

SELECT * FROM test
  WHERE last_name='Widenius'
  AND first_name >='M' AND first_name < 'N';

  然而,該索引不能用於以下查找:

SELECT * FROM test WHERE first_name='Michael';

SELECT * FROM test
  WHERE last_name='Widenius' OR first_name='Michael';

  假設你運行以下SELECT語句:

SELECT * FROM tbl_name
  WHERE col1=val1 AND col2=val2;

  如果一個組合索引包含col1和col2列,符合條件的行可以被直接抓取出。如果存在兩個單列索引,分別建立在col1列和col2列上,那麼優化器將嘗試使用索引合併優化(見第8.2.1.3節:“索引合併優化”),或者嘗試根據哪個索引可以過濾掉更多的行來找到過濾度最高的索引,並使用它來抓取行。

  如果表具有包含多個列的索引,則優化器可以使用索引中最左邊的前綴來查找行。例如,有一個索引包含三列(COL1,COL2,COL3),則你可以使用索引(COL1)、(COL1、COL2)和(COL1、COL2、COL3)來搜索。

  如果查詢列不以左前綴開始,MySQL將不能使用索引。假設你執行以下SELECT語句:

SELECT * FROM tbl_name WHERE col1=val1;
SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2;

SELECT * FROM tbl_name WHERE col2=val2;
SELECT * FROM tbl_name WHERE col2=val2 AND col3=val3;

  如果索引創建在(COL1,COL2,COL3)上,僅前兩個查詢可以使用索引。第三個和第四個查詢雖然涉及索引列,但是沒有使用索引,因爲(col2)和(col2, col3)不是(COL1,COL2,COL3)的左前綴。



8.3.6 驗證索引的使用

  始終檢查所有查詢是否真的使用了表中創建的索引。使用EXPLAIN語句,詳情見第8.8.1節:“使用EXPLAIN優化查詢”。



8.3.7 InnoDB and MyISAM Index Statistics Collection

8.3.8 Comparison of B-Tree and Hash Indexes

8.3.9 Use of Index Extensions

8.3.10 Optimizer Use of Generated Column Indexes




14.8.2 InnoDB索引

14.8.2.1 聚簇索引和二級索引

  每一個InnoDB表都有一個特殊的索引:聚簇索引,它存儲着行數據。通常聚簇索引等於主鍵。爲了使查詢、插入和其它數據庫操作有更好的性能,你必須明白InnoDB怎樣使用聚簇索引優化表的大部分普通查找和DML操作。

  • 當你在表上定義了一個主鍵時,InnoDB使用它作爲聚簇索引。所以請爲你的每一張表創建一個主鍵。如果沒有一個邏輯上唯一、且永遠非空的列或組合列,那麼需要創建一個新的、值自動增長的列,它的值被自動填充。

  • 如果你沒有在表上定義主鍵,MySQL將會定位到第一個唯一索引,並且如果該列的值全部爲非空(NOT-NULL),InnoDB使用它作爲聚簇索引。

  • 如果表中沒有主鍵,或者沒有一個適合的唯一索引,InnoDB將在內部生成一個隱藏的聚簇索引:GEN_CLUST_INDEX。它是一個組合列索引,包含了行號(ROW ID)列。ROW ID是一個6字節的字段,每插入一條新記錄,其值就單純地遞增。此時,ROW ID值由InnoDB生成,行按照ROW ID排序。

聚簇索引如何加快查詢速度

  通過聚簇索引訪問一行的速度非常快,因爲索引會搜索直接指向擁有所有行數據的頁。如果表很大,相對於使用索引記錄不同頁存儲不同行數據的存儲組織方式,聚簇索引的結構通常能夠節省很多磁盤IO操作。

二級索引與聚簇索引的關係

  除聚簇索引外的所有索引都稱爲二級索引。在InnoDB中,二級索引中的每一條記錄都包含主鍵列,此外爲二級索引指定了由哪些列構成。在聚簇索引中,InnoDB使用主鍵值來搜索行。
  如果主鍵值很長,二級索引會佔用更多的空間,所以建議主鍵儘量的短。

  更多使用InnoDB聚簇和二級索引的說明,請參見第8.3節“優化和索引”。



14.8.2.2 InnoDB索引的物理結構

  除了空間索引,InnoDB的所有索引都是B樹結構。空間索引是R樹,這些樹是索引多維數據的專用數據結構。索引記錄被存儲在B樹或R樹的葉子結點中的頁中。索引頁的默認大小爲16KB。
  當新的記錄被插入到InnoDB的聚簇索引時,InnoDB嘗試保留1/16的頁空間以便爲了將來插入和更新索引。如果索引記錄是按順序插入(升序或降序),the resulting index pages are about 15/16 full。如果隨機插入, the pages are from 1/2 to 15/16 full。

  當創建或重建B樹索引時,InnoDB執行大量的加載。創建索引的方法被稱爲排序索引構建。innodb_fill_factor選項定義了在構建排序索引時,每一個B樹上頁的使用率,預留下的空間是爲了以後索引的增長。排序索引構建不支持空間索引。有關更多信息,請參閱第14.8.2.3節“排序索引構建”。將innodb_fill_factor設置爲100,在聚簇索引頁中預留1/16的空間爲了以後索引的增長。

  如果InnoDB的索引頁填充因子低於MERGE_THRESHOLD(未指定時默認爲50%),InnoDB嘗試收縮索引樹來釋放頁面空間。MERGE_THRESHOLD參數適用於B樹和R樹。有關更多信息,請參閱14.6.13節,“配置索引頁的合併閾值”。

  在初始化MySQL實例之前,你可以通過設定innodb_page_size選項來定義該實例的所有InnoDB表空間的所有頁大小。

  一旦實例的頁大小被設置,那麼你將不能改變它除非重新實例化。支持的大小有64KB,32KB,16KB (默認值),8KB,和4KB,對應的選項值是64k,32k,16k,8k,和4k。
  MySQL 5.7開始支持32KB和64KB的頁大小。有關更多信息,請參見innodb_page_size文檔。
  不同頁大小的不同實例,不能互相使用數據和日誌文件。



14.8.2.3 排序索引構建

  當創建或重建索引時,InnoDB會批量加載,來代替一次插入一條索引記錄。創建索引的方法也被叫做排序索引構建。排序索引構建不支持空間索引。

  構建索引有三個階段。第一階段,掃描聚簇索引,生成索引條目並將其添加到排序緩衝區。當排序緩衝區變滿時,條目被排序並寫入臨時中間文件。這個過程也被稱爲“運行”。第二階段,有一個或多個“運行”已寫入臨時中間文件,對文件中的所有條目執行合併排序。在第三階段和最後階段,將排序的條目插入到B樹中。

  在介紹排序索引構建之前,先介紹以下這種構建索引的方法。使用插入APIs,一次性將索引條目插入到B樹的一條記錄中。這種方法涉及到打開一個B樹光標以找到插入位置,隨後使用樂觀插入將條目插入到B樹的頁中。如果這次插入由於頁空間變滿而失敗,那麼執行悲觀插入。爲了找到可用的空間,悲觀插入將涉及到打開B樹光標、分割、合併B樹節點一系列操作。使用“自頂向下”方式構建索引的缺點是,它會花費代價來搜索插入位置和不斷地分割合併B樹節點。
  排序索引構建採用“自下向上”的方法構建索引。見Sorted Index Builds

最後附上一個總結較好的博客:數據庫索引的作用優點和缺點

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章