1. 程式人生 > 其它 >find_in_set不利用索引_InnoDB索引實現原理以及注意點和建議

find_in_set不利用索引_InnoDB索引實現原理以及注意點和建議

技術標籤:find_in_set不利用索引

94788f6d9a0d147f620061f5dffae640.gif

一、InnoDB實現原理

雖然InnoDB也使用B+Tree作為索引結構,但具體實現方式卻與MyISAM截然不同。因為InnoDB支援聚簇索引(主鍵索引),聚簇索引就是表,所以InnoDB不用像MyISAM那樣需要獨立的行儲存。也就是說,InnoDB的資料檔案本身就是索引檔案。

聚簇索引的每一個葉子節點都包含了主鍵值、事務ID、用於事務和MVCC的回滾指標以及所有的剩餘列。假設我們以col1為主鍵,則下圖是一個InnoDB表的聚簇索引(主鍵索引)(Primary key)示意。

與MyISAM不同的是,InnoDB的二級索引和聚簇索引很不相同。InnoDB的二級索引的葉子節點儲存的不是行號(行指標),而是主鍵列。這種策略的缺點是二級索引需要兩次索引查詢,第一次在二級索引中查詢主鍵,第二次在聚簇索引中通過主鍵查詢需要的資料行。

畫外音:可以通過我們前面提到過的索引覆蓋來避免回表查詢,這樣就只需要一次回表查詢,對於InnoDB而言,就是隻需要一次索引查詢就可以查詢到需要的資料記錄,因為需要的資料記錄已經被索引到二級索引中,直接就可以找到。

因為InnoDB的索引的方式通過主鍵聚集資料,嚴重依賴主鍵。索引如果沒有定義主鍵,那麼InnoDB會選擇一個唯一的非空索引代替。如果沒有這樣的索引,InnoDB會隱式定義一個主鍵來作為聚簇索引。

二、優缺點

優點

  1. 可以把相關資料儲存在一起,減少資料查詢時的磁碟I/O

  2. 資料訪問更快,因為聚簇索引就是表,索引和資料儲存在一個B+Tree中

  3. 使用索引覆蓋的查詢時可以直接使用頁節點中的主鍵值

缺點

  1. 插入速度嚴重依賴插入順序

  2. 更新聚簇索引列的代價很高,因為會強制InnoDB把更新的列移動到新的位置

  3. 基於聚簇索引的表在插入新行,或者主鍵被更新導致需要移動行的時候,可能會導致“頁分裂”。當行的主鍵值要求必須將這一行插入到已滿的頁中時,儲存引擎會將該頁分裂為兩個頁面來容納該行,這就是一次頁分裂操作,頁分裂會導致表佔用更多的儲存空間。

三、 注意&建議

  1. 主鍵推薦使用整型,避免索引分裂;

  2. 查詢使用索引覆蓋能夠提升很大的效能,因為避免了回表查詢

  3. 選擇合適的順序建立索引,有的場景並非區分度越高的欄位放在前邊越好,聯合索引使用居多

  4. 合理使用in操作將範圍查詢轉換成多個等值查詢,但是如果有order by 不同的列來說是不會走索引的

  5. 大批量資料查詢任務分解為分批查詢

  6. 將複雜查詢轉換為簡單查詢

  7. 合理使用inner join,比如分頁的時候

四、一些問題的分析

1、索引分裂個人理解:在 MySQL插入記錄的同時會更新配置的相應索引檔案,根據以上的瞭解,在插入索引時,可能會存在索引的頁的分裂,因此會導致磁碟資料的移動。當插入的主鍵是隨機字串時,每次插入不會是在B+樹的最後插入,每次插入位置都是隨機的,每次都可能導致資料頁的移動,而且字串的儲存空間佔用也很大,這樣重建索引不僅僅效率低而且 MySQL的負載也會很高,同時還會導致大量的磁碟碎片,磁碟碎片多了也會對查詢造成一定的效能開銷,因為儲存位置不連續導致更多的磁碟I/O,這就是為什麼推薦定義主鍵為遞增整型的一個原因

