MySQL核心知識學習之路(5)

作爲一個後端工程師,想必沒有人沒用過數據庫,跟我一起復習一下MySQL吧,本文是我學習《MySQL實戰45講》的總結筆記的第五篇,總結了MySQL索引相關的實踐使用問題。

上一篇:MySQL核心知識學習之路(4)

1 普通索引與唯一索引如何選擇?

先說結論

查詢性能對比上普通索引和唯一索引差別不大。

更新性能對比上普通索引可以使用Change Buffer機制提高性能(前提:在業務層面保證數據唯一)。唯一索引則每次都需要判斷是否違反唯一約束,因此每次都需要從內存中找到對應數據頁,如果不在內存中則需要從磁盤讀取出來,因此效率較低。

因此,如果業務可以接受,從性能角度出發,建議優先考慮普通索引

關於Change Buffer機制

Change Buffer是一種特殊的數據結構,它的過程如下:

(1)在對數據變更時,如果數據所在的數據頁不在內存中的話,就先將更新操作記錄在Change Buffer中,不需要從磁盤中讀出數據頁。

(2)Change Buffer中的數據會最終更新到原數據頁,這個操作稱之爲Merge。

MySQL中進行Merge操作的時機包括:

  • 當目標數據頁加載到內存中的時候,會先執行Change Buffer中的Merge操作。

  • 系統後臺線程會定期執行Merge操作。

  • MySQL正常關閉(shutdown)時也會執行Merge操作。

使用Change Buffer的優點在於:將數據頁從磁盤中讀入內存涉及隨機IO訪問,是數據庫中成本最高的操作之一,Change Buffer可以有效減少隨機IO讀操作,從而提升性能

下圖展示了一個帶有Change Buffer的工作流程,假設我們向表t插入了兩行記錄,其中一行記錄在Page1(已經在內存中),另一行記錄在Page2(不在內存中,需要寫入到磁盤)。

insert into t(id,k) values(id1,k1),(id2,k2);

圖片來源:林曉斌《MySQL實戰45講》

Change Buffer的適用場景在於:寫多讀少的場景,數據頁在寫完以後不會被馬上訪問到。

Change Buffer不適用的場景:寫少讀多的場景,數據頁寫完後立馬會被查詢到,會立即出發merge操作,因此隨機IO訪問的次數不會減少。

Change Buffer與Redo log的對比:Redo log主要節省的是隨機寫磁盤的IO消耗(轉爲順序寫),而Change Buffer主要節省的是隨機讀磁盤的IO消耗

2 爲何MySQL有時候會選錯索引?

MySQL中,在索引建立之後,一條語句可能會命中多個索引,這時,索引的選擇就會交由 優化器來選擇合適的索引。優化器選擇索引的目的,是找到一個最優的執行方案,並用最小的代價去執行語句。

不過,MySQL中有時候會選錯索引,導致查詢性能較差,主要會出現在以下場景中。

場景1:由於索引統計信息不準確導致

解決辦法:使用 analyze table 命令重新統計索引信息。

原因:MySQL 在真正開始執行語句之前,並不能精確地知道滿足這個條件的記錄有多少條,而只能根據統計信息來估算記錄數。索引統計(cardinality列)信息不夠準確,會導致MySQL優化器無法準確判斷選擇。

補充:MySQL優化器對於索引的選擇,基於索引基數(cardinality)與表中數據行數(n_row_in_table)的比值,即索引選擇性:

索引選擇性=索引基數/數據行

cardinality非常關鍵,表示索引中不重複記錄的預估值。需要注意的是cardinality是一個預估值,而不是一個準確值。基本上用戶也不可能得到一個準確的值。在實際應用中,這個基數越大,索引的區分度越好。

我們可以使用 show index 方法,看到一個索引的基數。

場景2:優化器誤判導致

解決辦法A:應用端使用 force index 強行選擇一個索引。

select * from t force index(a) where a between 10000 and 20000;

解決辦法B:修改語句引導MySQL使用期望的索引。此方法不具備通用性。

解決辦法C:新增更合適的索引 或 刪除誤用的索引。此方法是一個繞過問題的思路。

3 如何給字符串字段加索引?

簡單粗暴:直接創建完整索引

直接創建完整索引,可能比較佔用空間

圖片來源:林曉斌《MySQL實戰45講》

前綴索引:節省空間的方式

創建前綴索引,比較節省空間,但會增加查詢掃描次數,並且不能使用覆蓋索引。比如下圖就展示了一個截取了email前六位的前綴索引。

圖片來源:林曉斌《MySQL實戰45講》

此方式需要判斷出前綴的合適長度,根據業務來定,主要看區分度。

示例:

select count(distinct left(email,4))as L4, 
count(distinct left(email,5))as L5,
count(distinct left(email,6))as L6
from SUser;

倒序存儲

倒序存儲,再創建前綴索引,用於繞過字符串本身前綴的區分度不夠的問題。

此方式適用於前綴區分度不高但後綴區分度高的場景,目的是提高索引的區分度。但此方式不支持範圍掃描。

示例:

select field_list from t 
where id_card = reverse('input_id_card_string');

Hash字段索引

創建hash字段索引,查詢性能穩定,但有額外的存儲和計算消耗。

此方式不支持範圍掃描。

示例:

select field_list from t 
where id_card_crc=crc32('input_id_card_string')
and id_card='input_id_card_string';

 

4 小結

本文總結了MySQL的索引相關的實踐使用問題,包括普通索引和唯一索引如何選擇,MySQL爲什麼有時候會選錯索引,怎麼給字符串字段加索引。

參考資料

林曉斌(丁奇),《MySQL實戰45講》

👇掃碼訂閱《MySQL實戰45講》

 

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