作爲一個後端工程師,想必沒有人沒用過數據庫,跟我一起復習一下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講》