2、自增主鍵的弊端 對於高併發的場景,在InnoDB中按照主鍵的順序插入可能會造成明顯的爭用,主鍵的上界會成為“熱點”,因為所有的插入都發生在此處,索引併發的插入可能會造成間隙鎖競爭,何為間隙鎖競爭,下個會詳細介紹;另外一個原因可能是Auto_increment的鎖機制,在 MySQL處理自增主鍵時,當innodb_autoinc_lock_mode為0或1時,在不知道插入有多少行時,比如insert t1 xx select xx from t2,對於這個statement的執行會進行鎖表,只有這個statement執行完以後才會釋放鎖,然後別的插入才能夠繼續執行,但是在innodb_autoinc_lock_mode=2時,這種情況不會存在表鎖,但是隻能保證所有併發執行的statement插入的記錄是唯一併且自增的,但是每個statement做的多行插入之間是不連線的

3、優化器不使用索引選擇全表掃描 比如一張order表中有聯合索引(order_id, goods_id),在此例子上來說明這個問題是從兩個方面來說:

查詢欄位在索引中
select order_id from order where order_id > 1000;
--如果檢視其執行計劃的話,發現是用use index condition,走的是索引覆蓋。
查詢欄位不在索引中
select * from order where order_id > 1000;

此條語句查詢的是該表所有欄位,有一部分欄位並未在此聯合索引中,因此走聯合索引查詢會走兩步,首先通過聯合索引確定符合條件的主鍵id,然後利用這些主鍵id再去聚簇索引中去查詢,然後得到所有記錄,利用主鍵id在聚簇索引中查詢記錄的過程是無序的,在磁碟上就變成了離散讀取的操作,假如當讀取的記錄很多時(一般是整個表的20%左右),這個時候優化器會選擇直接使用聚簇索引,也就是掃全表,因為順序讀取要快於離散讀取,這也就是為何一般不用區分度不大的欄位單獨做索引,注意是單獨因為利用此欄位查出來的資料會很多,有很大概率走全表掃描。

4、範圍查詢之後的條件不走索引 根據 MySQL的查詢原理的話,當處理到where的範圍查詢條件後,會將查詢到的行全部返回到伺服器端(查詢執行引擎),接下來的條件操作在伺服器端進行處理,這也就是為什麼範圍條件不走索引的原因了,因為之後的條件過濾已經不在儲存引擎完成了。但是在 MySQL 5.6以後假如了一個新的功能index condition pushdown(ICP),這個功能允許範圍查詢條件之後的條件繼續走索引,但是需要有幾個前提條件:

  • 詢條件的第一個條件需要時有邊界的,比如select * from xx where c1=x and c2>x and c3

  • 支援InnoDB和MyISAM儲存引擎;

  • where條件的欄位需要在索引中;

  • 分表ICP功能5.7開始支援;

  • 使用索引覆蓋時,ICP不起作用。

5、分頁offset值很大效能問題
在 MySQL中,分頁當offset值很大的時候,效能會非常的差,比如limit 100000, 20,需要查詢100020條資料,然後取20條,拋棄前100000條,在這個過程中產生了大量的隨機I/O,這是效能很差的原因,為了解決這個問題,切入點便是減少無用資料的查詢,減少隨機I/O

利用inner join
select * from t1 inner join (select id from t1 where xxx order by xx limit 1000000,5) as t2 using(id);
--子查詢先走索引覆蓋查得id,然後根據得到的id直接取5條得資料。
利用範圍查詢條件來限制取出的資料
select * from t1 where id > 1000000 order by id limit 0, 5;
--即利用條件id > 1000000在掃描索引是跳過1000000條記錄,然後取5條即可,這種處理方式的offset值便成為0了,但此種方式通常分頁不能用,但是可以用來分批取資料。

作者:稀飯裡的米 來源:https://www.toutiao.com/a6779216318999560717/