1. 程式人生 > >mysql 查詢優化 高效能mysql筆記

mysql 查詢優化 高效能mysql筆記

衡量查詢效能的三個指標
返回的行數。
掃描的行數:查詢所需要掃描的行數。
相應的時間:服務時間(執行sql的時間)+排隊時間(查詢在等待i/o或者在等待鎖)

優化慢查詢
採用索引覆蓋以及延時索引 
1重構查詢的方式
    可以複雜查詢分為多個簡單查詢
    切分查詢 : 
        如果要刪除一個表中的大量資料 sql 一次執行會鎖住很多資料佔用資源導致效能下降 可以切分為多次 
        執行一次刪除一萬比較高效 也可以增加間隔時間 從而把一次性的壓力分到一個很長的時間段中。
    分解關聯查詢
        將關聯查詢分解成多條簡單查詢。1.增加快取命中率(單表查詢容易命中)。2.減少了鎖競爭。3.減少冗餘資料的查詢。4.擴充套件性提高
重構表結構

mysql 查詢執行基礎
查詢流程

客戶端與服務區進行連線通訊
伺服器檢查是否有快取,快取如果命中則,直接返回快取中的結構。否則進行下一步
對sql進行解析預處理,再由優化器生成執行計劃
根據執行計劃,伺服器呼叫儲存引擎的API查詢
得到資料則快取並返回給客戶端


MySQL  客戶端和服務端通訊協議是“半雙工”的,客戶端傳送給伺服器和伺服器發給客戶端不能同時發生,這種協議讓MySQL通訊簡單快速,
但也就無法進行流量控制,一旦一端開始了,另一端是能等它結束。所以查詢語句很長的時候,引數max_allowed_packet(用於設定客戶端傳送到服務端的資料大小)就特別重要了。

MySQL 的查詢狀態 { SHOW FULL PROCESSLIST; } 
    Sleep 
    執行緒等待客戶端傳送新的請求
    Query 
    執行緒正在查詢或者正在將結果發給客戶端
    Locked 
    執行緒等待鎖釋放
    Analyzing and statistics 
    執行緒正在收集儲存引擎的統計資訊
    Copying to tmp table 
    執行緒正在執行查詢,並且將其結果集都複製到一個臨時表中,這種狀態一般要麼是在做Group By操作,要麼是檔案排序操作,或者是UNION操作。若後面還有on disk標記,則表示臨時表放在磁碟中
    Sorting result 
    執行緒正在對結果進行排序
    Sending data 
    表示多種情況,可能在多個狀態之間發傳輸資料,或者在生成結果集,或者在向客戶端返回資料
MySQL 的查詢快取
    在解析一個查詢前如果存在查詢快取則檢查使用者許可權有就返回 (快取以 查詢語句的hash值為key (對查詢語句大小敏感))
    
Mysql 查詢優化處理
mysql 通過sql語句關鍵詞生成sql執行計劃

mysql 優化器是基於成本的優化器 {SHOW  STATUS  LIKE  'last_query_cost'} 其中value 是成本(value個數據頁)   
mysql 評估成本時不考慮快取它假設讀取任何資料都是一次磁碟i/o
mysql 優化器的最優可能和我們想的最優(執行時間短)不一樣mysql是基於成本模型


MySQL(優化器)能處理的優化型別:

1. 重新定義關聯表的順序。
2. 將外連線轉化成內連線。
3. 使用等價變換規則。
    可以移除一些恆成立和一些恆不成立的判讀。例如(5 = 5 AND a > 5) 會被改寫為 a > 5
4. 優化COUNT()、MIN() 和 MAX()
        例如找到某一列的最小值,這一列如果有索引只需要查詢對應B-Tree索引最左端的記錄。
5. 覆蓋索引掃描
        索引中的列包含所有查詢中需要的列的時候,只需要使用索引返回資料,不需要搜尋資料行
6. 子查詢優化
    MySQL可以將子查詢轉換一種效率更高的形式,從而減少多個查詢多次對資料進行訪問。
7. 提前終止查詢
    當發現已經滿足查詢需求的時候,MySQL總是能夠立刻終止查詢。
