1. 程式人生 > >Mysql索引學習筆記

Mysql索引學習筆記

ech analyze 滿足 primary 假設 角度 重新 pre 函數

1、分類

  MySQL索引分為普通索引、唯一索引、主鍵索引、組合索引、全文索引。索引不會包含有null值的列,索引項可以為null(唯一索引、組合索引等),但是只要列中有null值就不會被包含在索引中。

(1)普通索引:create index index_name on table(column);

或者創建表時指定,create table(..., index index_name column);

(2)唯一索引:類似普通索引,索引列的值必須唯一(可以為空,這點和主鍵索引不同)

create unique index index_name on table(column);或者創建表時指定unique index_name column

(3)主鍵索引:特殊的唯一索引,不允許為空,只能有一個,一般是在建表時指定primary key(column)

(4)組合索引:在多個字段上創建索引,遵循最左前綴原則。alter table t add index index_name(a,b,c);

  最左前綴原則:https://mp.weixin.qq.com/s/RemJcqPIvLArmfWIhoaZ1g

(5)全文索引:主要用來查找文本中的關鍵字,不是直接與索引中的值相比較,像是一個搜索引擎,配合match against使用,現在只有char,varchar,text上可以創建全文索引。在數據量較大時,先將數據放在一張沒有全文索引的表裏,然後再利用create index創建全文索引,比先生成全文索引再插入數據快很多。

從另外的角度還可以分為

1、聚集索引。

  表數據按照索引的順序來存儲的,也就是說索引項的順序與表中記錄的物理順序一致。對於聚集索引,葉子結點即存儲了真實的數據行,不再有另外單獨的數據頁。 在一張表上最多只能創建一個聚集索引,因為真實數據的物理順序只能有一種。

2、非聚集索引。

  表數據存儲順序與索引順序無關。對於非聚集索引,葉結點包含索引字段值及指向數據頁數據行的邏輯指針,其行數量與數據表行數據量一致。

2、使用

2.1、何時使用索引

MySQL每次查詢只使用一個索引。與其說是“數據庫查詢只能用到一個索引”,倒不如說,和全表掃描比起來,去分析兩個索引B+樹更加耗費時間。所以where A=a and B=b這種查詢使用(A,B)的組合索引最佳,B+樹根據(A,B)來排序。

(1)主鍵,unique字段;

(2)和其他表做連接的字段需要加索引;

(3)在where裏使用>,≥,=,<,≤,is null和between等字段;

(4)使用不以通配符開始的like,where A like ‘China%‘;

(5)聚集函數MIN(),MAX()中的字段;

(6)order by和group by字段;

2.2、何時不使用索引

 (1)表記錄太少;

(2)數據重復且分布平均的字段(只有很少數據值的列);

(3)經常插入、刪除、修改的表要減少索引;

(4)text,image等類型不應該建立索引,這些列的數據量大(假如text前10個字符唯一,也可以對text前10個字符建立索引);

(5)MySQL能估計出全表掃描比使用索引更快時,不使用索引;

2.3、索引何時失效

(1)組合索引未使用最左前綴,例如組合索引(A,B),where B=b不會使用索引;

(2)like未使用最左前綴,where A like ‘%China‘;

技術分享圖片

(3)搜索一個索引而在另一個索引上做order by,where A=a order by B,只使用A上的索引,因為查詢只使用一個索引 ;

(4)or會使索引失效。如果查詢字段相同,也可以使用索引。例如where A=a1 or A=a2(生效),where A=a or B=b(失效)

技術分享圖片

  註意:要想使用or,又想讓索引生效,只能將or條件中的每個列都加上索引

(5)如果列類型是字符串,要使用引號。例如where A=‘China‘,否則索引失效(會進行類型轉換);

技術分享圖片

(6)在索引列上的操作,函數(upper()等)、or、!=(<>)、not in等;

others

