自從學習 MySQL 以來,咱們一直聽到或者看到不少優化建議,好比說不要用 select * 查詢,用什麼字段就查什麼字段;建議用自增主鍵來做爲表的主鍵,等等。這些建議聽得不少感受都成了 MySQL 開發的常識了,可是對於這些優化建議,咱們有沒有想過爲何要這麼作呢?這篇博文咱們從 MySQL 的原理出發,來解釋下爲何有這些優化建議?html
★本文實驗環境 MySQL 5.7.25mysql
」
MySQL 的默認存儲引擎 InnoDB 使用 B+ 樹來存儲數據的,因此在分析優化建議以前,咱們有必要了解 B+ 樹索引的基本原理。git
上圖是一個 B + 樹索引示意圖(B+ 樹的定義能夠看這裏),每一個節點表示一個磁盤塊,也能夠理解爲數據庫中的頁。咱們來分析下 B+ 樹索引的查找過程,若是我要查詢主鍵爲 35 的數據,索引會怎麼走呢?首先會判斷 35 小於根節點 37 ,繼續查詢左子樹,判斷 35 大於 22 和 33 那麼進入其右子樹,找到了葉子節點 33 ,繼續遍歷找到了 35 ,最後取出其 data 便可。在索引的狀況下,查詢 35 只用了3次 IO 操做,這是很是高效的。在真實的場景下,3層的b+樹能夠表示上百萬的數據,若是上百萬的數據查找只須要三次IO,性能提升將是巨大的,若是沒有索引,每一個數據項都要發生一次IO,那麼總共須要百萬次的IO,顯然成本很是很是高。上圖中也是體現了只要維持樹的高度足夠低,IO 操做就會足夠少,IO次數少,查詢性能就會高。github
上圖就是一個 explian 執行計劃,先看看上面各個字段的含義是什麼?web
id: Query Optimizer 所選定的執行計劃中的查詢編號。sql
select_type: 所使用的查詢類型,主要有幾種查詢類型:數據庫
類型名稱 | 說明 |
---|---|
SIMPLE | 除子查詢或者UNION查詢以外的其餘查詢。 |
PRIMARY | 子查詢中的最外層查詢,注意並非主鍵查詢。 |
UNION | UNION語句中第二個 SELECT 開始的後面全部 SELECT,第一個 SELECT 爲 PRIMARY。 |
DEPENDENT UNION | 子查詢中的 UNION,且爲 UNION 中從第二個 SELECT 開始的後面全部 SELECT ,一樣依賴於外部查詢的結果集。 |
UNION RESULT | UNION 中的合併結果。 |
SUBQUERY | 子查詢內層查詢的第一個 SELECT,結果不依賴於外部查詢結果集 。 |
DEPENDENT SUBQUERY | 子查詢內層查詢的第一個 SELECT,結果依賴於外部查詢結果集。 |
DERIVED | 驅動表,就是主表 |
MATERIALIZED | 子查詢的結果被保存爲虛擬臨時表 |
UNCACHEABLE SUBQUERY | 結果集沒法緩存的子查詢 |
UNCACHEABLE UNION | 結果集沒法緩存的 UNION 查詢 |
table: 顯示執行這一步所訪問的數據庫中的表的名稱。緩存
partitions: 查詢分區表匹配的分區,非分區表顯示 NULL 。編輯器
type: 查詢表所使用的方式,類型以下:post
類型名稱 | 說明 |
---|---|
all | 全表掃描。 |
const | 讀常量,最多隻有一條記錄匹配,因爲是常量,因此實際上只要讀一次。 |
system | 系統表,表中只有一條數據,它是特殊的 const 類型。 |
eq_ref | 最多隻會匹配一條結果,通常是經過主鍵或者惟一索引來訪問。 |
ref | Join 語句中被驅動表的索引查詢 |
full_text | 使用 full_text 索引 |
ref_or_null | 與ref惟一的區別就是在使用索引查詢以外再增長一個空值查詢。 |
index_merge | 查詢中同時使用兩個(或者多個)索引,而後對索引結果進行merge以後再讀表數據。 |
unique_subquery | 子查詢中的返回結果字段組合是主鍵或者惟一索引 |
index_subquery | 子查詢中的返回結果字段組合是索引,可是不是主鍵或者惟一索引 |
range | 索引範圍掃描,常常出如今比較條件中 ,如:<, > ,BETWEEN 等 |
Index | 全索引掃描 |
他們的性能由好到差依次是:system > const > eq_ref > ref > full_text > ref_or_null > unique_subquery > index_subquery > range > index_merge > index > all 。
possible_keys: 查詢可能用到的索引。
key_len: 用到的索引長度。
ref: 展現將那些列或者常量與命中的索引比較。
rows: 執行此次查詢掃描的行數。
filtered: 過濾行數百分比,最大值是100,當顯示100時候,表示沒有過濾行, rows顯示了檢查的估計行數,乘以過濾百分比將顯示與下表鏈接的行數。例如,若是行數爲1000,過濾條件爲50.00(50%),則與下表聯接的行數爲1000×50%= 500。
extra: 執行查詢額外的條件,詳細的條件能夠查看這裏。
掌握了前面這些前置知識,咱們來用這些知識分析下常常建議咱們的那些MySQL優化建議。
當咱們每次創建表的時候都在考慮是用表的自增主鍵呢?仍是用 UUID 呢?可是從性能考慮咱們仍是建議使用自增 Id,爲何呢?主要是因爲 MySQL B+ 樹索引性質決定的,數據的新增是要更新索引的,也就是要更新 B+ 樹。換句話說,使用自增Id 和 非自增 Id 哪一種更新 B+ 樹更快,成本更低,誰就是更優的選擇。咱們來模擬下自增 Id 插入和非自增 Id 插入狀況。
自增Id 插入狀況: 咱們在一個已經有10條數據的 B + 數上插入2條數據,分別是10和11,咱們看看樹是如何變化的。
咱們這裏能夠發現兩個特色:
一、自增的數據插入影響的範圍永遠只有最右的子樹,要麼直接在子樹插入節點,要麼就是子樹分裂,影響其父節點。
二、除了最右子樹,其餘子樹的節點都是滿的。
上面兩個特色有什麼影響呢?咱們根據前面 B+ 樹索引示意圖能夠知道,每一個點都是一個磁盤塊,操做每一個節點至關於進行一次 IO,因爲每次插入影響的節點只有最右子樹,那麼磁盤 IO 的範圍就可控;最重要一點是除最右子樹,其餘子樹的節點都是滿的,這種狀況,葉子節點數據的物理連續性會更好, 根據局部性原理,查詢性能也會更高。
非自增 Id 插入狀況:
非自增 Id 插入特色對比自增 Id 插入咱們很容易就能知道:
一、插入影響節點不可控,沒法預知。
二、每一個子樹都存在葉子節點不滿的狀況。
按照以前的分析思路,咱們也就知道了非自增 Id 插入有什麼性能劣勢了。因爲插入數據影響節點不可控,致使節點分裂的狀況就會更頻繁,節點分裂也是 IO 操做,性能天然受到影響。子樹的葉子節點不滿,會致使葉子節點物理連續性很差。最後若是咱們是UUID的話,Id 過長,會佔用節點空間,每一個頁能存儲的節點變少,頁分裂變多,性能也會受到影響。這也是爲何建議使用自增主鍵的緣由。
咱們常常聽到查詢表,只要查詢本身想要的字段,不須要的字段就不要查詢,嚴禁使用 select *
,咱們能想到很直觀的理由就是,數據庫要幫你翻譯成每一個字段名去查詢,接着查詢多餘的字段會佔用內存,帶寬等資源。這確實是一個理由,並且這個理由很重要,可是我這裏想說的是另一個緣由,覆蓋索引。我以前的一篇索引文章也介紹了覆蓋索引,感興趣的同窗能夠點擊這裏。覆蓋索引的意思是指查詢使用聯合索引覆蓋了要查詢的字段,這樣數據庫不用去進行回表,從而減小IO,提升性能。
這裏我用MySQL官方給的示列數據進行一個實驗,數據地址下載能夠點擊這裏。
我選擇 employees
表數據進行演示,默認數據是沒有聯合索引的,咱們加上一個聯合索引:
---employee表結構-----------
+------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------+------+-----+---------+-------+
| emp_no | int(11) | NO | PRI | NULL | |
| birth_date | date | NO | | NULL | |
| first_name | varchar(14) | NO | MUL | NULL | |
| last_name | varchar(16) | NO | | NULL | |
| gender | enum('M','F') | NO | | NULL | |
| hire_date | date | NO | | NULL | |
+------------+---------------+------+-----+---------+-------+
複製代碼
ALTER TABLE employees.employees add Index `idx_first_name_last_name` (first_name,last_name);
複製代碼
查看該表索引狀況:show index from employees.employees;
表已經成功建立了first_name
和 last_name
的符合索引,咱們開啓 profiles 監控 SQL 執行的狀況。
SET SESSION profiling = 1;
複製代碼
而後分別執行如下SQL
SELECT first_name,last_name FROM employees.employees WHERE first_name='Eric';
SELECT * FROM employees.employees WHERE first_name='Eric';
複製代碼
查看Profiles;
這裏咱們看到使用 select * 比 select 字段慢了4倍左右,爲何會這樣呢?咱們看看執行計劃。
咱們看到二者的執行計劃幾乎同樣,只有 Extra 有區別,使用字段的 Extra 顯示 using index,這就告訴你只使用索引就找到了想要的數據,由於你的索引就是用 first_name
和 last_name
創建的, 而 select * 它還須要查詢 gender
和hire_date
字段(主鍵字段不用額外查,輔助索引指向的就是主鍵),因此它不能不進行回表查詢其餘字段,性能差別也是這裏。
本文從原理上分析了咱們平常的兩點建議,爲何建議使用自增主鍵?爲何不建議使用 select * 查詢?其實主要最終的緣由仍是和索引相關,既然咱們用索引來提升咱們的效率就要充分利用它,下面是知識點總結:
一、B+ 樹查詢的效率高低是受其樹高影響,樹的高度越低,查詢IO次數越少,性能相對也就越高。
二、執行計劃的類型由好到差依次是:system > const > eq_ref > ref > full_text > ref_or_null > unique_subquery > index_subquery > range > index_merge > index > all 。
三、自增主鍵的好處就是連續,插入維護的成本相對較低,同時子樹的葉子節點大部分是滿節點,物理連續性好,查詢性能更優。
四、UUID 主鍵長度過長,致使單個子節點存儲的主鍵變少,更平凡的出發頁分裂,影響性能,這也是爲何建議索引不要太長的緣由。
五、覆蓋索引是很好的優化技巧,可讓查詢直接經過索引返回數據,而不用回表,減小IO,提高性能。
一、https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
二、https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain-join-types
三、http://blog.codinglabs.org/articles/theory-of-mysql-index.html
關注公衆號,享受第一時間更新。