MySQL優化相關總結

前言

1.1 b+樹介紹

       B+樹是B-樹的變體,也是一種多路搜索樹,其定義基本與B-樹同,除了:

  1. 非葉子結點的子樹指針與關鍵字個數相同;
  2. 非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-樹是開區間);
  3. 爲所有葉子結點增加一個鏈指針;
  4. 所有關鍵字都在葉子結點出現;

       b+樹結構如下圖所示:
在這裏插入圖片描述
       B+的搜索與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹可以在非葉子結點命中),其性能也等價於在關鍵字全集做一次二分查找;

1.1.1 b+樹的特性
  1. 所有關鍵字都出現在葉子結點的鏈表中(稠密索引),且鏈表中的關鍵字恰好是有序的;
  2. 不可能在非葉子結點命中;
  3. 非葉子結點相當於是葉子結點的索引(稀疏索引),葉子結點相當於是存儲(關鍵字)數據的數據層;
  4. 更適合文件索引系統;
1.1.2 b+樹索引原理

在這裏插入圖片描述
       如上圖所示,如果要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間因爲非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內存,發生第三次IO,同時內存中做二分查找找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。

1.2 索引優化的策略

  • 組合索引中的最左前綴匹配原則
  • 主鍵用作外鍵一定要建索引
  • 對 where,on,group by,order by 中出現的列使用索引
  • 儘量選擇區分度高的列作爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0
  • 對較小的數據列使用索引,這樣會使索引文件更小,同時內存中也可以裝載更多的索引鍵
  • 索引列不能參與計算,保持列“乾淨”,比如from_unixtime(create_time) = ’2019-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(’2019-05-29’);
  • 如果一個列是比較長的字符串,爲了加快這種長列的查詢速度,可以爲其建立前綴索引參考
  • 儘量的擴展索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可
  • 不要過多創建索引, 權衡索引個數與DML之間關係,DML也就是插入、刪除數據操作。這裏需要權衡一個問題,建立索引的目的是爲了提高查詢效率的,但建立的索引過多,會影響插入、刪除數據的速度,因爲我們修改的表數據,索引也需要進行調整重建
  • 對於like查詢,”%”不要放在前面。
    SELECT * FROMhoudunwangWHEREunameLIKE’後盾%’ – 走索引
    SELECT * FROMhoudunwangWHEREunameLIKE “%後盾%” – 不走索引
  • 查詢where條件數據類型不匹配也無法使用索引
  • 字符串與數字比較不使用索引;
    CREATE TABLEa(achar(10));
    EXPLAIN SELECT * FROMaWHEREa=“1” – 走索引
    EXPLAIN SELECT * FROM a WHERE a=1 – 不走索引
  • 正則表達式不使用索引,這應該很好理解,所以爲什麼在SQL中很難看到regexp關鍵字的原因

1.3 索引失效的場景舉例

【1】select * from book 當語句中沒有where子句時,那肯定是不走索引的。

【2】select * from book where name(書名,varchar類型) like ‘%中’,不走索引,只要%出現在第一個位置,那麼就不走索引。