1) 沒有查詢條件,或者查詢條件沒有建立索引 2) 在查詢條件上沒有使用引導列 3) 查詢的數量是大表的大部分,應該是30%以上。 4) 索引本身失效 5) 查詢條件使用函數在索引列上,或者對索引列進行運算,運算包括(+,-,*,/,! 等) 錯誤的例子:select * from test where id-1=9; 正確的例子:select * from test where id=10; 6) 對小表查詢 7) 提示不使用索引 8) 統計數據不真實 9) CBO計算走索引花費過大的情況。其實也包含了上面的情況,這裏指的是表占有的block要比索引小。 10)隱式轉換導致索引失效.這一點應當引起重視.也是開發中經常會犯的錯誤. 由於表的字段tu_mdn定義為varchar2(20),但在查詢時把該字段作為number類型以where條件傳給Oracle,這樣會導致索引失效. 錯誤的例子:select * from test where tu_mdn=13333333333; 正確的例子:select * from test where tu_mdn=‘13333333333‘; 12) 1,<> 2,單獨的>,<,(有時會用到,有時不會) 13,like "%_" 百分號在前. 4,表沒分析. 15,單獨引用復合索引裏非第一位置的索引列. 16,字符型字段為數字時在where條件裏不添加引號. 17,對索引列進行運算.需要建立函數索引. 18,not in ,not exist. 19,當變量采用的是times變量,而表的字段采用的是date變量時.或相反情況。 20,B-tree索引 is null不會走,is not null會走,位圖索引 is null,is not null 都會走 21,聯合索引 is not null 只要在建立的索引列(不分先後)都會走, in null時 必須要和建立索引第一列一起使用,當建立索引第一位置條件是is null 時,其他建立索引的列可以是is null(但必須在所有列 都滿足is null的時候),或者=一個值; 當建立索引的第一位置是=一個值時,其他索引列可以是任何情況(包括is null =一個值),以上兩種情況索引都會走。其他情況不會走

3、其他

3.1、explain語句

技術分享圖片

3.2、\g、\G

\g 的作用是分號和在sql語句中寫’;’是等效的
\G 的作用是將查到的結構旋轉90度變成縱向 

4、例子

create table test(
id1 int ,
id2 int,
id3 int,
id4 int,
key index_id12(id1,id2)
);

用到索引
explain select * from test where id1 < 10;
用到索引
explain select * from test where id1 < 10 and id2 > 1; 
用到索引
explain select * from test where id2 > 1 and id1 < 2; 
未用到索引,組合索引要滿足最左原則
explain select * from test where id2 > 1;
未用到索引
explain select * from test order by id1 desc ;
用到索引
explain select id1 from test order by id1 desc ;
explain select id1,id2 from test order by id1 desc ;
未用到索引
explain select id1,id2,id3 from test order by id1 desc ;

5、常見面試問題

以下全部是基於MySQL的InnoDB引擎

5.1、什麽是最左前綴原則

例如對於下面這個表

技術分享圖片

如果我們按照 name 字段來建立索引的話,采用B+樹的結構,大概的索引結構如下

技術分享圖片

如果我們要進行模糊查找,查找name 以“張"開頭的所有人的ID,即 sql 語句為

select ID from table where name like ‘張%‘

  由於在B+樹結構的索引中,索引項是按照索引定義裏面出現的字段順序排序的,索引在查找的時候,可以快速定位到 ID 為 100的張一,然後直接向右遍歷所有開頭的人,直到條件不滿足為止。

也就是說,我們找到第一個滿足條件的人之後,直接向右遍歷就可以了,由於索引是有序的,所有滿足條件的人都會聚集在一起。

而這種定位到最左邊,然後向右遍歷尋找,就是我們所說的最左前綴原則

5.2、為什麽用 B+ 樹做索引而不用哈希表做索引

1、哈希表是把索引字段映射成對應的哈希碼然後再存放在對應的位置,這樣的話,如果我們要進行模糊查找的話,顯然哈希表這種結構是不支持的,只能遍歷這個表。而B+樹則可以通過最左前綴原則快速找到對應的數據。

2、如果我們要進行範圍查找,例如查找ID為100 ~ 400的人,哈希表同樣不支持,只能遍歷全表。

3、索引字段通過哈希映射成哈希碼,如果很多字段都剛好映射到相同值的哈希碼的話,那麽形成的索引結構將會是一條很長的鏈表,這樣的話,查找的時間就會大大增加。

5.3、主鍵索引和非主鍵索引有什麽區別

例如對於下面這個表(其實就是上面的表中增加了一個k字段),且ID是主鍵。

技術分享圖片

主鍵索引和非主鍵索引的示意圖如下:

技術分享圖片

