1、Mysql的執行計划
1.1、為什么需要執行計划?
有的sql語句執行效率高,有的執行效率低,需要對sql語句做調整和優化,所以就會涉及到執行計划
1.2、執行計划是什么?
執行計划具體來說就是一條sql語句的執行過程
可以看到執行過程中用到了哪些關鍵的信息,並根據這些信息做判斷
1.3、如何使用執行計划?
就是在sql語句前面加上關鍵字explain,在sql語句前面加上explain之后,它會輸出n多個列
1.4、案例
1)數據表
testnd5
2)執行計划
explain SELECT * FROM testnd5
3)執行結果
4)結果分析
-
id
當sql語句非常復雜的時候,會有一個id序號的排列,根據序號的排列能顯示出來哪個子查詢或者子句優先執行,哪個字句后執行,僅此而已,有時候需要看,有時候不需要看,它不是一個關鍵信息
-
select_type
查詢的類型(簡單查詢、聯合查詢、子查詢),一般沒什么用
-
table
sql語句執行的表的名稱
-
type
很重要,表示查詢對應的類型,mysql默認的是ALL
有這幾種
- system
- const
- ref
- range
- index
- all
從前往后,效率依次降低,即system的效率是最高的,all要進行全表掃描,效率低,所以我們最少要保證type在range這個級別(通過加索引、調整當前子句…),達到ref更好但是某些情況是沒法優化的,優化知識為了在一定程度上解決問題,並不是一定有解。可以優化但並不是優化完一定有效果
官方文檔地址
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain_type有的sql語句非常簡單,sql語句越簡單,優化的程度就越低
-
possible_keys
可能用到的索引
把可能用到的索引都列出來,意義不大
-
key
很重要,表示當前的sql語句中到底有沒有用到索引,這個值盡量不要為空
-
rows
過濾的行數,只是預估值,不是精確值
-
Extra
比較重要,表示額外的信息
當出現的是using index:表示使用了索引覆蓋
using index condition表示使用了索引下推
using filesort表示使用了臨時空間進行排序,沒有使用索引進行排序
2、索引介紹
2.1、什么是索引?
- 索引是數據結構,可以高效地獲取數據
- 索引存儲在文件系統中
- 索引的文件存儲形式與存儲引擎有關
- 索引文件結構
- hash
- 二叉樹
- B樹
- B+樹 (MySql索引文件結構)
2.2、索引分類
-
主鍵索引
主鍵是一種唯一性索引,它必須指定為PRIMARY KEY,不能為空,每個表只能有一個主鍵
一個主鍵並非一定只有一個列,也可以是多個列組成的聯合主鍵
MySql會自動為主鍵創建索引
-
唯一索引
索引列的所有值都只能出現一次,即必須唯一,值可以為空;一張表可以在不同的字段建多個唯一索引
-
普通索引
基本的索引類型,值可以為空,沒有唯一性的限制
-
全文索引
全文索引的索引類型為FULLTEXT。全文索引可以在varchar、char、text類型的列上創建
-
組合索引
多列值組成一個索引,專門用於組合搜索
又稱:復合索引、聯合索引
3、索引 增刪查改
3.1、增加(創建)
- ALTER TABLE
推薦
適用於表創建完畢之后再添加
alter [ˈɔːltər] 修改、更改
ALTER TABLE 表名 ADD 索引類型 (unique,primary key,fulltext,index) [索引名](字段名)
-- 索引名,可要可不要;如果不要,當前的索引名就是該字段名
ALTER TABLE `table_name` ADD INDEX `index_name` (`column_list`)
ALTER TABLE `table_name` ADD UNIQUE (`column_list`)
ALTER TABLE `table_name` ADD PRIMARY KEY (`column_list`)
ALTER TABLE `table_name` ADD FULLTEXT KEY (`column_list`)
- CREATE INDEX
適用於表創建完畢之后再添加
CREATE INDEX 可對表增加 普通索引 或 UNIQUE索引
-- 只能添加 普通索引 或 UNIQUE索引
CREATE INDEX index_name ON table_name (column_list)
CREATE UNIQUE INDEX index_name ON table_name (column_list)
- 建表時添加
不推薦
CREATE TABLE `test1` (
`id` smallint(5) UNSIGNED AUTO_INCREMENT NOT NULL, -- 注意,下面創建了主鍵索引,這里就不用創建了
`username` varchar(64) NOT NULL COMMENT '用戶名',
`nickname` varchar(50) NOT NULL COMMENT '昵稱/姓名',
`intro` text,
PRIMARY KEY (`id`),
UNIQUE KEY `unique1` (`username`), -- 索引名稱,可要可不要,不要就是和列名一樣
KEY `index1` (`nickname`),
FULLTEXT KEY `intro` (`intro`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='后台用戶表';
3.2、刪除
DROP INDEX `index_name` ON `talbe_name`
ALTER TABLE `table_name` DROP INDEX `index_name`
-- 這兩句都是等價的,都是刪除掉 table_name 中的索引 index_name
ALTER TABLE `table_name` DROP PRIMARY KEY -- 刪除主鍵索引,注意主鍵索引只能用這種方式刪除
3.3、查看
show index from `table_name`;
3.4、更改
刪掉重建一個既可
3.5、創建索引的技巧
- 維度高的列創建索引
-
- 數據列中不重復值出現的個數,這個數量越高,維度就越高。
- 如數據表中存在8行數據a,b,c,d,a,b,c,d這個表的維度為4。
- 要為維度高的列創建索引,如性別和年齡,那年齡的維度就高於性別。
- 性別這樣的列不適合創建索引,因為維度過低。
- 對 where,on,group by,order by 中出現的列使用索引。
- 對較小的數據列使用索引,這樣會使索引文件更小,同時內存中也可以裝載更多的索引鍵
- 為較長的字符串使用前綴索引。
- 不要過多創建索引,除了增加額外的磁盤空間外,對於DML(數據庫管理語言,如增、刪、改)操作的速度影響很大,因為其每增刪改一次就得重新建立索引。
- 使用組合索引,可以減少文件索引大小,在使用時速度要優於多個單列索引
- 更新頻繁,數據區分度不高的字段,不宜建立索引
4、存儲引擎
MyIsam、InnoDB、Memory
Memory 底層數據結構為哈希表
比較 | MYISAM | INNODB(默認) |
---|---|---|
索引類型 | 非聚簇索引 數據和索引不在一起存儲 (只存地址) |
聚簇索引 數據和索引在一起存儲 |
事務 | 不支持 | 支持 |
數據表鎖定 | 支持 | 支持 |
數據行鎖定 | 不支持 | 支持 |
外鍵約束 | 不支持 | 支持 |
全文檢索 | 支持 | 支持(5.6及以后) |
所占空間 | 小 | 大,約2倍 |
底層數據結構 | B + 樹 | B + 樹 |
適合操作類型 | 大量select | 大量insert、delete、update |
- 聚簇索引
- 數據跟索引存儲在一起
- 非聚簇索引
- 數據跟索引不存儲在一起
5、索引 技術點
索引系統設計要點
1)索引應該存哪些信息
2)索引和數據存儲位置
對於InnoDB
索引和實際的數據都是存儲在磁盤上的,只不過在進行數據讀取的時候會優先把索引加載到內存中
存儲引擎:不同的數據文件在磁盤中有不同的組織形式
-
MyIsam存儲引擎
.frm
表結構.MYD
數據文件.MYI
索引 -
InnoDb存儲引擎
.frm
表結構.ibd
索引文件+數據文件
3)索引存儲什么格式的數據?
K-V格式(鍵-值對)
類似於查字典,根據頁數定位要查找的內容
4)選擇合理的數據結構進行存儲
為什么是B+數(為什么不是B樹或者hash表)
當表非常大的時候,索引會不會一起變大?
因為往表里存數據的時候,是沒法判斷這個表能夠存多少數據的,表中數據量在增大的時候,索引也在增大
索引在變大的過程中,沒辦法直接加載到內存怎么辦?
可能內存只有8G,但是mysql數據的索引達到了16G,則可以分塊讀取,1G 1G地讀,分而治之
盡可能多地提高IO效率
1:減少IO量
2:減少IO次數
6、操作系統基礎知識
6.1、局部性原理
-
時間局部性
之前被訪問過的數據很有可能再次被訪問
-
空間局部性
數據和程序都有聚集成群的傾向
6.2、磁盤預讀
內存跟磁盤再進行交互的時候有一個最小的邏輯單位,這個單位稱之為頁,或者datapage,一般是4kb或者8kb,由操作系統決定,我們在進行數據讀取的時候,一般是讀取頁的整數倍,也就是4k,8k,16k;Innodb存儲引擎在進行數據讀取的時候讀取的是16kb的數據
舉例:如下圖,我們可以看到,實際的文件大小是758字節,但是占用了4kb的大小(可以把磁盤看成一個一個的小格子,每一個格子都是4kb的大小,不管你有沒有占滿,都是4kb)
7、MySql 為什么選擇B+樹?
7.1、hash表
缺點:
- hash存儲需要將所有的數據文件添加到內存,浪費內存空間
- 如果是等值查詢,hash很快;但實際工作中范圍(range)查找的更多,而不是等值查詢,所以hash就不合適了
- 哈希索引
簡要說下,類似於數據結構中簡單實現的HASH表(散列表)一樣,當我們在mysql中用哈希索引時,也是對索引列計算一個散列值(類似md5、sha1、crc32),然后對這個散列值以順序(默認升序)排列,同時記錄該散列值對應數據表中某行的指針,當然這只是簡略模擬圖
比如對姓名列建立hash索引,生成hash值按順序排列,但是順序排列的hash值並不對應表中記錄,從地址指針可反應出來,而且,hash索引可能建立在兩列或者更多列上,取得是多列數據后的hash值,它不存儲表中數據。它先計算列數據的hash值,與索引中的hash值比較,找到了然后比對列數據是否相等,可能涉及其他列條件,然后返回數據。hash當然會有沖突,即碰撞,除非有很多沖突,一般hash索引效率很高,否則hash維護成本較高,因此哈希索引通常用在選擇性較高的列上面。哈希索引的結構決定了它的特點:
1. hash索引只是hash值順序排列,跟表數據沒有關系,無法應用於order by;
2. hash索引是對它的所有列計算哈希值,因此在查詢時,必須帶上所有列,比如有(a, b)哈希索引,查詢時必須 where a = 1 and b = 2,少任何一個不行;
3. hash索引只能用於比較查詢 = 或 IN,其他范圍查詢無效,本質還是因不存儲表數據;
4. 一旦出現碰撞,hash索引必須遍歷所有的hash值,將地址所指向數據一一比較,直到找到所有符合條件的行。
7.2、二叉樹
缺點:
- 會因為樹的深度過深而造成IO次數變多,影響讀取效率
7.3、B樹
缺點:
- 會因為樹的深度過深而造成IO次數變多,影響讀取效率
7.4、B+樹
- B+Tree每個節點可以包含更多的節點,這個做的原因有兩個:
- 為了降低樹的高度,減少IO次數
- 將數據范圍變為多個區間,區間越多,數據檢索越快
- 非葉子節點存儲key,葉子節點存儲key和數據
- 葉子節點兩兩指針相互連接(符合磁盤的預讀特性),順序查詢性能更高
- 在B+Tree上有兩個頭指針,一個指向根節點,另一個指向關鍵字最小的葉子節點,而且所有葉子節點(即數據節點)之間是一種鏈式環結構。因此可以對B+Tree進行兩種查找運算:
- 對於主鍵的范圍查找和分頁查找
- 從根節點開始,進行隨機查找
- 一般情況下3-4層的B+樹足以支撐千萬級的數據量存儲
Innodb是通過B+Tree結構對主鍵創建索引,然后葉子節點中存儲記錄,如果沒有主鍵,選擇唯一鍵,如果沒有唯一鍵,選擇6字節的row_id來進行存儲
如果創建的索引是其它字段(不是主鍵),那么在葉子節點中存儲的是該記錄的主鍵(不是數據),然后再通過主鍵索引查找對應的記錄
MyIsam 葉子節點中只存儲地址,不存具體記錄數據,通過地址再查找數據
8、索引優化
8.1、常見名詞
案例表:
id(主鍵) name(普通索引) age 1 張三 22 2 李四 25 3 王五 28
1)回表
如果創建的索引是其它字段(普通索引,不是主鍵),那么在葉子節點中存儲的是該記錄的主鍵(不是數據),然后再通過主鍵去 主鍵索引表 查找對應的記錄
經過了兩個表的查詢:
- 普通索引表:查到主鍵
- 主鍵索引表:查詢記錄數據
-- name是普通索引,在普通索引表中存儲的是主鍵id的值,即1 -- 要想得到記錄的值,需經過二次查詢 select * from table where name='張三'
2)索引覆蓋
-- 一次查詢就可以得到結果,不需要回表 select id from table where name='張三'
- 根據name的值去name的B+樹檢索對應的記錄,能獲取到id的屬性值,索引的葉子結點中包含了查詢的所有列,此時不需要回表,這個額過程叫做索引覆蓋,會有using index的提示,推薦使用
- 在某些場景中,可以考慮將要查詢的所有列都變成組合索引 ,此時會使用索引覆蓋(不會回表),加快查詢效率。
3)最左匹配
-- id主鍵 (name,age)組合索引/聯合索引/復合索引 -- 會用組合索引 select * from user where name = '張三' and age = '22'; -- 會用組合索引 select * from user where name = '張三'; -- 不會用組合索引,因為沒有 name select * from user where age = '22'; -- 會用組合索引,因為 age 和 name 都有,優化器會調整 age 和 name 的順序 select * from user where age = '22' and name = '張三';
對於復合索引來說,不總是匹配所有字段列,但是可以匹配索引中靠左的列
4)索引下推
- 把原來在server層進行的條件過濾下推到存儲引擎層,索引下推是默認開啟的
select * from user where name = '張三' and age = '22';
沒有索引下推前
先根據 name 從存儲引擎中拉取數據到 server 層,然后在 server 層中對 age 進行數據過濾
有索引下推后
根據 name 和 age,直接在存儲引擎中做數據過濾,把結果返給 server 層
可以減少返給 server 層的數據量
5)前綴索引
如果索引列長度過長,這種列索引時將會產生很大的索引文件,不便於操作,可以使用前綴索引方式進行索引;前綴索引應該控制在一個合適的點,控制在0.31黃金值即可(大於這個值就可以創建)。
-- 這個值大於0.31就可以創建前綴索引,Distinct去重復
SELECT COUNT(DISTINCT(LEFT(title,10)))/COUNT(*) FROM Arctic;
-- 增加前綴索引SQL,將人名的索引建立在10,這樣可以減少索引文件大小,加快索引查詢速度
ALTER TABLE user ADD INDEX `uname`(title(10));
8.2、優化小細節
-
當使用索引列進行查詢的時候,盡量不要使用表達式,把計算邏輯放到業務層,減輕數據庫運算壓力
-
盡量使用主鍵查詢,而不是其它索引,因為主鍵索引不會觸發回表查詢
-
如果 索引列 長度過長,可以使用前綴索引,否則會產生很大的索引文件,不便於操作
-
使用索引掃描來排序
-
mysql有兩種方式可以生成有序的結果:通過 排序操作 或者 按索引順序掃描
- 如果explain出來的type列的值為index,則說明mysql使用了索引掃描來做排序
- 掃描索引本身是很快的,因為只需要從一條索引記錄移動到緊接着的下一條記錄
- 但如果索引不能覆蓋查詢所需的全部列,那么就不得不每掃描一條索引記錄就得回表查詢一次對應的行
- 這基本都是隨機IO,因此按索引順序讀取數據的速度通常要比順序地全表掃描慢
mysql可以使用同一個索引即滿足排序,又用於查找行,如果可能的話,設計索引時應該盡可能地同時滿足這兩種任務
- 只有當索引的列順序和order by子句的順序完全一致,並且所有列的排序方式都一樣時,mysql才能夠使用索引來對結果進行排序
- 如果查詢需要關聯多張表,則只有當order by子句引用的字段全部為第一張表時,才能使用索引做排序
- order by子句和查找型查詢的限制是一樣的,需要滿足索引的最左前綴的要求
否則,mysql都需要執行順序操作,而無法利用索引排序
-
-
union all,in,or 都可以使用索引
-
對於 索引列 最好使用 union all
因為復雜的查詢【包含運算等】將使 or、in 放棄索引而全表掃描,除非確定 or、in 會使用索引
-
對於 非索引字段 用 or 或者 in,因為要全表掃描,而 union all 會成倍增加表掃描的次數
-
對於既有 索引字段【索引字段有效】又包含 非索引字段,使用三者都可以,推薦使用 or、in
參考:
-
-
范圍列可以用到索引
- 范圍條件是:<、<=、>、>=、between
- 范圍列可以用到索引,但是范圍列后面的列無法用到索引,索引最多用於一個范圍列
-
強制類型轉換會全表掃描
-- 數據庫中的電話號碼是 字符串形式(varchar),並建立了索引 -- 全表掃描,不會觸發索引;因為以 數值 的形式查詢,強制進行了類型轉換 explain select * from user where phone = 13800001234; -- 觸發了索引,不會全表掃描 explain select * from user where phone = '13800001234';
-
更新頻繁,數據區分度不高的字段,不宜建立索引
-
創建索引的列,不允許為null,可能會得到不符合預期的結果
-
當需要進行表連接的時候,最好不要超過三張表,因為需要join的字段,數據類型必須一致
-
能使用 limit 的時候,盡量使用 limit
-
單表索引建議控制在5個以內
-
組合索引中單索引字段在5個以內
-
索引的正確概念:
- 索引不是越多越好;索引也是要維護的,過多時會降低性能
- 在不了解系統的情況下,不要過早優化
參考:
博客園:https://www.cnblogs.com/yyjie/p/7486975.html
博客園:https://www.cnblogs.com/itdragon/p/8146439.html
菜鳥教程:https://www.runoob.com/w3cnote/mysql-index.html
知乎:https://zhuanlan.zhihu.com/p/197884014
感謝!