8. 等值傳播
    如果兩個列的值通過等式關聯,那麼MySQL能夠把其中一個列的WHERE條件傳遞到另一列中 select * from a inner join b on a.id=b.id where a.id >1 
    會應用到兩個表的關聯查詢中  a where id>1 inner join  b where id>1。
9. 列表IN()的比較
   MySQL中IN()列表中的資料線進行排序,然後通過二分查詢的方式來確定列表中的值是否滿足條件,是O(lgn)級別的操作,等價轉換成OR查詢的複雜度是O(n)
   
   
   
MySQL如何執行關聯?

MySQL中關聯不僅僅是一個查詢需要到兩個表匹配才叫關聯,每一個查詢,每一個片段(包括子查詢,甚至基於單表的SELECT)都可能是關聯。

MySQL採用巢狀迴圈關聯操作進行關聯。
。 
2. 根據各個表匹配的行,返
具體做法:1. MySQL先從一個表中迴圈取出單條資料,然後巢狀迴圈到下一個表中尋找匹配的行,依次下去,直到所有表中匹配的行為止回查詢中需要的各個列。
MySQL會嘗試在最後一個關聯表中找到所有匹配的行,如果最後一個關聯表無法找到更多的行以後,MySQL返回上一層次關聯表。看是否能夠找到,依次類推。
mysql 會重新調整查詢的關聯表順序   找到最優的順序
STRAIGHT_JOIN  可以控制關聯的順序比如 form t STRAIGHT_JOIN t2 on t.id=t2.id
n張表會有 n*(n-1)....*1中順序
當關聯表的數量超過 optimizer_search_depth 的預設值(62) 則會 使用窮盡查詢
    [1] 所謂貪婪式,是在對問題求解時,不從整體最優上考慮,以當前情況為基礎根據某個優化作為最優選擇,這導致貪婪演算法對問題不都能得到整體最優解,但能產生整體最優解或者是整體最優解的近似解

    [2] 所謂窮盡式,是把各種組合全部執行一遍,如A/B/C/D四個表,兩兩全部組合為AB/AC/AD/BC/BD/CD,三三組合為ABC/ABD/ACD/BCD,四個組合為ABCD;無一遺漏
    
    
    
    
排序
mysql 有兩種排序演算法

    兩次傳輸:第一次去除要排序的列進行排序 第二次根據排序好的行再去表中去除所有的資料行(此時會產生大量隨機io) myIsam 非常依賴作業系統所以myisam成本更高,當時這樣有利於排序緩衝區中可以容納更多的資料
    單次傳輸:單次傳輸會把所有資料去除進行排序這樣只需要一次順序io無需隨機io,缺點資料量大佔空間 會使更多的排序塊合併
    可以通過max_lenght_for_sort_data 的大小來設定使用那個 

mysql 查詢出第一條結果時就會逐步返回給客戶端如果需要快取此時也會快取在快取中 這樣可以節省空間

MySQL 查詢優化器的侷限性

1. 關聯子查詢

MySQL的子查詢實現得非常糟糕。最糟糕的一類查詢是WHERE條件中包含IN()的子查詢語句。 

SELECT * FROM sakia.film WHERE film_id IN( SELECT  film_id FROM sakia.film_actor WHERE actor_d = 1)
我們希望MySQL能夠先執行內層子查詢,這個子查詢通過索引來查詢,應該會很快,事實上,MySQL 並不這麼幹,它會把從查詢這樣優化:


SELECT * FROM sakia.film WHERE EXIST (SELECT * FROM sakia.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id);


MySQL把外層壓人到子查詢中處理,如果外層的表是非常大的表的話,這個查詢的效能就會非常的糟糕。

1 改進方案:SELECT film.* FROM sakila.film INNER JOIN sakila.film_actor USING(film_id) WHERE actor_id = 1;

2 SELECT *FROM a WHERE  id in (select id from b)當a表中的資料量小b表中的資料量大可以優化為     SELECT *FROM a WHERE EXISTS( SELECT * FROM b WHERE a.id = b.id); 

2 union 查詢 外部條件無法應用到內部 (select * from a ) union all (select * from a2)  limit 10 無法將limi 10 應用到 內部如

