先建好數據庫表,演示用的MySQL表,建表語句:html
CREATE TABLE `emp` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`empno` int(11) DEFAULT NULL COMMENT '僱員工號',
`ename` varchar(255) DEFAULT NULL COMMENT '僱員姓名',
`job` varchar(255) DEFAULT NULL COMMENT '工做',
`mgr` varchar(255) DEFAULT NULL COMMENT '經理的工號',
`hiredate` date DEFAULT NULL COMMENT '僱用日期',
`sal` double DEFAULT NULL COMMENT '工資',
`comm` double DEFAULT NULL COMMENT '津貼',
`deptno` int(11) DEFAULT NULL COMMENT '所屬部門號',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='僱員表';
CREATE TABLE `dept` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`deptno` int(11) DEFAULT NULL COMMENT '部門號',
`dname` varchar(255) DEFAULT NULL COMMENT '部門名稱',
`loc` varchar(255) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='部門表';
CREATE TABLE `salgrade` (
`id` int(11) NOT NULL COMMENT '主鍵',
`grade` varchar(255) DEFAULT NULL COMMENT '等級',
`lowsal` varchar(255) DEFAULT NULL COMMENT '最低工資',
`hisal` varchar(255) DEFAULT NULL COMMENT '最高工資',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='工資等級表';
CREATE TABLE `bonus` (
`id` int(11) NOT NULL COMMENT '主鍵',
`ename` varchar(255) DEFAULT NULL COMMENT '僱員姓名',
`job` varchar(255) DEFAULT NULL COMMENT '工做',
`sal` double DEFAULT NULL COMMENT '工資',
`comm` double DEFAULT NULL COMMENT '津貼',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='獎金錶';
複製代碼
後續執行計劃,查詢優化,索引優化等等知識的演練,基於以上幾個表來操做。mysql
要進行SQL調優,你得知道要調優的SQL語句是怎麼執行的,查看SQL語句的具體執行過程,以加快SQL語句的執行效率。git
可使用explain + SQL
語句來模擬優化器執行SQL查詢語句,從而知道MySQL是如何處理SQL語句的。github
關於explain
能夠看看官網介紹。算法
mysql> explain select * from emp;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
複製代碼
字段id
,select_type
等字段的解釋:sql
Column | Meaning |
---|---|
id | The SELECT identifier(該SELECT標識符) |
select_type | The SELECT type( 該SELECT類型) |
table | The table for the output row(輸出該行的表名) |
partitions | The matching partitions(匹配的分區) |
type | The join type(鏈接類型) |
possible_keys | The possible indexes to choose(可能的索引選擇) |
key | The index actually chosen(實際選擇的索引) |
key_len | The length of the chosen key(所選鍵的長度) |
ref | The columns compared to the index(與索引比較的列) |
rows | Estimate of rows to be examined(檢查的預估行數) |
filtered | Percentage of rows filtered by table condition(按表條件過濾的行百分比) |
extra | Additional information(附加信息) |
id
數據庫
select查詢的序列號,包含一組數字,表示查詢中執行select子句或者操做表的順序。緩存
id
號分爲三類:服務器
mysql> explain select * from emp e join dept d on e.deptno = d.deptno join salgrade sg on e.sal between sg.lowsal and sg.hisal;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | e | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 1 | SIMPLE | d | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | sg | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
複製代碼
這個查詢,用explain執行一下,
id
序號都是1,那麼MySQL的執行順序就是從上到下執行的。markdown
mysql> explain select * from emp e where e.deptno in (select d.deptno from dept d where d.dname = 'SALEDept');
+----+--------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | <subquery2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100.00 | NULL |
| 1 | SIMPLE | e | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where; Using join buffer (Block Nested Loop) |
| 2 | MATERIALIZED | d | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
+----+--------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
複製代碼
這個例子的執行順序是先執行
id
爲2的,而後執行id
爲1的。
仍是上面那個例子,先執行
id
爲2的,而後按順序從上往下執行id
爲1的。
select_type
主要用來分辨查詢的類型,是普通查詢仍是聯合查詢仍是子查詢。
select_type Value |
JSON Name | Meaning |
---|---|---|
SIMPLE | None | Simple SELECT (not using UNION or subqueries) |
PRIMARY | None | Outermost SELECT |
UNION | None | Second or later SELECT statement in a UNION |
DEPENDENT UNION | dependent (true) | Second or later SELECT statement in a UNION, dependent on outer query |
UNION RESULT | union_result | Result of a UNION. |
SUBQUERY | None | First SELECT in subquery |
DEPENDENT SUBQUERY | dependent (true) | First SELECT in subquery, dependent on outer query |
DERIVED | None | Derived table |
MATERIALIZED | materialized_from_subquery | Materialized subquery |
UNCACHEABLE SUBQUERY | cacheable (false) | A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query |
UNCACHEABLE UNION | cacheable (false) | The second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY) |
SIMPLE
簡單的查詢,不包含子查詢和unionmysql> explain select * from emp;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
複製代碼
primary
查詢中若包含任何複雜的子查詢,最外層查詢則被標記爲Primaryunion
若第二個select出如今union以後,則被標記爲unionmysql> explain select * from emp where deptno = 1001 union select * from emp where sal < 5000;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |
| 2 | UNION | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 33.33 | Using where |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
複製代碼
這條語句的select_type
包含了primary
和union
dependent union
跟union相似,此處的depentent表示union或union all聯合而成的結果會受外部表影響union result
從union表獲取結果的selectdependent subquery
subquery的子查詢要受到外部表查詢的影響mysql> explain select * from emp e where e.empno in ( select empno from emp where deptno = 1001 union select empno from emp where sal < 5000);
+----+--------------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | e | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |
| 3 | DEPENDENT UNION | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |
| NULL | UNION RESULT | <union2,3> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
複製代碼
這條SQL執行包含了PRIMARY
、DEPENDENT SUBQUERY
、DEPENDENT UNION
和UNION RESULT
subquery
在select或者where列表中包含子查詢舉例:
mysql> explain select * from emp where sal > (select avg(sal) from emp) ;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | PRIMARY | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 33.33 | Using where |
| 2 | SUBQUERY | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
複製代碼
DERIVED
from子句中出現的子查詢,也叫作派生表MATERIALIZED
Materialized subquery?UNCACHEABLE SUBQUERY
表示使用子查詢的結果不能被緩存例如:
mysql> explain select * from emp where empno = (select empno from emp where deptno=@@sort_buffer_size);
+----+----------------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+----------------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | PRIMARY | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | Using where |
| 2 | UNCACHEABLE SUBQUERY | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |
+----+----------------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
複製代碼
uncacheable union
表示union的查詢結果不能被緩存table
對應行正在訪問哪個表,表名或者別名,多是臨時表或者union合併結果集。
- 若是是具體的表名,則代表從實際的物理表中獲取數據,固然也能夠是表的別名
- 表名是derivedN的形式,表示使用了id爲N的查詢產生的衍生表
- 當有union result的時候,表名是union n1,n2等的形式,n1,n2表示參與union的id
type
type顯示的是訪問類型,訪問類型表示我是以何種方式去訪問咱們的數據,最容易想到的是全表掃描,直接暴力的遍歷一張表去尋找須要的數據,效率很是低下。
訪問的類型有不少,效率從最好到最壞依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
通常狀況下,得保證查詢至少達到range級別,最好能達到ref
all
全表掃描,通常狀況下出現這樣的sql語句並且數據量比較大的話那麼就須要進行優化一般,能夠經過添加索引來避免ALL
index
全索引掃描這個比all的效率要好,主要有兩種狀況:
range
表示利用索引查詢的時候限制了範圍,在指定範圍內進行查詢,這樣避免了index的全索引掃描,適用的操做符: =, <>, >, >=, <, <=, IS NULL, BETWEEN, LIKE, or IN()官網上舉例以下:
SELECT * FROM tbl_name WHERE key_column = 10;
SELECT * FROM tbl_name WHERE key_column BETWEEN 10 and 20;
SELECT * FROM tbl_name WHERE key_column IN (10,20,30);
SELECT * FROM tbl_name WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
index_subquery
利用索引來關聯子查詢,再也不掃描全表value IN (SELECT key_column FROM single_table WHERE some_expr)
unique_subquery
該鏈接類型相似與index_subquery,使用的是惟一索引value IN (SELECT primary_key FROM single_table WHERE some_expr)
index_merge
在查詢過程當中須要多個索引組合使用ref_or_null
對於某個字段既須要關聯條件,也須要null值的狀況下,查詢優化器會選擇這種訪問方式SELECT * FROM ref_table
WHERE key_column=expr OR key_column IS NULL;
fulltext
使用FULLTEXT索引執行joinref
使用了非惟一性索引進行數據的查找SELECT * FROM ref_table WHERE key_column=expr;
SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
eq_ref
使用惟一性索引進行數據查找SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
const
這個表至多有一個匹配行SELECT * FROM tbl_name WHERE primary_key=1;
SELECT * FROM tbl_name WHERE primary_key_part1=1 AND primary_key_part2=2;
例如:
mysql> explain select * from emp where id = 1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | emp | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
複製代碼
system
表只有一行記錄(等於系統表),這是const類型的特例,平時不會出現possible_keys
顯示可能應用在這張表中的索引,一個或多個,查詢涉及到的字段上若存在索引,則該索引將被列出,但不必定被查詢實際使用
key
實際使用的索引,若是爲null,則沒有使用索引,查詢中若使用了覆蓋索引,則該索引和查詢的select字段重疊
key_len
表示索引中使用的字節數,能夠經過key_len計算查詢中使用的索引長度,在不損失精度的狀況下長度越短越好
ref
顯示索引的哪一列被使用了,若是可能的話,是一個常數
rows
根據表的統計信息及索引使用狀況,大體估算出找出所需記錄須要讀取的行數,此參數很重要,直接反應的sql找了多少數據,在完成目的的狀況下越少越好
extra
包含額外的信息
using filesort
說明mysql沒法利用索引進行排序,只能利用排序算法進行排序,會消耗額外的位置using temporary
創建臨時表來保存中間結果,查詢完成以後把臨時表刪除using index
這個表示當前的查詢是覆蓋索引的,直接從索引中讀取數據,而不用訪問數據表。若是同時出現using where 代表索引被用來執行索引鍵值的查找,若是沒有,表示索引被用來讀取數據,而不是真的查找using where
使用where進行條件過濾using join buffer
使用鏈接緩存impossible where
where語句的結果老是false想要了解索引的優化方式,必需要對索引的底層原理有所瞭解。
索引用於快速查找具備特定列值的行。
若是沒有索引,MySQL必須從第一行開始,而後通讀整個表以找到相關的行。
表越大花費的時間越多,若是表中有相關列的索引,MySQL能夠快速肯定要在數據文件中間查找的位置,而沒必要查看全部數據。這比順序讀取每一行要快得多。
既然MySQL索引能幫助咱們快速查詢到數據,那麼它的底層是怎麼存儲數據的呢?
hash
hash表的索引格式
hash表存儲數據的缺點:
事實上,MySQL存儲引擎是memory
時,索引數據結構採用的就是hash表。
二叉樹
二叉樹的結構是這樣的:
二叉樹會由於樹的深度而形成數據傾斜,若是樹的深度過深,會形成io次數變多,影響數據讀取的效率。
AVL樹 須要旋轉,看圖例:
紅黑樹 除了旋轉操做還多了一個變色
的功能(爲了減小旋轉),這樣雖然插入的速度快,可是損失了查詢的效率。
二叉樹、AVL樹、紅黑樹 都會由於樹的深度過深而形成io次數變多,影響數據讀取的效率。
再來看一下 B樹
B樹特色:
圖例說明:
每一個節點佔用一個磁盤塊,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指針,指針存儲的是子節點所在磁盤塊的地址。
兩個關鍵詞劃分紅的三個範圍域對應三個指針指向的子樹的數據的範圍域。
以根節點爲例,關鍵字爲 16 和 34,P1 指針指向的子樹的數據範圍爲小於 16,P2 指針指向的子樹的數據範圍爲 16~34,P3 指針指向的子樹的數據範圍爲大於 34。
查找關鍵字過程:
一、根據根節點找到磁盤塊 1,讀入內存。【磁盤 I/O 操做第 1 次】
二、比較關鍵字 28 在區間(16,34),找到磁盤塊 1 的指針 P2。
三、根據 P2 指針找到磁盤塊 3,讀入內存。【磁盤 I/O 操做第 2 次】
四、比較關鍵字 28 在區間(25,31),找到磁盤塊 3 的指針 P2。
五、根據 P2 指針找到磁盤塊 8,讀入內存。【磁盤 I/O 操做第 3 次】
六、在磁盤塊 8 中的關鍵字列表中找到關鍵字 28。
由此,咱們能夠得知B樹存儲的缺點:
官網:Most MySQL indexes (PRIMARY KEY, UNIQUE, INDEX, and FULLTEXT) are stored in B-trees
不要誤會,其實MySQL索引的存儲結構是B+樹
,上面咱們一頓分析,知道B樹
是不合適的。
mysql索引數據結構---B+Tree
B+Tree是在BTree的基礎之上作的一種優化,變化以下:
一、B+Tree每一個節點能夠包含更多的節點,這個作的緣由有兩個,第一個緣由是爲了下降樹的高度,第二個緣由是將數據範圍變爲多個區間,區間越多,數據檢索越快。
二、非葉子節點存儲key,葉子節點存儲key和數據。
三、葉子節點兩兩指針相互鏈接(符合磁盤的預讀特性),順序查詢性能更高。
B+樹存儲查找示意圖:
注意:
在B+Tree上有兩個頭指針,一個指向根節點,另外一個指向關鍵字最小的葉子節點,並且全部葉子節點(即數據節點)之間是一種鏈式環結構。
所以能夠對 B+Tree 進行兩種查找運算:一種是對於主鍵的範圍查找和分頁查找,另外一種是從根節點開始,進行隨機查找。
因爲B+樹葉子結點只存放data,根節點只存放key,那麼咱們計算一下,即便只有3層B+樹,也能製成千萬級別的數據。
假設有這樣一個表以下,其中id是主鍵:
mysql> select * from stu;
+------+---------+------+
| id | name | age |
+------+---------+------+
| 1 | Jack Ma | 18 |
| 2 | Pony | 19 |
+------+---------+------+
複製代碼
咱們對普通列建普通索引,這時候咱們來查:
select * from stu where name='Pony';
複製代碼
因爲name
建了索引,查詢時先找name
的B+樹
,找到主鍵id
後,再找主鍵id
的B+樹
,從而找到整行記錄。
這個最終會回到主鍵上來查找B+樹,這個就是回表
。
若是是這個查詢:
mysql> select id from stu where name='Pony';
複製代碼
就沒有回表了,由於直接找到主鍵id
,返回就完了,不須要再找其餘的了。
沒有回表就叫覆蓋索引
。
再來以name
和age
兩個字段建組合索引(name, age),而後有這樣一個查詢:
select * from stu where name=? and age=?
複製代碼
這時按照組合索引(name, age)
查詢,先匹配name
,再匹配age
,若是查詢變成這樣:
select * from stu where age=?
複製代碼
直接不按name
查了,此時索引不會生效,也就是不會按照索引查詢---這就是最左匹配
原則。
加入我就要按age查,還要有索引來優化呢?能夠這樣作:
age
字段單獨建個索引可能也叫
謂詞下推
。。。
select t1.name,t2.name from t1 join t2 on t1.id=t2.id
複製代碼
t1有10條記錄,t2有20條記錄。
咱們猜測一下,這個要麼按這個方式執行:
先t1,t2按id合併(合併後20條),而後再查t1.name,t2.name
或者:
先把t1.name,t2.name找出來,再按照id關聯
若是不使用索引條件下推優化的話,MySQL只能根據索引查詢出t1,t2合併後的全部行,而後再依次比較是否符合所有條件。
當使用了索引條件下推優化技術後,能夠經過索引中存儲的數據判斷當前索引對應的數據是否符合條件,只有符合條件的數據纔將整行數據查詢出來。
Explain
爲了知道優化SQL語句的執行,須要查看SQL語句的具體執行過程,以加快SQL語句的執行效率。首發公衆號 行百里er ,歡迎老鐵們關注閱讀指正。代碼倉庫 GitHub github.com/xblzer/Java…