高性能Mysql讀後感(二)

高性能索引

1. 獨立的列

索引列不能是表達式的一部分, 也不能是函數的參數.
/* 不能使用 user_id 列上的索引 */
select … where user_id + 1 = 5;
/* 不能使用 date 列上的索引 */
select … where TO_DAYS(CURRENT_DATE) - TO_DAYS(date) <=10;
始終將索引列單獨放在比較符號的一側

2. 前綴索引和索引選擇性

對很長的字符列(BLOB, TEXT, 很長的VARCHAR)做索引列是, 可以索引開始的部分字符. —> 前綴索引
mysql也不允許索引這些列的完整長度.

不重複的索引值(基數)和數據表的記錄總數(#T)的比值. 取值爲 1/#T~1 之間,
比值越高查詢效率越高(因爲可以過濾掉更多的數據行), 查詢效率越高. —> 索引選擇性
唯一索引的選擇性是1.

合適的長度.
前綴的”基數”應該接近於完整列的”基數”
注意
mysql 無法使用前綴索引做 order by 和 group by, 也無法做索引覆蓋掃描.

ps: 後綴索引, mysql原聲不支持, 但是可以吧列值 字符串反轉 在入庫, 後加前綴索引即可.

3. 多列索引

在多個列上建立獨立的單列索引大部分情況下並不能提高 mysql 的查詢性能.
mysql5.0 和更新版本引入了 索引合併 的策略.

ex:
select user_id,role_id from user_role
where user_id = 1 or role_id = 1;
可能會變成使用 union all (mysql5.0之後, 優化器做的優化)
select user_id,role_id from user_role where user_id = 1
union all
select user_id,role_id from user_role where role_id = 1 and user_id <> 1;
* 當對多個索引做相交時(多個 and 條件) —> 意味着需要一個包含相關列的多列索引.
* 當對多個索引做聯合時(多個 or 條件) —> 會耗費大量CPU和內存資源在算法的緩存, 排序, 和 合併上.
特別是索引選擇性不高時.
* 優化器不會把以上計算到 “查詢成本” 裏,有可能執行此查詢計劃還不如走全表掃描.

ps: optimizer_switch 關閉索引合併
ignore index 讓優化器忽略掉某些索引

4. 合適的索引列順序 (B-Tree索引)

正確的索引順序依賴於使用該索引的查詢.
並且更好的滿足查詢中的排序和分組. (B-Tree索引是順序存儲數據的)

如果只考慮優化 where條件, 不考慮排序和分組. 那麼將選擇性最高的列放在前面比較好.
否則就實際去測試吧…經驗法則未必適合所有的場景.

5. 聚簇索引 (InnoDB)

並不是一種單獨的索引類型, 而是一種數據存儲格式.

InnoDB的聚簇索引就是在同一個結構中保存了B-Tree索引和數據行. (想想myisam)

InnoDB默認會選擇表的主鍵(primary key), 如果沒有這樣的索引,
InnoDB存儲引擎會隱試的定義一個”主鍵”來作爲聚簇索引

好處:

  1. 把相關的數據保存在一起, (同一個”頁面”內的數據, 這裏指硬盤的頁, 相對的文件碎片會少些)
  2. 數據訪問更快. 因爲聚簇索引將數據和索引保存在同一個B-Tree中.
  3. 覆蓋索引掃描的查詢可以直接使用 頁節點(B-Tree的頁節點) 的主鍵值.
    缺點:
  4. 提升I/O密集型應用的性能. 如果能把數據全部放在內存中,
    那麼順序I/O訪問就不重要了,聚簇索引也就沒有了優勢
  5. 插入速度嚴重依賴於插入順序. 順序寫入的速度最快.
    (如果不是主鍵順序寫入的話,在加載完數據後,最好 OPTIMIZE TABLE下)
  6. 更新聚簇索引列的代價非常高, 因爲InnoDB會強制被更新的行移動到新位置(在硬盤中的位置)
  7. 在插入 或 更新時, 可能造成”頁分裂”(硬盤中的).
    當某個頁存滿時(寫入或更新數據到該行), 存儲引擎會將該頁分裂爲兩個頁來存儲數據該行數據
  8. 可能會導致全表掃描變慢, 在行比較稀疏(主鍵列不連續), 或者頁分裂較多的情況下導致數據存儲不連續
  9. 二級索引需要訪問兩次索引查找, 而不是一次.
    * InnoDB和MyISAM存放表的抽象圖 *

這裏寫圖片描述

* 在InnoDB表中按主鍵的順序寫入行 *

最簡單的是使用 AUTO_INCREMENT 的自增列.

最好避免隨機的(不連續的且值分佈範圍非常大的)聚簇索引. 比如UUID.

* 順序的主鍵造成的問題 *
在高併發的情況下. 按主鍵順序插入可能會造成爭用, 併發插入可能導致間隙鎖. 寫入可能會成爲瓶頸.

6. 覆蓋索引

mysql有時也可以使用索引來直接獲取列的數據. (這樣做就不再需要讀取數據行了)
一個索引包含(或者說覆蓋)所有需要查詢的字段的值 —> 覆蓋索引

* 只需要掃描索引而無需回表 *

  1. 索引的條目通常是小於數據行數的. 減少數據的訪問量, 減少磁盤I/O.
    因爲索引通常比數據更小, 可以更好的放入內存. (尤其是MyISAM, 其可以壓縮索引)
  2. 一般情況下索引是按照列值得順序存儲的(至少是在”單個硬盤頁”內是這樣的).
    所以對於範圍查詢(<,>等)會好些, 部分隨機I/O訪問變成順序I/O.
    (OPTIMIZE 命令優化索引完全順序排列)
  3. MyISAM 存儲引擎只在內存中緩存索引, 數據的緩存完全依賴於操作系統.
    所以在訪問數據時會進行一次系統調用,這可能有嚴重的系統性能問題.
  4. InnoDB 存儲引擎使用的是聚簇索引, 而且 InnoDB 的二級索引在葉子節點中保存的是行的主鍵值,
    如果二級索引可以覆蓋查詢, 那麼久不用再去訪問一次聚簇索引進行二次查詢了.

覆蓋索引的使用條件

在 索引中 滿足查詢的成本 比 查詢行 的成本 小很多 時

維護索引也是有代價的, 更新記錄行時, 新增記錄行時, 還有事務中 等等.

覆蓋索引必須要存儲索引列的值.
mysql 只能會用 B-Tree 索引做覆蓋索引.
哈希索引, 空間索引, 和全文索引 都不存儲索引列的值.

* 覆蓋索引的 坑 *
mysql 查詢優化器會在執行查詢前 判斷是否有一個索引能進行覆蓋. 如果索引覆蓋了 where 條件中的字段,
但不是整個查詢涉及的字段, 如果條件爲假, mysql5.5和其之前的版本也總是會回表獲取數據行.
(取一些不需要的數據, 然後在過濾掉返回給客戶端)

Tips:
InnoDB 的二級索引的葉子節點都包含了主鍵值, 變相的 相當於
key (username) == key (username,id) —> 其實 id 是 username 列上的索引中保存的值

7. 通過索引掃描來做排序

mysql 的排序方式有兩種:
1. 普通的排序操作 file_sort
2. 按索引順序掃描
如果有 join 操作的時候, order by 後的所有字段都是第一張表的時候, 才能使用到索引來排序.
並且也需要滿足 索引的最左前綴原則

ps:
key i_date_username_age (date, username, age)

* 但是 如果將排序的第一個字段被制定爲常量時,可以不遵循最左前綴原則 *
select … WHERE date = 2017-08-08 order by username, age
* 排序的順序也很重要 ASC / DESC *
排序不同,不能使用索引
select … WHERE date = 2017-08-08 order by username asc, age desc
* 注意範圍條件 是不能使用索引來排序的 *

select … where date < 2017-08-08 order username, age

8. 壓縮索引

針對 MyISAM 引擎, 減小索引的大小, 可以讓更多的索引放在內存中.

9. 冗餘和重複的索引

每創建一個索引, mysql 都要對其進行維護, 並且優化器在優化的時候, 也會去逐個進行考慮.
如果有了(a, b), 有建了(a), 那麼(a)就是重複的索引了, 可以刪除掉.
但是(b,a)相對而(a,b)來說就不是冗餘索引.
對於 InnoDB 存儲引擎, (a,id) 索引, id 就是多餘的了, 因爲索引 a 的值就是其對應的 id.

10. 未使用的索引

刪除之.
percona toolkit —> pt-index-usage

11. 索引和鎖

索引可以讓 鎖定 根據索引過濾出來的數據行.
InnoDB只有在訪問數據行的時候, 纔會對其加鎖. 而索引可以減少InnoDB訪問的數據行.
InnoDB 根據索引檢索數據返回給mysql服務器層後, mysql 服務器根據where字句過濾數據.
此時, 這些數據行已經被鎖住了.
全表掃描的話就等於是鎖表了.
ps: InnoDB在 二級索引上使用的是 共享(讀)鎖, 主鍵索引的時候是 排他(寫)鎖.

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