(select * from a limit 10 ) union all (select * from a2 order by id) 如果可以應用到內部就無需再去查詢下一個union的表了  無法應用到內部會產生臨時表再去limi 10 這樣成本變高。


等值傳遞
如果兩個表關聯 用的是id  然後又有一個select a inner join b on a.id=b.id and  id in(1,2,3,3....很大) 此時這個in 會應用到兩個表的查詢中 會使得查詢變慢

索引合併優化
mysql 不支援多核併發查詢 (有一個查詢  select * from t  這個表資料很多 其他資料庫可以把表分成多份 多個cpu 去讀取 mysql 只支援一個單核去讀取)
mariadb 支援hash索引其他的不支援
mysql 不支援鬆散索引掃描 如 index(a,b,c) ; select * from t where b=1 無法使用索引  但是  
     select max(b) from t group by a 或者 select max(c) from t group by a ,b 但是select max(b) from t group by a ,c  是不行的。
     explan 會有   user_index_for_group_by  鬆散索引
     最大值最小值優化 假如你要獲取的最大值或最小值的列是有序的  可以  select a from t limit 1 即可獲取最小值
     在統一表查詢更新需要給表起一個別名 update t inner join (select id,b from t) t2 on t2.id=t.id set t.a=t2.b  
     

6.6查詢優化器的提示hint
在查詢中加入相應的提示,可控制該查詢的執行計劃

hight_priority 和 low_priority  執行順序相關

       當多個語句同時訪問某一個表時,哪些語句的優先順序高、低

        h*:select語句,mysql將其放在表佇列的前面;insert語句,簡單抵消全域性low_priority設定的影響

        l*:讓語句一直等待

        對使用表鎖的引擎有效,不要在innodb或有細粒度鎖機制 併發控制的引擎中使用:導致併發插入被禁

delayed:對insert replace有效

       mysql 將 使用該提示的語句 立即返回給客戶端,將插入的行資料放入到快取區 在表空閒時批量將資料寫入

       不是所有的儲存引擎都支援,會導致last_insert_id()無法正常工作 比較適合日誌表的大批量插入客戶端無需等待。

 

straight_join: 關聯順序相關

        放在select後:查詢中所有表按在語句中順序進行關聯

        可放任何兩個關聯表的名字間:固定器前後兩表的關聯順序

        可使explain語句查優化器選擇的關聯順序,然後使用該提示重寫查詢,在看順序,升級需重審視查詢

sql_small_result和sql_big_result:select

        告訴優化器對group by或distinct查詢如何使用臨時表及排序

       small:告訴器結果集會很小,可將結果集放在記憶體的索引臨時表,避免排序

       big:告訴器結果集可能非常大,建議使用磁碟臨時表做排序操作

sql_buffer_result:

       告訴器將查詢結果放入到一臨時表,儘可能快地釋放表鎖(這樣會佔用更多的服務端記憶體)

 

sql_cache和sql_no_cache:

        這個結果集是否應該快取在查詢快取中

sql_calc_found_rows:

        讓返回的結果集包含更多資訊,不應該使用,可通found_row()獲得這個值

for update和lock in share mode:只對實現了行級鎖的引擎有效

         控制select語句鎖機制,會對符合查詢條件的資料行加鎖,避免使用,鎖掙用對於沒有用到id的覆蓋索引查詢無效 因為沒有用到id  因為行的資訊儲存在主鍵中。

use index 、ignore index和force index:

         使用或不使用哪些索引來查詢記錄

         5.1及後可通過for order by 和for group by指定是否對排序和分組有效

         force index同use index(除force index會告訴優化器全表掃描成本高於索引掃描)

 

5.6新增:

1、optimizer_search_depth:控制優化器在窮舉執行計劃時的限度

2、optimizer_prune_level:預設開啟,讓優化器據需要掃描的行數來決定是否跳過某些執行計劃

3、optimizer_switch:包含些開啟/關閉優化器特性的標誌位

前兩個引數控制優化器走一些捷徑:讓優化器處理複雜sql時仍高效,但也可能錯過些真正最優的執行計劃

自定義設定的  優化器提示  可能使新版的優化策略失效:耍小聰明 不太好 


