MySQL 索引的優化

1、MySQL如何使用索引(index)mysql

  1.1 索引概述算法

  索引用於快速查找具備特定列值的行。sql

  若是不使用索引,MySQL必須從表的第一行開始,而後掃描整個表來尋找符合條件的行。這種狀況下,表越大,掃描整表的代價就越大,就越低效。數組

  索引是提升SELECT操做性能的最佳方式,MySQL的全部data type也都能創建索引。緩存

 

  可是,過多地建立索引會浪費存儲空間,也會浪費時間,由於MySQL會花費必定的時間決定使用哪些索引。數據結構

  並且,索引還會增長插入、更新和刪除的成本,由於在執行這些操做時必須更新每一個索引。ide

  由此可知,在設計索引時,咱們必須找到正確的平衡,才能使用最優索引集實現快速查詢。函數

 

  大多數MySQL索引(PRIMARY KEY,UNIQUE,INDEX和FULLTEXT)都存儲在B樹中。 例外:空間數據類型的索引使用R樹; MEMORY表也支持哈希索引; InnoDB使用FULLTEXT索引的反轉列表。性能

 

  1.2 MySQL一般會爲如下操做使用索引(特別地,MEMORY表中的hash索引的使用會在後面討論):優化

  • 爲了快速查找匹配where子句的行;
  • 刪除知足條件的行。若是有多個index可供選擇,MySQL一般會使用那個找到的結果集最小的index。
  • 對於多列索引(Mutil-column index),只有對索引列進行最左的連續組合,才能出發對這個多列索引的使用。
  • 在執行join操做時從其它表中取回行時,若是被使用的不一樣表中的兩個列的data type和size都相同,那麼MySQL能夠更加高效得使用它們的索引。這種狀況下,VARCHAR和CHAR若是size同樣,則它們被視做同一類型。如varchar(10)和char(10)。
  • 在非二進制字符串的字段的比較中,兩個列字段應該使用一樣的字符集(character set)。例如,比較utf-8的列與Latin1的列會排除掉索引的使用。
  • 不一樣類型的列比較時,例如一個字符串的列與一個時間或者數值列進行比較,若是他們的值在沒有通過轉換而不能比較,可能會阻止索引的使用。例如,數字1與字符串「1」,「 1」,「00001」 or 「01.e1」。這種狀況下,不會對String列使用任何索引。
  • 查找特定索引列key_col中的MIN()和MAX()。這個拆線呢會由預處理器進行優化,它會檢查是否在Where條件子句中使用了索引中key_col以前出現的列索引。
    SELECT MIN(key_part2),MAX(key_part2) FROM tbl_name WHERE key_part1=10
  • sort或者group一個表,若是進行排序或者分組的表是某個可用索引的最左前綴。
  • 在某些狀況下,查詢能夠被優化爲以在不諮詢數據行的狀況下檢索值。

 

  索引對於較小的表沒有太多用處,而一個大型表,若是查詢操做總要訪問大多數的行,那麼索引也效果甚微。

  事實上,當查詢須要訪問大多數行時,按序依次訪問比經過索引來訪問會更快。順序讀取能夠最大限度地減小磁盤搜索,即便查詢不須要全部行也是如此。

 

2、主鍵的優化

  主鍵列一般是在重要查詢中會使用的列。它具備關聯的索引,以實現快速查詢性能。 查詢性能受益於NOT NULL優化,由於它不能包含任何NULL值。

  使用InnoDB存儲引擎,表格數據在物理上進行組織,以根據主鍵或列進行超快速查找和排序。

  若是你的數據表很大並且十分重要,卻沒有主鍵或者主鍵列,你能夠建立一個單獨的自增數值列,將其做爲主鍵(ID)。這個id也能夠在join查詢中做爲其它表的外鍵。

 

3、外鍵的優化

  若一個表有不少列,你須要查詢不一樣列的組合,那麼將頻率較低的數據拆分爲每一個都有幾列的單表可能會頗有效,並經過複製數字ID將它們與主表關聯起來。 主表中的列。這樣,每一個小表均可以有一個主鍵來快速查找其數據,您可使用鏈接操做查詢所需的列集。根據數據的分佈方式,查詢可能執行較少的I/O和佔用更少的緩存內存,由於相關列被打包在磁盤上。(爲了最大化性能,查詢嘗試從磁盤讀取儘量少的數據塊;只有幾列的表能夠在每一個數據塊中容納更多的行。)

 