其中R代表一整行的值。

  從圖中不難看出,主鍵索引和非主鍵索引的區別是:非主鍵索引的葉子節點存放的是主鍵的值,而主鍵索引的葉子節點存放的是整行數據,其中非主鍵索引也被稱為二級索引,而主鍵索引也被稱為聚簇索引

  根據這兩種結構我們來進行下查詢,看看他們在查詢上有什麽區別。

1、如果查詢語句是 select * from table where ID = 100,即主鍵查詢的方式,則只需要搜索 ID 這棵 B+樹。

2、如果查詢語句是 select * from table where k = 1,即非主鍵的查詢方式,則先搜索k索引樹,得到ID=100,再到ID索引樹搜索一次,這個過程也被稱為回表。

5.4、為什麽建議使用主鍵自增的索引

對於這棵主鍵索引的樹

技術分享圖片

如果我們插入 ID = 650 的一行數據,那麽直接在最右邊插入就可以了

技術分享圖片

但是如果插入的是 ID = 350 的一行數據,由於 B+ 樹是有序的,那麽需要將下面的葉子節點進行移動,騰出位置來插入 ID = 350 的數據,這樣就會比較消耗時間,如果剛好 R4 所在的數據頁已經滿了,需要進行頁分裂操作,這樣會更加糟糕。

但是,如果我們的主鍵是自增的,每次插入的 ID 都會比前面的大,那麽我們每次只需要在後面插入就行, 不需要移動位置、分裂等操作,這樣可以提高性能。也就是為什麽建議使用主鍵自增的索引。

5.5、一條SQL語句執行得很慢的原因有哪些

一個 SQL 執行的很慢,我們要分兩種情況討論:

5.5.1、大多數情況下很正常,偶爾很慢,則有如下原因

(1)、數據庫在刷新臟頁,例如 redo log 寫滿了需要同步到磁盤。

  當我們要往數據庫插入一條數據、或者要更新一條數據的時候,我們知道數據庫會在內存中把對應字段的數據更新了,但是更新之後,這些更新的字段並不會馬上同步持久化到磁盤中去,而是把這些更新的記錄寫入到 redo log 日記中去,等到空閑的時候,在通過 redo log 裏的日記把最新的數據同步到磁盤中去。

  不過,redo log 裏的容量是有限的,如果數據庫一直很忙,更新又很頻繁,這個時候 redo log 很快就會被寫滿了,這個時候就沒辦法等到空閑的時候再把數據同步到磁盤的,只能暫停其他操作,全身心來把數據同步到磁盤中去的,而這個時候,就會導致我們平時正常的SQL語句突然執行的很慢,所以說,數據庫在在同步數據到磁盤的時候,就有可能導致我們的SQL語句執行的很慢了。

(2)、執行的時候,遇到鎖,如表鎖、行鎖。

  這個就比較容易想到了,我們要執行的這條語句,剛好這條語句涉及到的,別人在用,並且加鎖了,我們拿不到鎖,只能慢慢等待別人釋放鎖了。或者,表沒有加鎖,但要使用到的某個一行被加鎖了,這個時候,我也沒辦法啊。

  如果要判斷是否真的在等待鎖,我們可以用 show processlist這個命令來查看當前的狀態哦,這裏我要提醒一下,有些命令最好記錄一下。

5.5.2、這條 SQL 語句一直執行的很慢,那麽sql本身的問題了,一般有如下原因。

我們先來假設我們有一個表,表裏有下面兩個字段,分別是主鍵 id,和兩個普通字段 c 和 d。

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

  

(1)、沒有用上索引:例如該字段沒有索引;由於對字段進行運算、函數操作導致無法用索引。

沒有用上索引,c 字段上沒有索引,那麽抱歉,只能走全表掃描了

select * from t where 100 <c and c < 100000;

字段有索引,但卻沒有用索引  

select * from t where c - 1 = 1000;

正確的查詢應該如下

select * from t where c = 1000 + 1;

函數操作導致沒有用上索引

select * from t where fun(c,2) = 1000;

    

(2)、數據庫選錯了索引。

  我們知道,主鍵索引和非主鍵索引是有區別的,主鍵索引存放的值是整行字段的數據,而非主鍵索引上存放的值不是整行字段的數據,而且存放主鍵字段的值。面試小知識:MySQL索引相關 裏面有說到主鍵索引和非主鍵索引的區別.

  也就是說,我們如果走 c 這個字段的索引的話,最後會查詢到對應主鍵的值,然後,再根據主鍵的值走主鍵索引,查詢到整行數據返回。

  就算你在 c 字段上有索引,系統也並不一定會走 c 這個字段上的索引,而是有可能會直接掃描掃描全表,找出所有符合 100 < c and c < 100000 的數據。