6.7優化特定型別的查詢
多數優化技巧和特定版本有關,注意版本

6.7.1優化count查詢
作用:

myIsam
 適合因為


1、count(列) 統計某個列的行數,如果count()指定列或列的表示式,統計的是這個表示式有值的結果數(不統計null);

2、count(*)  統計結果集的行數。

關於MyISAM:無where的count(*)非常快  因為MyISAM 引擎儲存了表的總行數 所以快  當有where 條件的結果集就和其他一樣了

     取小的範圍: 比如要取得id>5的資料 那麼可以用總資料量減去 id<5的資料量(MyISAM 引擎)、
     近似值: explan 查詢然後獲得 記錄數的近似值 
     獲取多個count 可以用sum  例如:select  SUM(IF((age=18),1, 0))  AS age18 ,SUM(IF((age=19),1, 0))  AS age19  from table 
     彙總表:把資料定時查詢放到彙總表中
     快速簡單和精確只能實現其中兩個
6.7.2優化關聯查詢
    1、確保on或using子句中列上有索引,無其他理由,只在關聯順序的第二張表相應列建索引

    2、group by 和order by 的表示式只涉及一個表中的列,才能使用索引

    3、版本升級,注意關聯語法、運算子優先順序等可能發生變化的地方

 

6.7.3優化子查詢
    儘可能使用關聯查詢代替,mysql5.6及更新的版本或mariadb可忽略

6.7.4優化group by和distinct
可使用索引來優化

無法使用索引時:

    group by使用臨時表或檔案排序做分組,可通過提示sql_big_result和sql_samll_result‘指揮’優化器

    對關聯查詢做分組,且按照查詢表的某個列進行分組,採用查詢表的標識列(on t.id=t2.id  id就是標識列)來分組的效率會更高

優化group by with rollup:

    如對返回分組結果再做一次超級聚合,可使用with rollup實現,儘可能將with rollup功能轉移到程式中處理
    group by a,b with rollup  其中 with rollup 會按照 a分組然後把資料加入到結果集
    

6.7.5優化limit分頁  limit  0,10 這樣會掃描所有的記錄然後提取10條 儘量避免  因該用 limit 10
偏移量很大但是要的資料很少,優化 要麼在頁面中限制分頁的數量 要麼優化大偏移量的效能

1、儘可能使用索引覆蓋掃描,據需要做一次關聯操作再返回需要的列

2、延遲關聯:提效率,掃描儘可能少的頁,獲取要訪問的記錄後據關聯列回原表查詢需的列select a,b from t inner join (select id from t where name='ss'limit 5)t2 where t2.id=t.id

3、將limit查詢轉換為已知位置的查詢,讓mysql通過範圍掃描獲得對應的結果

        預先計算出邊界值假如id是連續的  form table where id>10 limit 10 下一次就是 id>20 limit 10 

 

6.7.6優化SQL_CALC_FOUND_ROWS:
     分頁時,技巧在limit語句中加SQL_CALC_FOUND_ROWS提示,可獲得去掉limit後滿足條件的行數,作為分頁的總數,會掃描all滿足條件的行、拋棄不需要的行,代價可能有點高

     1、將具體的頁數換成“下一頁”按鈕,每頁顯示20條但查詢21條哦,21條存在則有資料,否則沒有資料(哈哈~優秀優秀)

     2、先獲取快取較多資料,每次分頁從快取中獲取:程式據結果集大小採取不同策略,<1000 顯示all分頁連結 >1000 靈活設計     比 找到all再拋棄效率高

     使用explain結果中的rows值作為結果集總數的近似值,需精確結果再count(*)

 

6.7.7使用者union查詢
通過建立、填充臨時表來執行union查詢,很多優化策略在union中無法很好使用,
需要手工將where 、limit、 order by 等下推到union各個子查詢中,讓優化器充分利用這些條件進行優化

除非需要伺服器消除重複行,否則一定要使用union all ,無all ,mysql會給臨時表加上distinct:導致對臨時表做唯一性查詢,代價高

6.7.8靜態查詢分析
percona toolkit 的pt-query-advisor能解析查詢日誌、分析查詢模式、給出all可能會有潛在問題的查詢、給出建議