4、Column Indexs

  最多見的索引類型涉及單個列,能夠在某個數據結構中存儲該列的值的副本,容許快速查找具備相應列值的行。

  這個給數據結構就是B-tree,它容許索引快速查找到一個特定的值,或者一個值集合,或者某些範圍的 值,對應where條件子句中相關的操做符,=,>,<=,between,in等。

  不一樣的存儲引擎下,每一個表中最大的索引數以及最大的索引長度是不一樣的。全部的存儲引擎每表支持至少16個索引,以及支持的索引總長度至少爲256字節。固然多數存儲引擎有更大的支持。

  更多關於列索引的知識參考下面章節:

  13.1.14 「create index syntax」

 

  4.1 指定索引前綴長度(區分前綴索引)

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

  這個語句在建表時對一個String列建立了一個只使用該列的前10個字符的索引。

  這種方式可讓索引文件更小。當對BLOB或者TEXT列建立索引時,必須指定前綴長度,這個長度最大可達到1000字節,InnoDB通常是767字節。

 

  4.2 全文索引(FullText index)

  FULLTEXT索引用於全文搜索。 只有InnoDB和MyISAM存儲引擎支持FULLTEXT索引,而且僅支持CHAR,VARCHAR和TEXT列。

  索引始終發生在整個列上,而且不支持指定列索引前綴。 有關詳細信息,請參見第12.9節「全文搜索功能」。

 

  優化適用於針對單個InnoDB表的某些類型的FULLTEXT查詢。當查詢具備如下特徵時,特別有效:

  • FULLTEXT查詢僅返回文檔ID,文檔ID和搜索排名。
  • FULLTEXT查詢按分數的降序對匹配的行進行排序,並應用LIMIT子句來獲取前N個匹配的行。 要應用此優化,必須沒有WHERE子句和只有一個ORDER BY子句按降序排列。
  • FULLTEXT查詢僅檢索與搜索項匹配的行的COUNT(*)值,而不包含其餘WHERE子句。 將WHERE子句編碼爲WHERE MATCH(text)AGAINST('other_text'),不帶任何> 0比較運算符。

  對於包含全文表達式的查詢,MySQL在查詢執行的優化階段評估這些表達式。 優化器不僅是查看全文表達式並進行估計,它其實是在開發執行計劃的過程當中對它們進行評估。

  這種行爲的含義是,對於全文查詢,EXPLAIN一般比在優化階段沒有進行表達式評估的非全文查詢慢。

  對於全文查詢的EXPLAIN可能會顯示因爲在優化期間發生匹配而在Extra列中優化的Select表; 在這種狀況下,在之後的執行期間不須要訪問表。

  您能夠在空間數據類型上建立索引。 MyISAM和InnoDB支持空間類型的R樹索引。 其餘存儲引擎使用B樹來索引空間類型(ARCHIVE除外,它不支持空間類型索引)

 

  4.3 MEMORY存儲引擎上的索引

  MEMORY存儲引擎默認使用HASH索引,但也支持BTREE索引。

 

5、多列索引

  MySQL能夠建立複合索引(即多列索引)。 索引最多可包含16列。 對於某些數據類型,您能夠指定只使用索引列的前綴長度。

  當查詢涉及到了多列索引中的所有的列,或者涉及到這些列的最左前n列,MySQL就會使用多列索引。

  若是在索引定義中以正確的順序指定列,則單個複合索引能夠加速同一表上的多種查詢。

 

  多列索引能夠被當作爲一個排序數組,它的一行由一個相關索引列的全部值鏈接起來。

  做爲複合索引的替代方法,您能夠根據其餘列的信息引入「散列」列。 若是此列很短,至關獨特且已編制索引,則它可能比許多列上的「寬」索引更快。 在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索引會在如下狀況下被使用:

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';

  然而,下面的狀況,name索引就不會被使用:

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

  如今假設須要執行這樣一個查詢:

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

  若是col1和col2上存在多列索引,則能夠直接獲取相應的行。

  若是col1和col2上存在單獨的單列索引,優化程序將嘗試使用索引合併優化(請參見第8.2.1.3節「索引合併優化」),或嘗試經過肯定哪一個索引排除更多行來查找限制性最強的索引 並使用該索引來獲取行。

  若是表具備多列索引,則optimizer查找行可使用索引的任何最左前綴。 例如,若是在(col1,col2,col3)上有三列索引,則在(col1),(col1,col2)和(col1,col2,col3)上編制索引搜索功能。(最左前綴規則)

 

6、驗證索引的使用

  要常常檢查是,否全部的查詢都真正使用了表中建立的索引。

  使用EXPLAIN語句,查看8.8.1節中的章節「Optimizing Queries with EXPLAIN」

 

