本篇博客偏理論, 將會介紹如下知識:html
數據庫的數據通常存儲在磁盤中, 相比較內存, 磁盤的訪問速度較慢索引就是能夠幫助數據庫快速從磁盤中找到數據的一種數據結構python
雖然會下降, 可是通常的應用系統,讀寫比例在10:1左右,並且插入操做和通常的更新操做不多出現性能問題,遇到最多的,也是最容易出問題的,仍是一些複雜的查詢操做,因此查詢語句的優化顯然是重中之重mysql
緣由 :sql
1.一個軟件慢會影響用戶體驗, 可是慢的緣由有不少, 你不能當即肯定就是 SQL 的問題, 當你定位到 SQL 問題的時候就已通過去好久了, 問題沒有獲得及時的解決數據庫
2.大多數DBA都是管理型DBA而非開發型, 因此即使是DBA從日誌中看到了慢查詢sql, 也會由於其不懂業務而很難分析出慢的緣由windows
就好比買火車票(無索引) : 若是沒有12360火車票訂購軟件, 擺在咱們面前的就是成千上萬輛火車, 選擇那一輛的條件有火車類型、出發和終點、時間等等, 咱們須要一輛一輛火車去比對本身的篩選條件, 運氣好第一輛就是要找的火車, 運氣很差第一千輛纔是要找的火車緩存
加入索引 : 如今咱們只須要在12360軟件上選擇高鐵, 就能篩選掉不是高鐵的火車, 縮小了查詢範圍; 再輸入出發點和終點, 又縮小了查詢範圍; 再輸入時間, 範圍又減小, 最終找到本身須要的車次, 由不固定查詢次數變成很小的固定查詢次數數據結構
IO延遲 = 平均尋道時間 + 平均延遲時間(通常爲9ms)--->例子:假設當前硬盤轉軸(盤片)轉速是7200/min,也就是120/s,那麼轉一圈須要花費1/120≈8ms,半圈也就是4ms(假設找到數據要半圈)函數
9ms左右對於咱們來說很短, 但對於一臺500-MIPS的機器來講每秒能夠執行5億條指令, 換句話說執行一次IO的時間能夠執行40萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間, 這簡直是場災難性能
考慮到磁盤IO是很是高昂的操做,計算機操做系統作了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於局部預讀性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據咱們稱之爲一頁(page)。具體一頁有多大數據跟操做系統有關,通常爲4k或8k,也就是咱們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計很是有幫助
索引的數據結構是 B+樹, 而 B+樹 是通過 二叉排序樹 到 二叉平衡樹 再到 B樹 最後到 B+樹 演變過來的, 下面簡單介紹一下:
對於一列數字 : 五、六、七、八、九、10
利用二叉排序樹咱們只須要3次便可找到匹配的數據; 若是在數字列中一條條的查找的話,咱們須要5次才能找到
上面咱們講解了利用二叉排序樹能夠快速的找到數據; 可是,若是上面的二叉排序樹是這樣的構造:
平均查找長度是3, 若是咱們調整一下關鍵字的序列
調整以後平均查找長度是 2.2, 從上面咱們能夠看出平均查找長度與數的高度有關, 平均查找長度越小, 查找速度就越快, 因此咱們應該儘量的讓這棵樹矮
這裏引入了平衡因子的概念, 左子樹的高度減右子數的高度就是平衡因子, 平衡因子的絕對值小於或等於一就是平衡二叉樹, 大於一就是非平衡二叉樹, 以下圖平衡因子爲 4 就是非平衡二叉樹
咱們調整一下關鍵字序列, 各子數平衡因子絕對值都小於或等於 1, 那麼這就是一顆平衡二叉樹
若是咱們要存儲海量的數據呢?能夠想象到二叉樹的節點將會很是多,高度也會及其高,咱們查找數據時也會進行不少次磁盤IO,咱們查找數據的效率將會極低
從上圖能夠看出,B樹相對於平衡二叉樹,每一個節點(B樹中節點稱之爲頁)存儲了更多的鍵值(key)和數據(data),而且每一個節點擁有更多的子節點,子節點的個數通常稱爲階,上述圖中的B樹爲3階B樹,高度也會很低。 基於這個特性,B樹查找數據讀取磁盤的次數將會不多,數據的查找效率也會比平衡二叉樹高不少
假設每一個節點能夠儲存兩個值(不表明只能存兩個), 咱們找到75:
- 先與 頁1 比較,在 35 右邊找到 p3指針 定位到 頁4
- 與 頁4 中的索引對比, 在 65-87 之間, 找到指針 p2, 定位到 頁10
- 與 頁10 中的索引對比, 找到相對應的 75
B+ 樹非葉子節點上是不存儲數據的,僅存儲鍵值,而 B 樹節點中不只存儲鍵值,也會存儲數據
之因此這麼作是由於在數據庫中頁的大小是固定的,InnoDB 中頁的默認大小是 16KB。若是不存儲數據,那麼就會存儲更多的鍵值,相應的樹的階數(節點的子節點樹)就會更大,樹就會更矮更胖,如此一來咱們查找數據進行磁盤的 IO 次數又會再次減小,數據查詢的效率也會更快
B+ 樹的階數是等於鍵值的數量的,若是咱們的 B+ 樹一個節點能夠存儲 1000 個鍵值,那麼 3 層 B+ 樹能夠存儲 1000×1000×1000=10 億個數據。
通常根節點是常駐內存的,因此通常咱們查找 10 億數據,只須要 2 次磁盤 IO
3層的b+樹能夠表示上百萬的數據,若是上百萬的數據查找只須要兩次IO,性能提升將是巨大的,若是沒有索引,每一個數據項都要發生一次IO,那麼總共須要百萬次的IO,顯然成本很是很是高
- 索引字段要儘可能的小 : 磁盤塊的大小也就是一個數據頁的大小,是固定的. 若是數據項佔的空間越小,數據項的數量越多,樹的高度就越低, 查詢過的IO次數就越少. 這就是爲何每一個數據項,即索引字段要儘可能的小,好比int佔4字節,要比bigint8字節少一半。這也是爲何b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度降低, 降低則會致使每層可存儲的數據就少, 由於磁盤塊是固定的, 從而要增長層次, 進而致使樹增高, 樹增高意味着找到底層數據的IO次數增多, 致使查詢速度大幅度降低
- 索引的最左匹配特性 : 當b+樹的數據項是複合的數據結構,好比(name,age,sex)的時候,b+數是按照從左到右的順序來創建搜索樹的. 好比當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來肯定下一步的所搜方向,若是name相同再依次比較age和sex,最後獲得檢索的數據. 但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪一個節點,由於創建搜索樹的時候name就是第一個比較因子,必需要先根據name來搜索才能知道下一步去哪裏查詢。 好比當(張三,F)這樣的數據來檢索時,b+樹能夠用name來指定搜索方向,但下一個字段age的缺失,因此只能把名字等於張三的數據都找到,而後再匹配性別是F的數據了, 這個是很是重要的性質,即索引的最左匹配特性
數據庫中的 B+樹 索引能夠分爲彙集索引(clustered index)和輔助索引(secondary index), 彙集索引與輔助索引相同的是:無論是彙集索引仍是輔助索引,其內部都是B+樹的形式,即高度是平衡的, 不一樣的是 :
彙集索引的葉子節點存放的是一整行完整的信息, 而輔助索引的葉子節點存放的並不是完整信息(下面介紹)
InnoDB 彙集索引的葉子節點存儲行記錄,所以 InnoDB 必需要有且只有一個彙集索引
若是表定義了 PK (Primary Key,主鍵),那麼 PK 就是彙集索引
若是表沒有定義 PK,則第一個不爲空且惟一(NOT NULL UNIQUE) 的列就是彙集索引
不然 InnoDB 會另外建立一個隱藏的 ROWID 做爲彙集索引
因爲這種機制是直接定位行記錄,所以使得基於 PK 的查詢速度很是快
表中除了彙集索引外其餘索引都是輔助索引(Secondary Index,也稱爲非彙集索引)
與彙集索引的區別是:輔助索引的葉子節點不包含行記錄的所有數據。葉子節點除了包含鍵值之外,每一個葉子節點中的索引行中還包含一個書籤(bookmark)。該書籤用來告訴InnoDB存儲引擎去哪裏能夠找到與索引相對應的行數據
輔助索引的存在並不影響數據在彙集索引中的組織,所以每張表上能夠有多個輔助索引,但只能有一個彙集索引
當經過輔助索引來尋找數據時,InnoDB存儲引擎會遍歷輔助索引並經過葉子級別的指針得到指向主鍵索引的主鍵,而後再經過主鍵索引來找到一個完整的行記錄
上面的三種索引, 惟一索引除了能夠增長查詢速度以外各自還具備約束條件, 而普通索引index key沒有任何的約束條件,只是用來幫助你加快速查詢數據
注意:聯合索引不是用來加速查詢用的,不在咱們的而研究範圍以內
hash類型的索引:查詢單條快,範圍查詢慢 btree類型的索引:b+樹,層數越多,數據量指數級增加(咱們就用它,由於innodb默認支持它)
InnoDB 支持事務,支持行級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引 MyISAM 不支持事務,支持表級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引 Memory 不支持事務,支持表級別鎖定,支持 B-tree、Hash 等索引,不支持 Full-text 索引 NDB 支持事務,支持行級別鎖定,支持 Hash 索引,不支持 B-tree、Full-text 等索引 Archive 不支持事務,支持表級別鎖定,不支持 B-tree、Hash、Full-text 等索引
🍓方式一 : 建立表時建索引 create table [表名] ( [unique|fulltext|spatial] [index|key] [索引名] [字段名(長度)] [asc|desc] ); 🍓方式二 : 在已存在的表上建立 create [unique|fulltext|spatial] index [索引名] on [表名] [字段名(長度)] [asc|desc]; 🍓方式二 : alter 在已存在的表上建立索引 alter table [表名] add [unique|fulltext|spatial] index [索引名] [字段名(長度)] [asc|desc];
🍓方式一 create table t01( id int, name char(10), age int, sex enum("male","female"), unique key unique_id(id), index index_name(name) # index沒有key ); 🍓方式二 create index index_age on t01(age); 🍓方式三 alter table t01 add index index_sex(sex);
drop index [索引名] on t01; # 語法 drop index index_age on t01; # 示例
🍓建立表 create table t01( id int, name varchar(10), sex enum("male","female"), email varchar(18) ); 🍓建立存儲過程,進行自動插入記錄 delimiter %%% create procedure p01() begin declare i int default 1; while(i<3000000)do insert t01 value(i,"shawn","male",concat("shawn",i,"@163.com")); set i=i+1; end while; end %%% delimiter ; 🍓查看存儲過程 show create procedure p01\G # \G 垂直顯示結果 🍓調用存儲過程 call p01(); # windows執行測試大概一個半小時,3百萬條記錄,200多M 🍓刪除存儲過程 drop procedure p01;
select * from t01 where id=3000000;
沒有索引,mysql不知道有沒有這條記錄, 因此從頭至尾的對記錄進行遍歷,有多少磁盤塊就要進行多少I\O,速度很慢
create index index_id on t01(id); # 爲 id 字段創建普通索引
觀察 data 文件夾下的 t01 表數據文件大小增長了
select * from t01 where id=3000000; # 能夠觀察到速度明顯的提高
mysql先去索引表裏根據 b+樹 的搜索原理很快搜索到 id 等於3000000的記錄,直接命中索引, IO大大下降,於是速度明顯提高
咱們以沒有創建索引的字段設置爲條件來進行查詢, 能夠發現速度依然很慢
select * from t01 email="shawn3000000@163.com"; # 而且記錄越大查詢越慢
對 email 字段創建索引試試
create index index_email on t01(email); # 字段數據越大,創建的時間越長(因此建議不要使用數據很大的字段創建索引,這裏只是作實驗) select * from t01 where email="shawn3000000@163.com"; # 再去查詢,能夠發現速度是數量級的提高
並非說建立了索引就必定能加速查詢, 有些狀況就算命中了索引也未必能起到很好的提速效果, 下面來測試一下各類狀況 (若是不想看過程,能夠直接看小結末尾的結論)
出現上面的狀況就是由於字段的區分度過低, 在 B+樹 中對於這些字段沒法比較大小, 由於值都是相等的, 毫無疑問,只能增長樹的高度來保證這些數據的存儲, 樹的高度越高, 查詢速度就越慢
🍓"and"與"or"的邏輯 [條件1] and [條件2] : 全部條件都成立纔算成立,但凡要有一個條件不成立則最終結果不成立 [條件1] or [條件2] : 只要有一個條件成立則最終結果就成立 🍓"and"的工做原理 條件: a = 10 and b = 'xxx' and c > 3 and d =4 索引: 製做聯合索引(d,a,b,c) 工做原理: 對於連續多個and:mysql會按照聯合索引,從左到右的順序找一個區分度高的索引字段(這樣即可以快速鎖定很小的範圍),加速查詢,即按照d—>a->b->c的順序 🍓"or"的工做牌原理 條件: a = 10 or b = 'xxx' or c > 3 or d =4 索引: 製做聯合索引(d,a,b,c) 工做原理: 對於連續多個or:mysql會按照條件的順序,從左到右依次判斷,即a->b->c->d
create table user( -> id int not null auto_increment, -> name char(16) not null, -> age int not null, -> primary key(id), # id 爲主鍵並設置索引(彙集索引) -> index index_name(name)); # name 字段設置索引(輔助索引) insert user(name,age) value -> ("shawn",23), -> ("song",22), -> ("hai",20), -> ("xing",18), -> ("yanxi",45), -> ("zichen",25);
select * from user where id=2;
上面爲主鍵查詢方式, 即經過彙集索引, 能找到 id 爲 2 的完整記錄
select * from user where name="song";
上面爲輔助索引查詢方式, 則須要先搜索 name 索引樹,獲得 song 對應的 id 值爲 2,再到 id 索引樹搜索一次, 這個過程稱爲回表
select id from user where name="hai";
上面語句查詢的條件是 name 字段, name 字段有索引樹, 而且上面保存有 name 和 id 的值, 能夠直接提供查詢結果, 不須要進行回表操做, 也就是說, 在這個查詢裏面, 索引 name 已經覆蓋了咱們所要查詢的 id 字段需求, 這就稱爲覆蓋索引
select age from user where name="xing";
上面語句經過 name 索引樹找到 name 字段對應的 "xing" 和 id 值, 但沒有 age 字段信息, 因而經過 id 字段進行回表操做查找到知足條件的數據
🔰聯合索引是指對錶上的多個列合起來作一個索引. 聯合索引的建立方法與單個索引的建立方法同樣,不一樣之處在僅在於有多個索引列
🔰最左前綴匹配原則, 是很是重要的原則, mysql會從左到右進行匹配
drop index index_name on user; create index index_name_age on user(name,age); # 實際應用中應該把最經常使用的字段放在最左邊
select name,age from user where name="song"; # 條件字段 name select name,age from user where name="song" and age>18; # 條件字段 name + age
select name,age from user where age=2; # 條件字段 age (不會走聯合索引)
索引下推(index condition pushdown )簡稱ICP,在Mysql5.6的版本上推出,用於優化查詢
使用最左前綴匹配原則 + 聯合查詢能夠加快查詢速度, 若是咱們的條件存在範圍查詢, 那麼 SQL 語句是怎麼運行的呢?
select * from user where name like "s%" and age=22;
如上表的記錄, "s" 開頭的記錄有兩條
Innodb 會忽略 age 這個字段, 直接經過 name 來進行查詢, 在(name,age)這個聯合索引上找到兩條結果, 而後拿到 id 爲 1 和 2 進行"兩次回表查詢"
Innodb 不會忽略 age 這個字段, 而是在索引內部就判斷了 age 是否等於 22, 不等於 22 的記錄直接跳過 所以在(name,age)這個聯合索引上只匹配到一個記錄, 此時拿着這一個 id 去回表到全部的數據只須要"回表一次"
官方文檔 : https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
ps : 強調 rows 是核心指標,絕大部分 rows 小的語句執行必定很快。因此優化語句基本上都是在優化rows
explain 簡稱查看執行計劃,使用 explain 關鍵字能夠模擬優化器執行SQL查詢語句,從而知道MySQL 是如何處理 SQL 語句的
語法 : explain + [SQL語句]
explain select * from t01;
字段 | 說明 |
---|---|
id | MySQL Query Optimizer 選定的執行計劃中查詢的序列號。表示查詢中執行 select 子句或操做表的順序,id值越大優先級越高,越先被執行; 若id 相同,執行順序由上至下 |
select_type 查詢類型 | 說明 |
---|---|
SIMPLE | 簡單的 select 查詢,不使用 union 及子查詢 |
PRIMARY | 最外層的 select 查詢 |
UNION | UNION 中的第二個或隨後的 select 查詢,不 依賴於外部查詢的結果集 |
DEPENDENT UNION | UNION 中的第二個或隨後的 select 查詢,依 賴於外部查詢的結果集 |
SUBQUERY | 子查詢中的第一個 select 查詢,不依賴於外 部查詢的結果集 |
DEPENDENT SUBQUERY | 子查詢中的第一個 select 查詢,依賴於外部 查詢的結果集 |
DERIVED | 用於 from 子句裏有子查詢的狀況。 MySQL 會 遞歸執行這些子查詢, 把結果放在臨時表裏 |
UNCACHEABLE SUBQUERY | 結果集不能被緩存的子查詢,必須從新爲外 層查詢的每一行進行評估 |
UNCACHEABLE UNION | UNION 中的第二個或隨後的 select 查詢,屬 於不可緩存的子查詢 |
字段 | 說明 |
---|---|
table | 輸出行所引用的表 |
很是重要的項, 顯示鏈接使用的類型, 按最優到最差的類型排序
type : 鏈接類型 | 說明 |
---|---|
system | 表僅有一行(=系統表)。這是 const 鏈接類型的一個特例 |
const | const 用於用常數值比較 PRIMARY KEY 時。當 查詢的表僅有一行時,使用 System |
eq_ref | const 用於用常數值比較 PRIMARY KEY 時。當 查詢的表僅有一行時,使用 System |
ref | 鏈接不能基於關鍵字選擇單個行,可能查找 到多個符合條件的行。 叫作 ref 是由於索引要 跟某個參考值相比較。這個參考值或者是一 個常數,或者是來自一個表裏的多表查詢的 結果值 |
ref_or_null | 如同 ref, 可是 MySQL 必須在初次查找的結果 裏找出 null 條目,而後進行二次查找。 |
index_merge | 說明索引合併優化被使用了 |
unique_subquery | 在某些 IN 查詢中使用此種類型,而不是常規的 ref:value IN (SELECT primary_key FROM single_table WHERE some_expr) |
index_subquery | 在 某 些 IN 查 詢 中 使 用 此 種 類 型 , 與 unique_subquery 相似,可是查詢的是非惟一 性索引: value IN (SELECT key_column FROM single_table WHERE some_expr) |
range | 只檢索給定範圍的行,使用一個索引來選擇 行。key 列顯示使用了哪一個索引。當使用=、 <>、>、>=、<、<=、IS NULL、<=>、BETWEEN 或者 IN 操做符,用常量比較關鍵字列時,可 以使用 range |
index | 全表掃描,只是掃描表的時候按照索引次序 進行而不是行。主要優勢就是避免了排序, 可是開銷仍然很是大 |
all | 最壞的狀況,從頭至尾全表掃描 |
字段 | 說明 |
---|---|
possible_keys | 指出 MySQL 能在該表中使用哪些索引有助於 查詢。若是爲空,說明沒有可用的索引 |
字段 | 說明 |
---|---|
key | MySQL 實際從 possible_key 選擇使用的索引。 若是爲 NULL,則沒有使用索引。不多的狀況 下,MYSQL 會選擇優化不足的索引。這種情 況下,能夠在 SELECT 語句中使用 USE INDEX (indexname)來強制使用一個索引或者用 IGNORE INDEX(indexname)來強制 MYSQL 忽略索引 |
字段 | 說明 |
---|---|
key_len | 使用的索引的長度。在不損失精確性的狀況 下,長度越短越好。 |
字段 | 說明 |
---|---|
ref | 顯示索引的哪一列被使用了 |
字段 | 說明 |
---|---|
rows | MYSQL 認爲必須檢查的用來返回請求數據的行數 |
extra 項 | 說明 |
---|---|
Using filesort | 表示 MySQL 會對結果使用一個外部索引排序,而不是從表裏按索引次序讀到相關內容。可能在內存或者磁盤上進行排序。MySQL 中沒法利用索引完成的排序操做稱爲「文件排序」 |
Using temporary | 表示 MySQL 在對查詢結果排序時使用臨時表。常見於排序 order by 和分組查詢 group by |
explain select * from t01 where id=100000; explain select * from t01 where id>10000 and id<20000; explain select * from t01 where id>20000;