每次百度sql優化都是那麼幾個東西,此次本身來寫一下本身的總結html
首先你要知道數據庫中的數據是怎麼存儲的,數據庫將數據放置在一個叫作數據頁的結構中,這個數據頁中的數據用單鏈表串起來,ok,到這,我只要找到這個數據頁就能遍歷他,也就是能查到全部數據了,可是一個頁放不了全部數據,因此呢,得在增長一個頁,咱們成爲頁溢出,或者叫頁分裂,一大堆數據得用多個頁儲存,多個頁用雙向鏈表串起來,這樣數據就都能查到了.mysql
光查到還不行,咱們還得更快的查到,因此數據存放就是個問題了,不能亂放,得有順序的放,有順序的放就會致使放的時候比較麻煩(好比像arraylist同樣,插入得把後面的數據所有移動才能完成中間的插入),可是這樣方便查找,因此咱們得按順序去存放數據,可是剛纔也說了咱們不能把全部數據都放到一塊,因此數據放到頁中,多個頁散亂起來也很差管理,因此還要有一個管理頁的東西,把頁管理起來,天然而然就會造成一個叫樹的結構,這樣咱們只要有根節點在手,全部數據都能查找到了(這個根在mysql中是位置不變的,固定的),因爲有序,這就是一個搜索樹.sql
剛纔說到樹,咱們聽過的有二叉樹,二叉搜索樹,平衡二叉樹,紅黑樹,樹就是一種能更快的查找數據的數據結構,那麼mysql中用的是哪一種樹呢?數據庫
上面的都不是(因此上面那個圖沒畫成二叉樹),咱們知道,樹的查找跟樹的深度有關係,因此爲了更快的查找咱們不能一個節點只能有2個兒子,咱們讓他有多個,這樣下來,一個瘦高的樹就會變的矮胖,更加方便查找,這樣的一棵樹咱們稱之爲多路搜索樹(傳說中的B-樹,不念B減樹,就是B樹,中間是槓不是減號,就是多路搜索樹),B樹有個特色,他的每一個節點都能儲存數據,這樣的話咱們要遍歷樹中的內容,必須使用中序遍歷,這樣搞就很麻煩,因而出現了B-樹另一種變種,B+樹,B+樹比B-樹更加矮胖,說明他查找效率更高,並且B+樹規定因此內容必須儲存在葉子節點中,就是樹的最下面一層,並且這最下面一層還得用鏈表串起來這樣咱們要遍歷全部數據就更好查找了,直接遍歷最下面一層的鏈表就好了,因此咱們的mysql用的就是這種樹,叫B+樹json
具體樹的數據結構能夠本身去看關於數據結構的書,這裏參考小灰的漫畫數據結構數據結構
B-樹 https://mp.weixin.qq.com/s/rDCEFzoKHIjyHfI_bsz5Rwapp
B+樹 https://mp.weixin.qq.com/s/jRZMMONW3QP43dsDKIV9VQ函數
B+樹示意圖工具
B+樹是搜索樹,因此上面那些沒有存值的節點,天然就相似於一個目錄,這個目錄,就是咱們說的索引,一個索引就對應一顆B+樹測試
數據庫在存儲數據時,會根據主鍵,創建一顆向上面同樣的B+樹(若是沒有主鍵,數據庫會在儲存數據的時候自動給你一個隱藏列做爲主鍵),而咱們說的優化,就是如何更好的使用這個樹,這纔是sql優化的關鍵點.
這個樹是有序的,因此你要是隨便在中間插入數據,必定會調整整個的樹的結構,(爲了保證有序性嘛),因此咱們要知道的第一點,索引會影響插入,修改,刪除的效率.
接下來咱們說說如何利用這個樹(索引)
索引的分類
1.聚簇索引(就是主鍵索引)
表自動會建立這個索引,這個B+樹,會把主鍵的值做爲索引KEY按順序儲存,每一個葉子節點會將整個數據內容所有儲存下來,也就是你一條數據裏有什麼,我這個葉子節點中就有什麼,最慢的查詢,全表掃描,就光用我這顆樹就行,我啥都有
2.非聚簇索引(二級索引)
除了主鍵的這個索引,其餘索引都是非聚簇索引,也就是你本身創建的那個索引,就至關於在表空間下,新創建了一個B+樹,這個樹跟上面不同,這個樹是用你指定的列的值做爲KEY按順序儲存,你指定的 列的值和主鍵的id 做爲值儲存在葉子節點中,也就是說,你要是根據我來查詢,查完以後還得拿着主鍵id再去聚簇索引中查找一遍,才能拿到全部的值,這個過程稱之爲回表,那就又有一個優化點,儘可能減小回表操做
查詢非聚簇索引是順序io / 回表是隨機io,順序io要比隨機io快不少 ,若是回表查詢量大,偏向於使用全表掃描,直接訪問聚簇索引
3.聯合索引(這個本質仍是個二級索引)
這顆樹關鍵點以下:
假如按照A/B/C三列來創建了一個聯合索引,聯合索引是一棵樹, 分別給ABC三列創建二級索引,是三棵樹
假如按照A/B/C三列來創建了一個聯合索引,會先按照A的值做爲KEY,當A相同時,再按照B的值做爲KEY,當B相同時,再按照C的值做爲KEY的順序來儲存數據,葉子節點中數據是這三列的值和主鍵id
看到這裏是否是感受跟order by字段好類似,這個確實跟order by有關係
1.索引不是萬能的,不能隨便加索引
上面說索引會影響插入,修改,刪除的效率,這個影響不止一點點,由於一個索引就是一棵樹,你要是在某列上創建了多個索引,當修改這列時,會從新調整相關的好幾顆樹,效率就浪費在這裏了,因此索引對查詢有好處,對修改沒有一點好處,不能隨便創建索引
2.能用主鍵作條件,就別用別的
根據主鍵查詢只會訪問聚簇索引,並且聚簇索引中包含其餘全部數據,因此說這是最快的訪問方式
3.根據列中數據的分散性,來決定是否添加索引
列中數據越分散,經過條件篩選後留下來的數據就會越少,好比身份證號,等值查詢一次匹配一個,性別,等值查詢一次匹配一坨,咱們知道咱們二級索引是可能須要回表的,你查詢出來一坨的時間其實並很少,可是拿着你返回的一坨id去回表,這就浪費時間了,因此,列中數據越分散,加索引後的查詢效率會越高
4.在有索引的列中少使用null,或者說少用null做爲條件去查詢
由於數據庫中null不等於null,因此即使是惟一約束,都沒法限制多個null的插入,因此通常某列設置爲惟一約束,都要限制非空,不然可能沒有惟一的意義了
5.巧用聯合索引
第一點 : 聯合索引創建的順序就是按順序一級一級的創建,因此咱們查詢的時候有個最左匹配特性,好比按照name,age,sex創建聯合索引,你按照name,age,sex三列查詢(順序無所謂,查詢優化器會幫你作優化)就能用上索引,你按照name,age兩列也能用到索引,你按照name單列也能夠,由於這些都是聯合索引的所有或者局部,可是你要是按照age或者sex單列,或者age,sex兩列這就不行了,由於我name是不知道是否是有序的,這時優化器可能就更偏向於使用全表掃描了
第二點 : 咱們上面說了,聯合索引的葉子節點中數據是這三列的值和主鍵id,若是你查詢後顯示的條件也是這三列,那麼數據在二級索引的B+樹中就有,就不會去回表了,少一步回表操做,固然更快,這叫覆蓋索引
6.利用索引覆蓋
緣由如上,同時,咱們也要求顯示的列儘可能不要用*,生產環境中,須要什麼查什麼,不要隨便寫*,目的就是爲了碰上覆蓋索引
7.關於排序
單列排序能直接使用索引,可是若是是多列排序就要考慮一下了,mysql中規定規定使用聯合索引的各個排序列的排序順序必須是一致的.在多列排序中,若是要用就只能使用聯合索引,而聯合索引創建的B+樹有特定順序,若是sql中多列排序順序與B+樹中順序統一,則可使用,可是若是創建了ABC聯合索引,可是order by A,B desc ,C 這樣就沒法使用聯合索引了
mysql8的新特性,降序索引 https://www.cnblogs.com/ivictor/p/9072361.html
8.關於模糊查詢
咱們都知道模糊查詢會全表掃描,可是爲何呢?由於建樹的時候我是根據數據的值一個一個比較出順序創建的樹,可是你給我一個%開頭的數據,好比%aaa%,那就是匹配誰均可以了啊,因此只能用全表掃描了.可是索引能夠匹配列前綴,若是你給個人是 aaa% 以固定的字符串開頭的數據,那我也能利用索引,由於 aaa開頭的數據必定是存放在二級索引某一片連續的區域的,可是若是你要是%aaa就不行了,若是非要這樣作,能夠在入庫時將數據的逆序字符串也存入數據庫,也創建索引這樣就能利用這個逆序列作 aaa%的匹配了
9.只爲用於搜索、排序或分組的列建立索引
也就是隻爲 where order by group by 後面的列創建索引,不要爲查詢出來顯示的列創建索引
10.不要冗餘索引
好比咱們創建了聯合索引ABC,和單列的非聚簇索引A,若是單純查找A='aaa'的數據,其實這兩個索引理論上咱們都能利用到,而聯合索引的功能比單列的A的索引還廣,因此徹底能夠刪除掉A索引,功能沒有差異,還少維護一顆B+樹
11.保證索引列是以單獨列的形式出現
a = 5+3 能夠利用索引, 可是 a-3 = 5 就沒法利用索引了 ,由於沒有一棵樹中的值對應着 a-3的值,同時優化器也不會自動優化這種寫法
12.索引中的數據佔用的空間儘可能小
好比bitint效率就會高於int,由於你佔用數據空間越小,表示我一樣大小的地方就能多存數據,同一個節點數據越多,B+樹就越矮胖,反之,就越瘦高,影響查找的層數,因此某些大字段又不須要全值匹配的,能夠在索引上限制只對前多少個字符創建索引
13. or 的使用,不要和沒有索引的條件or在一塊兒
or使用起來要當心,由於有優化器的存在,好比咱們有一條語句select * from table where a>1 or b='a',這裏的a有索引,而b沒有索引,優化器在優化SQL時,會把沒有索引的條件做爲true(由於篩選要進行全表掃描才行),那麼sql會被優化成爲select * from table where a>1 or TRUE, or TRUE ,那就是true ,接着優化就是select * from table where true,因此要全表掃描,a的索引也沒用了,也就是說使用到索引的搜索條件和沒有使用該索引的搜索條件使用or鏈接起來後是沒法使用該索引的.
14.聯表查詢時,儘可能讓數據量小的表作主表
用小表去驅動大表,減小子表被匹配次數
15.關於子查詢的order 和 group
子查詢中order by 是沒用的,不要加
子查詢中,sql中若是沒有聚合函數以及HAVING
子句,group by也是沒有用的
以上兩個寫了也會被查詢優化器優化掉
16.關於不相關子查詢
不相關就是說子查詢能夠單獨執行,不依賴外部條件,想要優化,就按照單表去優化就行
17.in 後面的集合不要太大
in 後面的集合中數據若是用到索引,優化器會一個一個掃描,可是若是太大了,就會本身直接估算一個值,可能就不會用到索引了,這個閾值跟eq_range_index_dive_limit參數有關
查詢eq_range_index_dive_limit : SHOW VARIABLES LIKE '%dive%' 默認是200,也就是說集合結果超過200個,mysql會本身估算一個成本
如今的優化器策略選擇是基於成本優化,在sql真正查詢以前,優化器會根據咱們sql攜帶的條件和表中數據量(這是一個估值),來選擇使用哪一種執行計劃(好比數據量會影響執行計劃的選擇,表中一共10條數據,某種狀況下,優化器可能就不用索引,直接全表掃描了),因此你的sql具體執行的時候是使用的哪一個索引,單純看sql是看不出來的,須要查詢一下執行計劃,也就是使用 EXPLAIN 關鍵字
查詢優化器有時候會對sql進行索引合併,既然是索引合併,就得在條件中使用多個索引,可是具體是否合併索引,得看查詢優化器作出的成本分析
索引合併(index merge) , 有三種合併索引狀況
1.intersection(取交集) , 好比 select * from table where a=? and b=? , ab兩列分別創建二級索引,正常狀況是按照a篩選數據,回表,而後再回表結果中按照b篩選數據,若是使用了索引合併, 會先查詢a條件中的主鍵,再查詢b條件中的主鍵,而後對兩份主鍵取交集,最後回表,這樣作的好處是:索引是按照順序排好的,因此只須要兩次順序IO,回表時隨機IO便可,順序IO賊快,目的是減小回表的操做
(原文)使用條件:
狀況一:二級索引列是等值匹配的狀況,對於聯合索引來講,在聯合索引中的每一個列都必須等值匹配,不能出現只匹配部分列的狀況。好比聯合索引abc,條件中只用了a沒有用bc,就不能索引合併
狀況二:主鍵列能夠是範圍匹配,聚簇索引和二級索引合併的時候,二級索引必須是等值匹配,二級索引值相同時,是按照主鍵從小到大排序的,因此能夠索引合併
2.union(取並集), , 好比 select * from table where a=? or b=? , ab兩列分別創建二級索引,正常狀況是按照a篩選數據,回表,而後再回表結果中按照b篩選數據,若是使用了索引合併, 會先查詢a條件中的主鍵,再查詢b條件中的主鍵,而後對兩份主鍵取並集,最後回表,這樣作的好處是:索引是按照順序排好的,因此只須要兩次順序IO,回表時隨機IO便可,順序IO賊快
(原文)使用條件:
狀況一:二級索引列是等值匹配的狀況,對於聯合索引來講,在聯合索引中的每一個列都必須等值匹配,不能出現只匹配部分列的狀況。好比聯合索引abc,條件中只用了a沒有用bc,就不能索引合併
狀況二:主鍵列能夠是範圍匹配,聚簇索引和二級索引合併的時候,二級索引必須是等值匹配,二級索引值相同時,是按照主鍵從小到大排序的,因此能夠索引合併
狀況三:or兩邊的結果是intersection合併後的結果,可使用索引合併
3.Sort-Union(先排序後,再取並集),由於union必須使用等值,以後二級索引等值才能利用二級索引中的已經排好序的主鍵進行取交集操做,因此若是select * from table where a>? or b<? ,是不能直接union的,可是能夠先查詢a條件中的主鍵,再查詢b條件中的主鍵,而後對兩份主鍵各自排序,以後再取並集
(原文)使用條件:
在上面union的基礎上,還須要保證查找出的主鍵少,不然排序就會很浪費時間,查詢優化器也就不會這樣合併索引了
根據查詢優化器計算出來的方式,咱們有以下幾種查詢的過程
1.const 速度爲常量,這是最快的一種查詢速度,通常的根據主鍵等值查找,或者根據惟一約束的二級索引查找,一次只查到一條,就是這麼快(這時雖然會回表,可是也是會很快),可是條件中不能有跟null產生關係的
2.ref 速度也挺快,通常是因爲普通二級索引等值匹配,但因爲二級索引並不限制索引列值的惟一性,因此可能找到多條對應的記錄,再根據這些記錄回表查找, 也就是說使用二級索引來執行查詢的代價取決於等值匹配到的二級索引記錄條數.若是匹配的記錄較少,則回表的代價仍是比較低的,但若是匹配的記錄太多了,可能會直接使用全表掃描(這中間有個叫查詢優化器的東西,在sql執行以前,會在表的元數據表中先評判一下用哪一種方式查詢,好比在這裏,表中一共10w條數據,查詢優化器估摸了一下大約有9.9w數據會被匹配到,就會直接跳過索引,使用全表掃描)
這裏關於null要作一個解釋
由於數據庫中null != null,因此不管是普通的二級索引,仍是惟一二級索引,它們的索引列對包含NULL值的數量並不限制,因此咱們採用key IS NULL這種形式的搜索條件##最多##只能使用ref的訪問方法,而不是const的訪問方法。
3.ref_or_null 比上面慢一點,由於條件中多了一些帶null值的條件,可能回表次數就會偏多
4.range 範圍查詢時,好比用 in or關聯的條件,速度可能就不會太快了,由於有回表操做
5.index 使用覆蓋索引時,也就是說咱們能夠直接經過遍歷二級索引的葉子節點的記錄來比較某個條件是否成立,把匹配成功的二級索引記錄的列的值直接加到結果集中就好了。因爲二級索引記錄比聚簇索記錄小的多(聚簇索引記錄要存儲全部用戶定義的列以及所謂的隱藏列,而二級索引記錄只須要存放索引列和主鍵),並且這個過程也不用進行回表操做,因此直接遍歷二級索引比直接遍歷聚簇索引的成本要小不少
6.all 全表掃描,直接訪問聚簇索引
聯表查詢中,有主表和子表,對於主表其實跟上面查詢方式同樣,而後主表結果根據關聯條件去循環匹配子表數據,也就是主表訪問一次,子表訪問屢次(看主表篩選剩下幾條)
兩張表聯接查詢至關於兩層for循環,三張就是三層
要想加快聯表查詢速度,須要優化子表查詢
1.eq_ref 相似於單表中的const,意味着關於子表查詢條件上加入了主鍵或者惟一索引
剩下的和單表同樣
join buffer:若是每次匹配被驅動表都從新將被驅動表加載到內存,這樣的話太耗費時間了,因此提出了join buffer這麼一塊空間,就至關於預加載,一次加載,屢次利用,能夠調節join_buffer_size這個參數的大小,來加快聯表查詢速度
語法 : EXPLAIN {sql語句}
這個表是用來顯示結果的,他本質不是用來優化sql的,他的做用是指出你當前sql的一個狀態,從中你能夠找到下一步應該優化的點,優化sql須要用到上面說的那些方法
id:在一個大的查詢語句中每一個SELECT
關鍵字都對應一個惟一的id,若是爲null,說明是臨時表
select_type:有以下幾個值
SIMPLE: 簡單查詢,不包含UNION
或者子查詢的查詢都算做是SIMPLE
類型
PRIMARY:主要的查詢,對於包含UNION
、UNION ALL
或者子查詢的大查詢,主要的那個查詢:好比最左側,或者最外層查詢
UNION:不相關聯接查詢,對於包含UNION
、UNION ALL
或者子查詢的大查詢,除了PRIMARY,剩下的都是UNION
DEPENDENT UNION:相關聯接查詢,對於包含UNION
、UNION ALL
或者子查詢的大查詢,若是其餘查詢都和外層的查詢有關聯關係,那麼類型就是DEPENDENT UNION
UNION RESULT:union關鍵字有去重效果,去重時會將全部數據放在一個臨時表中,這個臨時表類型就是UNION RESULT
SUBQUERY:不相關子查詢(mysql會將不相關子查詢轉成一張臨時表寫入內存或者硬盤,查詢時可能直接將sql改寫爲聯接查詢,子表只訪問一次便可)
DEPENDENT SUBQUERY:相關子查詢(mysql沒法優化相關子查詢,因此可能屢次訪問子表)
DERIVED:對於採用物化的方式執行的包含派生表的查詢,該派生表對應的子查詢的select_type
就是DERIVED
MATERIALIZED:當查詢優化器在執行包含子查詢的語句時,選擇將子查詢物化以後與外層查詢進行鏈接查詢時,該子查詢對應的select_type
屬性就是MATERIALIZED
type:有以下幾個值
system:若是你表的引擎是MyISAM,就是這個類型,表示精確查詢
const:
eq_ref:
ref:
fulltext:全文索引
ref_or_null:
index_merge:索引合併
unique_subquery:若是查詢優化器決定將IN
子查詢轉換爲EXISTS
子查詢,並且子查詢可使用到主鍵進行等值匹配的話,就是unique_subquery
index_subquery:若是查詢優化器決定將IN
子查詢轉換爲EXISTS
子查詢,並且子查詢可使用到普通索引進行等值匹配的話,就是index_subquery
range:
index:用到了索引,可是須要將索引整個掃描一遍,能夠理解爲有索引時最低效率
all
上面沒寫的就是跟單表和聯表中寫的同樣
possible_keys:可能用到的索引,每一種可能用到的索引都會讓優化器去計算一遍成本,因此儘可能減小這裏的值
key:查詢時真正使用的索引
ref:聯接時用到的外鍵
rows:掃描的行數,越小越好
filtered:在聯表查詢中有意義,這個值是一個比例,越小越好,表示在主子表關聯後掃描出的rows中,大概有多少比例的數據知足聯表最後查出的結果
好比主子表關聯條件爲 on t1.key = t2.key 這個條件篩選出的rows爲100行
同時還有條件是where t1.kkeeyy = ? 這個條件從上面100行中大概只有10行
那麼filtered就是10,表示10%
extra:額外的備註,好比你where條件是否用到了索引,是否使用了buffer pool,都會顯示在這裏
補充1:上面的explain關鍵字展現的表並不能看出成本 能夠在explain和sql語句中間加上 FORMAT=JSON ,就能看到一個json數據
EXPLAIN FORMAT=JSON {sql語句}
補充2:使用explain語句查詢以後,能夠接着執行 SHOW WARNINGS 這條語句
若是顯示code=1003,那麼massage就會顯示mysql重寫後的sql,可是這個sql不是標準sql不能執行,意會便可
經過optimizer trace能夠查看優化器優化的過程
1.首先查看優化器過程記錄是否開啓:默認關閉
SHOW VARIABLES LIKE 'optimizer_trace';
2.接着打開這個功能:
SET optimizer_trace="enabled=on";
SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000; # 這條命令用來設置trace可用的空間大小,若是輸出的內容太多,會被截斷,要看所有的數據,把這個值調大
3.而後執行sql語句
select ....
4.接着查看優化過程
SELECT * FROM information_schema.OPTIMIZER_TRACE;
四列:分別是 QUERY 查詢語句
TRACE 咱們重點查看的內容,優化過程
MISSING_BYTES_BEYOND_MAX_MEM_SIZE 因空間不足而忽略掉的文本字節數,爲0表示沒有忽略,>0表示trace內容顯示不全,能夠設置
SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000;
INSUFFICIENT_PRIVILEGES 權限是否可用,0可用 1不可用
5.關閉優化器記錄
SET optimizer_trace="enabled=off";
這裏注意一點,我測試時在sqlyog上,發現總也查不到第4步的數據,可是在命令行中執行是能夠看到結果的,說明sqlyog工具會在select語句後執行其餘sql,因此建議在命令行中測試(navcat沒有試過,你們能夠自行測試)
trace內容大體分爲3個階段,準備階段,優化階段(挨個索引成本分析,分析可用仍是不可用),執行階段(最終階段)
準備階段(join_preparation) : 就是你那條sql語句
優化階段(join_optimization) : 轉化條件(將一些無用的條件,位置不對的條件【好比on後面的非關聯關係的條件會被放到where後】刪除或者轉移位置 ),分析每張表的每個索引是否可用(因此我認爲若是索引太多,會增長分析過程),預估不一樣單表的訪問成本,嘗試增長一些其餘輔助條件
執行階段(join_execution) : 最終選擇的執行結果
參考文章 : 掘金小冊中的<<MySql是怎樣運行的:從根上理解MySql> >
做者 文章寫的仍是很詳實的,經過閱讀他的這本小冊,寫了上邊的這篇隨筆,這本小冊讀完讓我對mysql中的一些原理又略知了三四,可是本人對於小冊中不少細節還未能吸取,因此上面的文字描述並非相似於教學同樣教讀者怎麼作,權當本身的一篇隨筆,記錄一些關鍵知識點,簡單串聯了一下,若是有緣能看到這篇文章,建議能夠去我提供的連接或者本身搜索其中知識點,已得到更加清晰的瞭解.
這篇文章能起到拋磚引玉的做用,足矣