7、InnoDB和MyISAM索引的統計集合

  存儲引擎會收集有關表的統計信息以供優化器使用。

  表統計信息基於值組,而值組是一組具備「相同鍵前綴值(the same key prefix value)」的行。 出於優化程序的目的,一個重要的統計數據是平均值組大小( the average value group size.)。

  MySQL使用如下方式的平均值組大小:

  • 用於估計每次ref訪問必須讀取行的方式
  • 用於估計一個局部鏈接將產生多少行; 也就是說,此表單的操做將產生的行數:
    (...) JOIN tbl_name ON tbl_name.key = expr

  隨着索引的平均值組大小增長,索引對於這兩個目的不太有用,由於每一個查找的平均行數增長:

  爲了使索引有利於優化,最好每一個索引值都以表中的少許行爲目標。 當給定的索引值產生大量行時,索引不太有用,MySQL不太可能使用它。

 

  平均值組大小與表基數相關,表基數是值組的數量。

  SHOW INDEX語句顯示基於N / S的基數值,其中N是表中的行數,S是平均值組大小。 該比率產生表中的近似值組數。

 

8、B-tree索引和Hash索引的比較

  瞭解它們的不一樣很重要,尤爲對於MEMORY這樣容許你選擇B-tree索引或者hash索引的存儲引擎。

 

  8.1 B樹索引的特徵

  B樹索引可用於使用=,>,> =,<,<=或BETWEEN運算符的表達式中的列比較。

  若是LIKE的參數是不以通配符開頭的常量字符串,則索引也可用於LIKE比較。

  例如,如下的查詢語句可使用索引:

SELECT * FROM tbl_name WHERE key_col LIKE 'Patrick%'; SELECT * FROM tbl_name WHERE key_col LIKE 'Pat%_ck%';

  在第一個語句中,只考慮在 'Patrick'<= key_col <'Patrick' 範圍內的行。

  在第二個語句中,僅考慮範圍在 'Pat'<= key_col <'Pau' 的行。

  然而,下面的查詢語句則不會使用索引:

SELECT * FROM tbl_name WHERE key_col LIKE '%Patrick%'; SELECT * FROM tbl_name WHERE key_col LIKE other_col;

  第一個語句中,like的參數以通配符起頭,第二個語句,like的值不是一個常量。

  

  若是您使用「 ... LIKE'%string%' 」且string超過三個字符,則MySQL會使用Turbo Boyer-Moore算法初始化字符串的模式,而後使用此模式更快地執行搜索。

  若是一個列col創建了索引,那麼查詢中使用「col IS NULL」會使用索引。

 

  不跨越WHERE子句中全部AND級別的任何索引不用於優化查詢。 換句話說,爲了可以使用索引,必須在每一個AND組中使用索引的前綴。

  如下where子句的狀況會使用索引:

... WHERE index_part1=1 AND index_part2=2 AND other_column=3

/* index = 1 OR index = 2 */ ... WHERE index=1 OR A=10 AND index=2

/* optimized like "index_part1='hello'" */ ... WHERE index_part1='hello' AND index_part3=5

/* Can use index on index1 but not on index2 or index3 */ ... WHERE index1=1 AND index2=2 OR index1=3 AND index3=3;

  如下where子句的狀況不會使用索引:

/* index_part1 is not used */ ... WHERE index_part2=1 AND index_part3=2

/* Index is not used in both parts of the WHERE clause */ ... WHERE index=1 OR A=10

/* No index spans all rows */ ... WHERE index_part1=1 OR index_part2=10

  

  某些狀況下,MySQL不會使用索引,即便某個索引是可用的。

  發生這種狀況的一種狀況是,優化器估計使用索引將須要MySQL訪問表中很是大比例的行。 (在這種狀況下,表掃描可能會快得多,由於它須要較少的搜索。)

  可是,若是這樣的查詢使用LIMIT來僅檢索某些行,那麼MySQL不管如何都會使用索引,由於它能夠更快地找到在結果中返回的幾行。

 

  8.2 Hash索引的特徵

  Hash索引擁有一些不一樣的特徵:

  • 它們僅用於使用=或<=>運算符的相等比較(但速度很是快)。它們不用於例如「<」找到必定範圍的值的操做符。 依賴於這種單值查找的類型的系統被稱爲「鍵值存儲」; 要將MySQL用於此類應用程序,請儘量使用哈希索引。
  • 優化器沒法使用哈希索引來加速ORDER BY操做。 (此類索引不能用於按順序搜索下一個條目。)
  • MySQL沒法肯定兩個值之間大約有多少行(範圍優化器使用它來決定使用哪一個索引)。 若是將MyISAM或InnoDB表更改成哈希索引的MEMORY表,則可能會影響某些查詢。
  • 只有整個鍵可用於搜索行。 (使用B樹索引,鍵的任何最左邊的前綴均可用於查找行。)

 

9、索引使用的擴展

  InnoDB經過將主鍵列附加到它來自動擴展每一個次級索引。

  有以下表:

CREATE TABLE t1 ( i1 INT NOT NULL DEFAULT 0, i2 INT NOT NULL DEFAULT 0, d DATE DEFAULT NULL, PRIMARY KEY (i1, i2), INDEX k_d (d) ) ENGINE = InnoDB;

  此表定義列(i1,i2)上的主鍵。 它還在列(d)上定義了二級索引k_d,但InnoDB內部擴展了該索引並將其視爲列(d,i1,i2)。

  在肯定如何以及是否使用該索引時,優化程序會考慮擴展二級索引的主鍵列。 這能夠帶來更高效的查詢執行計劃和更好的性能。

  優化器可使用擴展的二級索引進行ref,range和index_merge索引訪問,用於鬆散索引掃描,用於鏈接和排序的優化,以及MIN()/ MAX()優化。

  

  下面的例子展現了執行計劃是如何被優化器是否使用了擴展的二級索引所影響的。

  假設t1被這些行填充:

INSERT INTO t1 VALUES (1, 1, '1998-01-01'), (1, 2, '1999-01-01'), (1, 3, '2000-01-01'), (1, 4, '2001-01-01'), (1, 5, '2002-01-01'), (2, 1, '1998-01-01'), (2, 2, '1999-01-01'), (2, 3, '2000-01-01'), (2, 4, '2001-01-01'), (2, 5, '2002-01-01'), (3, 1, '1998-01-01'), (3, 2, '1999-01-01'), (3, 3, '2000-01-01'), (3, 4, '2001-01-01'), (3, 5, '2002-01-01'), (4, 1, '1998-01-01'), (4, 2, '1999-01-01'), (4, 3, '2000-01-01'), (4, 4, '2001-01-01'), (4, 5, '2002-01-01'), (5, 1, '1998-01-01'), (5, 2, '1999-01-01'), (5, 3, '2000-01-01'), (5, 4, '2001-01-01'), (5, 5, '2002-01-01');

  如今考慮這個查詢:

EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'

  在這種狀況下,優化器不能使用主鍵,由於它包含列(i1,i2),查詢不引用i2。 相反,優化器能夠在(d)上使用輔助索引k_d,執行計劃取決因而否使用擴展索引。

  當優化器不考慮索引擴展時,它將索引k_d視爲僅(d)。 查詢的EXPLAIN產生如下結果:

mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ref possible_keys: PRIMARY,k_d key: k_d key_len: 4 ref: const rows: 5 Extra: Using where; Using index

  當優化器考慮索引擴展時,它將k_d視爲(d,i1,i2).這種狀況下,可使用最索引的最左前綴(d,i1)來得到更好的執行計劃。

mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ref possible_keys: PRIMARY,k_d key: k_d key_len: 8 ref: const,const rows: 1 Extra: Using index

  在兩個例子中,key顯示了優化器會使用二級索引k_d,而EXPLAIN輸出則展現了使用擴展索引後的改進:

  • key_len從4個字節變爲8個字節,表示key的查找使用列d和i1,而不只僅是d。
  • ref值從const變爲const,const,由於key的查找使用兩個key部分,而不是一個。
  • 行計數從5減小到1,代表InnoDB應該檢查更少的行以產生結果。
  • Extra值從using where; using index來using index。 這意味着只能使用索引讀取行,而無需查詢數據行中的列。

  最後,能夠設置相關係統變量(use_index_extensions)來控制索引擴展的使用。

 

10、優化器使用生成的列索引

  MySQL支持生成(generated)列的索引。例如:

CREATE TABLE t1 (f1 INT, gc INT AS (f1 + 1) STORED, INDEX (gc));

  生成列gc被定義爲f1+1,這個列也建立了索引。優化器在構建執行計劃時會考慮這個索引。

  在如下查詢中,WHERE子句引用gc,優化程序會考慮該列上的索引,以確認是否能產生更有效的計劃。

SELECT * FROM t1 WHERE gc > 9;

  優化器可使用生成列上的索引來生成執行計劃,即便在查詢中沒有直接引用生成列的名字。若是WHERE,ORDER BY或GROUP BY子句引用與某個有索引的生成列的定義匹配的表達式,則會發生這種狀況。以下所示:

SELECT * FROM t1 WHERE f1 + 1 > 9;

 

  對於生成列的索引的使用,有如下限制和條件:

  • 查詢語句中的表達式若是匹配到了生成列的定義,那它們必須是徹底一致的,且有一樣的結果類型。例如,若是定義生成列的表達式爲f1+1,查詢語句中使用的表達式爲「1+f1」,或者(f1+1)被用於與String比較,那麼優化器則不會認同。
  • 優化適用於如下運算符:=,<,<=,>,> =,BETWEEN和IN()。
  • 必須將生成的列定義爲至少包含函數調用或前一項中提到的運算符之一的表達式。
  • ...(還有幾條,實在是不想看了)
相關文章
相關標籤/搜索