讓我再深擼一次mysql吧,這次主要以應對面試來說說mysql,大概幾個方向,索引結構,查詢引擎,索引優化,explain的詳解和trace工具的使用。
索引:
我們先來看一下mysql的B+tree,本文幾乎都在圍繞這個圖來說的。
mysql的底層是使用B+tree來存儲數據的,和B+tree有一點點不同的是葉子節點是雙向鏈表的結構,並不是圖內的單向指針的。且null值放置在葉子節點的最前面。這個是主鍵索引。
下面我來看一下聯合索引,比如我們現在有Student表,將name,age,address三個字段設置成聯合索引,這時存儲的節點變為先按照name排序,name一致按照age排序的B+tree,攜帶數據為主鍵ID,並不攜帶整體數據的。
查詢引擎:
我們常見的查詢引擎主要是InnoDB還有MyISAM,區別主要是,MyISAM存儲B+tree的索引攜帶數據都是內存地址,我們在查詢的時候需要拿到hash計算后的內存地址,然后回行得到數據,而InnoDB直接攜帶數據,不需要回行,MyISAM不支持事物,不支持外鍵,也不支持行級鎖,對於數據的非范圍查詢效率可能要高於InnoDB,且在底層有維護count總條數的內存,但是MyISAM的范圍查詢是不能用到索引的。我們大部分使用的都是InnoDB查詢引擎,順便提一下,MyISAM在磁盤上的文件為三個,一個是表的結構,一個是索引文件,一個是真正的數據文件,InnoDB在磁盤上存的是兩個文件,一個是表結構文件,兩一個是索引和數據文件。
explain的詳解:
我們執行
explain select * from student;
這時會有十列數據,分別的是id,select_type,table,type,possible_keys,key,key_len,ref,rows,Extra。我們來逐個說一下他們都是干啥的。深入理解explain和B+tree的使用,mysql面試也就有救了。
id:就是一個編號,同時也代表了select的執行順序,一般來說,我們有幾個select就有幾行數據,他們可能擁有相同或者不同的ID,執行順序為ID大的優先執行,id相同,從上到下執行。id為null的最后執行。
select_type:代表我們的執行是一個什么樣子的SQL,是簡單查詢啊,還是連表查詢,大致可以分為
primary:復雜查詢的最外層查詢,(嵌套查詢的最外層)比如explain select 1 from (select * from table) t;
subquery:子查詢的select,但不表示在from后面的查詢,比如explain select (select 1 from table) from table;或者explain select * from table where id = (select 1 from table where id = 2)
derived:和上面的subquery是相對的,表示在外層from后面的子查詢。比如explain select * from (select * from table) t;
union:聯合查詢,explain select * from table union select * from table;
union result:表示聯合查詢后的組合,並不代表實際的select。
table:代表你查詢的是哪一張表,如果表你給予了別名,這里會顯示別名,會顯示<derivedn>,標紅色的n為執行計划里的id列。還有某些時候會顯示<union1,2>,也就是說,我們合並id為1和2的結果虛擬表。有時候還會顯示null,例如EXPLAIN SELECT 1
system一般是表為空,或者表里只有一行數據。 性能也是最好的,用左腳腳指頭想想,數據都為空,或者只要一行,查詢一定不會慢到哪里去。
const:用到了主鍵索引的查詢,效率依然給力。主鍵索引葉子節點直接帶着數據呢,不需要再去掃描第二顆樹,效果一定給力了。
eq_ref:eq比較啊。所以簡單的sql不會出現這個玩意。例如explain select * from table t innter join table2 t2 on t.pid = t2.id;也就是說該select下關聯的一定是主鍵id,效率也是很OK的,后面會說一下innter join的查詢機制。在trace的使用會說的,別慌,干貨面試還沒到來。
ref:相比eq_ref來說比較好記憶的,還是比較,也就是非主鍵索引的比較。例如explain select * from table t innter join table2 t2 on t.pid = t2.name;還是走索引的性能還是可以的。說明一下,這個type為ref,簡單查詢也可以出現,不一定是兩張表關聯才會出現的。
range:范圍查詢,也可以理解為索引范圍查詢。
index:掃描全表索引,比All強一點,說完All會舉例。
All:垃圾了,全表掃描。
例如:explain SELECT classNum from student; 就是一個index查詢,因為classNum是一個非主鍵索引,所以在我們的節點上存儲的,不需要攜帶id再次去第二個上掃描。
explain SELECT classNum,create_time from student;就是一個all查詢,因為classNum雖然是一個非主鍵索引,可以拿到classNum的數據,但是我們卻得不到create_time的數據,其實也可以通過classNum的索引樹得到id,然后再拿着id去主鍵索引樹上找create_time,需要查找兩顆樹,代價太大了,mysql底層幫我們優化了,在這里說一下啊。sql具體走不走索引和表內數據量一點關系都沒有,不是說表里的數據大,就一定走索引或者不走索引,后面我們在trace會具體分析。
possible_keys:可能用到的索引。比如主鍵索引,非主鍵聯合索引。
set session optimizer_trace="enabled=on",end_markers_in_json=on
直接在mysql控制台運行就OK的,平時沒事別開這個玩意,會對性能有影響的。
然后我們運行sql;在后面加上SELECT * FROM information_schema.OPTIMIZER_TRACE;例如:
select * from student WHERE name > '張三'; SELECT * FROM information_schema.OPTIMIZER_TRACE;
這時我們看結果2中會有有這樣一個數據
我們來主要看第二列,TRACE,復制出來弄到json解析器內。
然后我們查找一下cost這個參數,cost就是我們使用各個索引的一個指標,越大表示越差,只在一個sql內比較,不要在兩個不同的sql比較啊。我們來看一下我的cost
這個是全表掃描大概是4.1。然后我繼續向下看。
這有還有一個使用name_num_address這個聯合索引的cost為3.41要比前面的4.1好,那么mysql選擇走name_num_address聯合索引。我這還有一個事例。EXPLAIN select * from student WHERE name > 'a';
正常來說,name是一個聯合索引,我們拿按照name去范圍查找,type列應該為range,其實不然,mysql並沒有選擇走任何索引。可以自己嘗試用trace去看看執行過程。
由於我愛動mysql的默認配置,這里簡單說一下using filesort排序,底層分為兩中排序方式,一種是單路排序,也可以理解為一次排序或者叫非回溯排序,就是你的查詢結果足夠小(小於1024字節),mysql有一個
max_length_for_sort_data 的參數默認為1024字節,我們就將我們要排序的結果集拿到sort buffer中進行排序,如果大於1024字節,放不下啦,也就是雙路排序,也可以叫回溯排序,就是我們只拿着需要排序的字段和唯一標識的id到sort buffer中進行排序,排序以后再回去找他們對應的數據,
這個就是雙路排序,使用trace工具可以看到不同的using filesort,可以自己嘗試。輸入set max_length_for_sort_data = 字節數,可以自己更改這個參數,最好沒事別動這個玩意。讓DBA調,我們只是知道這么回事,太底層的還沒深入研究。
貌似說了這么多可以出幾個面試題來聊聊了。
1、我們InnoDB的主鍵用數字自增好,還是UUID好?
答:當然是數字的好,還是回到我們的B+tree,這顆樹是按照由小到大,由左向右來排列的。我們的數字便於我們去比較,UUID比較起來是很耗力耗時的。而且UUID比較占地方,mysql的B+tree的每個節點16KB大小,我們用數字類型,可在有限的空間內,有限的層級,存儲更多的數據(其實沒啥用了,三層的B+tree就可以存2000萬的數據了)
而且最好是自動增長的,因為中間有間隙時,當我們插入的數據正好需要排列在間隙位置,可能會造成樹的重新排列,影響效率。
2、為什么要設置is not null字段。
答:mysql對於null是不友好的,官方文檔也是這樣來說的,不建議使用null,null都放在B+tree最左側,對於比較大小是很不利的。在sql語句中where ** is null 會直接不使用索引,與其null還不如給予其一個默認值。
3、什么是最左前綴原則。
答:我們在使用復合索引時必須要按照其順序充分的使用,比如我們的聯合索引為ABC三列,那我們想用C就一定先使用B,想使用B一定要使用A。范圍查詢以后的索引不再繼續使用,並且不要做任何函數計算處理,也會不再走索引查詢了。
再就是比如有一個字段varchar類型,我們在比較數字的時候一定要加“”,不然mysql底層會執行一個強轉函數,從而造成不在走索引。
4、說說對於mysql的優化措施。個人總結。
答: 使用int類型作為主鍵,且自動增長。(一定要設置一個主鍵),設置索引字段is not null,索引字段要選擇區分度高使用頻率高的字段。
貨幣字段使用DECIMAL來存儲。 很多事還要對應實際的業務需要來確定的。
mysql的索引個人覺得差不多就這么多吧。關於索引的使用優化並沒有說太多,這個還是需要靠個人經驗的,心中有索引的存儲模型,熟練使用explain我相信優化sql不成問題的。
下一期我們再來說說mysql的鎖,事務,分布式還有日志。 還有MVCC
有一個點忘記說了,select count(name) from table 非主鍵索引是最快的。別不信,自己琢磨琢磨。自己去試(InnoDB)。MyISAM引擎有維持count的內存。
最進弄了一個公眾號,小菜技術,歡迎大家的加入