【3】select name from book where sell_num(銷量,數值類型) > 100,\color{#5505ff}{數值類型進行不等操作時},不走索引。

【4】select name from book where sell_num/2=100,對銷量進行表達式操作或者是函數操作的,不走索引。

可以將sql語句改爲 select name from book where sell_num=200

【5】select name from book where author(作者,varchar類型)=‘tom’ or author=‘jack’,使用or進行連接的,也不走索引,\color{#ff05ff}{應改爲}select name from book where author=‘tom’ union all select name from book where author=‘jack’

【6】select name from book where translator(譯者,varchar類型) is null,即進行null值判斷,不走索引,可以在插入書籍信息的時候,譯者爲null的話就存入0,之後使用select name from book where translator='0’來查詢譯者爲null的書籍名。

【7】select name from book where version(版本,varchar類型)=1.0,即需要進行隱式類型轉換的,這裏是從varchar類型轉化爲浮點類型,不會走索引,將1.0改爲‘1.0’,則走索引。

【8】在組合索引中的查詢不滿足最左前綴原則時,查詢不走索引。如果我們對book表建立聯合索引(a,b,c),那麼以下的語句是走索引的

select * from book where a='x' ;

select * from book where a='x' and b='y' ;

select * from book where a='x' and c='z' ;

select * from book where a='x' and b='y' and c='z' ;

       而以下語句不走索引

select * from book where b='y' ;

select * from book where c='z' ;

select * from book where b='y' and c='z' ;

1.4 MySQL優化建議

1.4.1 查詢優化策略
  • limit [offset],[rows] 大數據量情況下的分頁優化
select * from t order by id LIMIT 90000,10;

       優化1:

// 主鍵子查詢:
select * from t where id >= (select id from t order by id limit 90000,1) limit 10;

       優化2:

// 主鍵子查詢:
select * from t where id between 90000 and 90010; 

       優化3:

// 主鍵子查詢:
select * from t where id in (90001,90002,90003,90004,90005,90006,90007,90008,90009,90010); 

       核心思想:只要能快速返回主鍵id就有希望優化limit , 按這樣的邏輯,百萬級的limit 應該在0.0x秒就可以分完。

  • 避免使用 select *
    需要什麼信息就查詢什麼信息,查詢的列多了,查詢速度肯定會慢。

  • 當只需要查詢出一條數據的時候,要使用 limit 1
    比如要查詢數據中是否有男生,只要查詢一條含有男生的記錄就行了,後面不需要再查了,使用Limit 1 可以在找到一條數據後停止搜索。

  • 優化where查詢
    ①避免在where子句中對字段進行表達式操作

比如:selectfromwhere age*2=36; 建議改成 selectfromwhere age=36/2

       ②應儘量避免在 where 子句中使用 !=或<> 操作符,否則將引擎放棄使用索引而進行全表掃描

       ③應儘量避免在 where 子句中對字段進行 null 值 判斷

       ④應儘量避免在 where 子句中使用 or 來連接條件

  • 慎用 in 和 not in
select id from t where num in(1,2,3) 
//建議改成
select id from t where num between 1 and 3
  • in和exists, not in和not exists的合理運用

       exists 和 not exists 的具體用法可參考該篇博客
       很多時候用 exists 代替 in 是一個好的選擇:如查詢語句使用了not in那麼內外表都進行全表掃描,沒用到索引,而not exists子查詢依然能用到表上索引,所以無論哪個表大,用not exists都比not in要快。

select num from a where num in(select num from b) 
//建議改成: 
select num from a where exists(select 1 from b where num = a.num)

       區分in和exists主要是造成了驅動順序的改變(這是性能變化的關鍵),如果是exists,那麼以外層表爲驅動表,先被訪問,如果是IN,那麼先執行子查詢。所以INEXISTS\color{#9e0eff}{IN適合於外表大而內表小的情況;EXISTS適合於外表小而內表大的情況。}
       關於not in和not exists,推薦使用not exists,不僅僅是效率問題,not in可能存在邏輯問題。

  • 表關聯時,被用來Join的字段應是相同的類型,且該字段應建索引。
    這樣,MySQL內部會啓動優化Join的SQL語句的機制。
1.4.2 其他優化策略
  • 建數據庫表時,給字段設置固定合適的大小
    字段不能設置的太大,設置太大就造成浪費,會使查詢速度變慢。
  • 字段儘量使用not null
  • 如果字段的取值是有限而且固定的,使用 ENUM ,而不是 VARCHAR
    比如“性別”,“國家”,“民族”, “省份”,“狀態”或“部門”。因爲在MySQL中,ENUM類型被當作數值型數據來處理,而數值型數據被處理起來的速度要比文本類型快得多。
  • 作必要的垂直分割
    將常用和有關係的字段放在相同的表中,把一張表的數據分成幾張表,這樣可以降低表的複雜度和字段的數目,從而達到優化的目的。
  • 用EXPLAIN 分析 SELECT 查詢
    使用EXPLAIN,可以幫助瞭解MySQL是如何處理sql語句的,可以查看到sql的執行計劃,就能更好的去了解sql語句的不足,然後優化。

1.5 SQL語句相關總結

  1. 查詢條件on、where、having區別
  • ON和WHERE的區別
    主要與限制條件起作用的時機有關,ON根據限制條件對數據庫記錄進行過濾,然後生產臨時表;而WHERE是在臨時表生產之後,根據限制條件從臨時表中篩選結果。
    因爲以上原因,ON和WHERE的區別主要有下:
    1)返回結果:在左外(右外)連接中,ON會返回左表(右表)中的所有記錄;而WHERE中,此時相當於inner join,只會返回滿足條件的記錄(因爲是從臨時表中篩選,會過濾掉不滿足條件的)。
    2)速度:因爲ON限制條件發生時間較早,臨時表的數據集要小,因此ON的性能要優於WHERE。
  • HAVING和WHERE的區別:
    也是與限制條件起作用時機有關,HAVING是在聚集函數計算結果出來之後篩選結果,查詢結果只返回符合條件的分組,HAVING通常出現在GROUP BY子句中。而WHERE是在計算之前篩選結果,如果聚集函數使用WHERE,那麼聚集函數只計算滿足WHERE子句限制條件的數據。
    在使用和功能上,HAVING和WHERE有以下區別:
    1)HAVING不能單獨出現,只能出現在GROUP BY子句之中;WHERE即可以和SELECT等其他子句搭配使用,也可以和GROUP BY子句搭配使用,WHERE的優先級要高於聚合函數高於HAVING。
    2)因爲WHERE在聚集函數之前篩選數據,HAVING在計算之後篩選分組,因此WHERE的查詢速度要比HAVING的查詢速度快。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章