為什麽會這樣呢?

  其實是這樣的,系統在執行這條語句的時候,會進行預測:究竟是走 c 索引掃描的行數少,還是直接掃描全表掃描的行數少呢?顯然,掃描行數越少當然越好了,因為掃描行數越少,意味著I/O操作的次數越少。

  如果是掃描全表的話,那麽掃描的次數就是這個表的總行數了,假設為 n;而如果走索引 c 的話,我們通過索引 c 找到主鍵之後,還得再通過主鍵索引來找我們整行的數據,也就是說,需要走兩次索引。而且,我們也不知道符合 100 c < and c < 10000 這個條件的數據有多少行,萬一這個表是全部數據都符合呢?這個時候意味著,走 c 索引不僅掃描的行數是 n,同時還得每行數據走兩次索引。

  所以呢,系統是有可能走全表掃描而不走索引的。那系統是怎麽判斷呢?

  判斷來源於系統的預測,也就是說,如果要走 c 字段索引的話,系統會預測走 c 字段索引大概需要掃描多少行。如果預測到要掃描的行數很多,它可能就不走索引而直接掃描全表了。

  那麽問題來了,系統是怎麽預測判斷的呢?

  系統是通過索引的區分度來判斷的,一個索引上不同的值越多,意味著出現相同數值的索引越少,意味著索引的區分度越高。我們也把區分度稱之為基數,即區分度越高,基數越大。所以呢,基數越大,意味著符合 100 < c and c < 10000 這個條件的行數越少。

所以呢,一個索引的基數越大,意味著走索引查詢越有優勢。

  那麽問題來了,怎麽知道這個索引的基數呢?

  系統當然是不會遍歷全部來獲得一個索引的基數的,代價太大了,索引系統是通過遍歷部分數據,也就是通過采樣的方式,來預測索引的基數的。

  扯了這麽多,重點的來了,居然是采樣,那就有可能出現失誤的情況,也就是說,c 這個索引的基數實際上是很大的,但是采樣的時候,卻很不幸,把這個索引的基數預測成很小。例如你采樣的那一部分數據剛好基數很小,然後就誤以為索引的基數很小。然後就呵呵,系統就不走 c 索引了,直接走全部掃描了

所以呢,說了這麽多,得出結論:由於統計的失誤,導致系統沒有走索引,而是走了全表掃描,而這,也是導致我們 SQL 語句執行的很慢的原因。

這裏我聲明一下,系統判斷是否走索引,掃描行數的預測其實只是原因之一,這條查詢語句是否需要使用使用臨時表、是否需要排序等也是會影響系統的選擇的。

  不過呢,我們有時候也可以通過強制走索引的方式來查詢,例如

select * from t force index(a) where c < 100 and c < 100000;

  我們也可以通過

show index from t;

  來查詢索引的基數和實際是否符合,如果和實際很不符合的話,我們可以重新來統計索引的基數,可以用這條命令

analyze table t;

  來重新統計分析。

既然會預測錯索引的基數,這也意味著,當我們的查詢語句有多個索引的時候,系統有可能也會選錯索引哦,這也可能是 SQL 執行的很慢的一個原因。

5.5.3、總結

一個 SQL 執行的很慢,我們要分兩種情況討論:

1、大多數情況下很正常,偶爾很慢,則有如下原因

(1)、數據庫在刷新臟頁,例如 redo log 寫滿了需要同步到磁盤。

(2)、執行的時候,遇到鎖,如表鎖、行鎖。

2、這條 SQL 語句一直執行的很慢,則有如下原因。

(1)、沒有用上索引:例如該字段沒有索引;由於對字段進行運算、函數操作導致無法用索引。

(2)、數據庫選錯了索引。

6、出處文章

https://blog.csdn.net/guoxingege/article/details/51034387

https://blog.csdn.net/weixin_39420024/article/details/80040549

MySQL索引相關:

https://mp.weixin.qq.com/s/RemJcqPIvLArmfWIhoaZ1g

一條SQL語句執行得很慢的原因有哪些:

https://mp.weixin.qq.com/s/pTywDcdg8AVZ8qvR0KZFFQ

Mysql索引學習筆記