如何進行 MySQL 慢查詢優化?

爲什麼查詢速度會慢

 

  1.慢是指一個查詢的響應時間長。一個查詢的過程:

 


  • 客戶端發送一條查詢給服務器


  • 服務器端先檢查查詢緩存,如果命中了緩存,則立可返回存儲在緩存中的結果。否則進入下一個階段


  • 服務器端進行SQL解析、預處理,再由優化器生成對應的執行計劃。


  • MySQL根據優化器生成的執行計劃,調用存儲引擎的API來執行查詢。


  • 將結果返回給客戶端


 

  2.數據訪問

 


  • 是否向數據庫請求了不需要的數據


  • 是否掃描額外的記錄


 

  3.查詢的方式

 


  • 一個複雜的查詢還是多個簡單的查詢


  • 切分查詢(將大查詢切分成小查詢,循環完成小查詢)


  • 分解關聯查詢


 

慢查詢分析

 

  問題SQL

 

    把複雜的SQL分成多個簡單SQL並執行,查看具體那個字段會慢,區分度不高。

 

  EXPLAIN

 

    顯示SQL如何使用索引的執行計劃。

 

    執行計劃的參數:

 

table 顯示這一行的數據是關於哪張表的

 

type 顯示連接使用了何種類型。從最好到最差的連接類型爲const、eq_reg、ref、range、indexhe和ALL

 

possible_keys 顯示可能應用在這張表中的索引。如果爲空,沒有可能的索引。可以爲相關的域從WHERE語句中選擇一個合適的語句 

 

key 實際使用的索引。如果爲NULL,則沒有使用索引。很少的情況下,MYSQL會選擇優化不足的索引。這種情況下,可以在SELECT語句中使用USE INDEX(indexname)來強制使用一個索引或者用IGNORE INDEX(indexname)來強制MYSQL忽略索引 

 

key_len  使用的索引的長度。在不損失精確性的情況下,長度越短越好 

 

ref 顯示索引的哪一列被使用了,如果可能的話,是一個常數

 

rows 掃描請求數據的行數 

 

Extra 關於MYSQL如何解析查詢的額外信息

 

  PROFILE

 

    顯示SQL執行消耗系統資源的信息。

 

查詢執行的過程

 

 

MySQL客戶端/服務器通信協議是\u201C半雙工\u201D的。客服端/服務器端都可以向對方發送數據,但不能同時發生。所以我們無法也無須將一個消息切成小塊獨立來發送。

 

這種協議沒辦法進行流量控制。

 

客戶端發送請求的數據包大小由參數max_allowed_packet限制。如果查詢太大,服務端會拒絕接受更多的數據並拋出相應的錯誤。

 

服務器端返回的多個數據包,客戶端必須完整接受。

 

 

  1.查詢狀態 SHOW FULL PROCESSLIST      

 

 


mysql>SHOW FULL PROCESSLIST; Id User Host db Command Time State Info ------ ------ --------------- ------------ ------- ------ ------ ----------------------- 1 root localhost:61316 laravel_blog Query 0 (NULL) show FULL processlist 2 root localhost:61319 (NULL) Sleep 94 (NULL)

 

 

     對於一個連接,或者說一個線程,任何時刻都有一個狀態,該狀態表示了MySQL當前正在做什麼。

 

 

 

  2.查詢緩存  

 

 


-- 查看緩存是否開啓 (query_cache_type 爲 ON 表示已經開啓mysql> show variables like "%query_cache%"; +------------------------------+----------+ | Variable_name | Value | +------------------------------+----------+ | have_query_cache | YES | | query_cache_limit | 1048576 | | query_cache_min_res_unit | 4096 | | query_cache_size | 20971520 | | query_cache_type | ON | | query_cache_wlock_invalidate | OFF | +------------------------------+----------+

 

 

    檢查sql是否命中緩存。命中則檢查一次用戶權限後返回,這個檢查是通過一個對大小寫敏感的哈希查找實現的。兩次查詢只要有一個字節的不同就會失敗。否則將進入下一個階段。

 

    當sql中有不確定的數據時,則不會被緩存。例如用戶自定義函數、存儲函數、用戶變量、臨時表、mysql庫中的系統表,其查詢結果都不會被緩存。

 

  

 

 3.查詢優化

 

    語法解析器和預處理

 

      MySQL通過關鍵字將sql語句進行解析,並生成一顆對應的解析樹。這個過程解析器主要通過語法規則來驗證和解析。比如sql中是否使用了錯誤的關鍵字或者關鍵字的順序是否正確等。預處理則會根據MySQL規則進一步檢查解析樹是否合法。比如檢查要查詢的數據表和數據列是否存在等。

 

    查詢優化器

 

      經過前面的步驟生成的語法樹被認爲是合法的了,並且由優化器將其轉化成查詢計劃。多數情況下,一條查詢可以有很多種執行方式,最後都返回相應的結果。優化器的作用就是找到這其中最好的執行計劃。

 

      MySQL使用基於成本的優化器,通過計算成本選擇其中最小的一個。通過SHOW STATUS LIKE "Last_query_cost";查看成本。成本的最小單位是隨機讀取一個4K數據頁的成本。

 

      MySQL的查詢優化器是一個非常複雜的部件,它使用了非常多的優化策略來生成一個最優的執行計劃:

 


  •    


    • 重新定義關聯表的順序


    • 將外連接轉化成內連接


    • 使用等價變換規則


    • 優化count()、min()、max()


    • 預估並轉化爲常數表達式


    • 覆蓋索引描述


    • 子查詢優化


    • 提前終止查詢


    • 等值傳播


    • 列表IN()的比較    



    •      



 

      上面列舉了一些,隨着MySQL的不斷髮展,優化器使用的優化策略也在不斷的進化。 

 

    查詢執行引擎

 

      在完成解析和優化階段以後,MySQL會生成對應的執行計劃,查詢執行引擎根據執行計劃給出的指令逐步執行得出結果。整個執行過程的大部分操作均是通過調用存儲引擎實現的接口來完成,這些接口被稱爲handler API。查詢過程中的每一張表由一個handler實例表示。實際上,MySQL在查詢優化階段就爲每一張表創建了一個handler實例,優化器可以根據這些實例的接口來獲取表的相關信息,包括表的所有列名、索引統計信息等。存儲引擎接口提供了非常豐富的功能,但其底層僅有幾十個接口,這些接口像搭積木一樣完成了一次查詢的大部分操作。

 

    返回結果

 

      查詢執行的最後一個階段就是將結果返回給客戶端。即使查詢不到數據,MySQL仍然會返回這個查詢的相關信息,比如該查詢影響到的行數以及執行時間等。

 

      如果查詢緩存被打開且這個查詢可以被緩存,MySQL也會將結果存放到緩存中。

 

      結果集返回客戶端是一個增量且逐步返回的過程。有可能MySQL在生成第一條結果時,就開始向客戶端逐步返回結果集了。這樣服務端就無須存儲太多結果而消耗過多內存,也可以讓客戶端第一時間獲得返回結果。需要注意的是,結果集中的每一行都會以一個滿足①中所描述的通信協議的數據包發送,再通過TCP協議進行傳輸,在傳輸過程中,可能對MySQL的數據包進行緩存然後批量發送。

 

 

 

性能優化

 

  優化count()查詢

 

    count()是一個特殊的函數。可以統計行數、某個列值的數量。在統計列值時要求列值是非空的(不統計NULL)。在統計行數時count(*)不會被擴展成所有的列,而是忽略所有的列。這樣寫意義清晰,性能好。

 


  •    


    • 在不要求完全精確時,EXPLAIN返回一個優化器估算的近似值


    • 快速,精確和實現簡單,三者永遠只能滿足其二,必須舍掉其中一個。增加一個彙總表也是



 

  

 

  優化LIMIT分頁

 

    LIMIT 10000,20這樣的查詢,MySQL需要查詢10020條記錄後返回最後20記錄。一般優化爲WHERE id>10000 LIMIT 20。其他優化關聯一個冗餘表,冗餘表只包含主鍵列和需要排序的數據列。

 

  

 

  數據類型優化

 

    選擇數據類型的原則:更小的通常更好、簡單就好、儘量避免NULL。

 

    更小的數據類型通常會更快,因爲佔用更少的磁盤、內存和CPU緩存。

 

    簡單的數據類型需要更少的CPU週期。例:int比char的操作代價低。

 

  這裏總結幾個可能容易理解錯誤的技巧:

 





    1. 通常來說把可爲NULL的列改爲NOT NULL不會對性能提升有多少幫助,只是如果計劃在列上創建索引,就應該將該列設置爲NOT NULL。



    2. 對整數類型指定寬度,比如INT(11),沒有任何卵用。INT使用32位(4個字節)存儲空間,那麼它的表示範圍已經確定,所以INT(1)和INT(20)對於存儲和計算是相同的。



    3. UNSIGNED表示不允許負值,大致可以使正數的上限提高一倍。比如TINYINT存儲範圍是-128 ~ 127,而UNSIGNED TINYINT存儲的範圍卻是0 - 255。



    4. 通常來講,沒有太大的必要使用DECIMAL數據類型。即使是在需要存儲財務數據時,仍然可以使用BIGINT。比如需要精確到萬分之一,那麼可以將數據乘以一百萬然後使用BIGINT存儲。這樣可以避免浮點數計算不準確和DECIMAL精確計算代價高的問題。



    5. TIMESTAMP使用4個字節存儲空間,DATETIME使用8個字節存儲空間。因而,TIMESTAMP只能表示1970 - 2038年,比DATETIME表示的範圍小得多,而且TIMESTAMP的值因時區不同而不同。



    6. 大多數情況下沒有使用枚舉類型的必要,其中一個缺點是枚舉的字符串列表是固定的,添加和刪除字符串(枚舉選項)必須使用ALTER TABLE(如果只只是在列表末尾追加元素,不需要重建表)。



    7. schema的列不要太多。原因是存儲引擎的API工作時需要在服務器層和存儲引擎層之間通過行緩衝格式拷貝數據,然後在服務器層將緩衝內容解碼成各個列,這個轉換過程的代價是非常高的。如果列太多而實際使用的列又很少的話,有可能會導致CPU佔用過高。



    8. 大表ALTER TABLE非常耗時,MySQL執行大部分修改表結果操作的方法是用新的結構創建一個張空表,從舊錶中查出所有的數據插入新表,然後再刪除舊錶。尤其當內存不足而表又很大,而且還有很大索引的情況下,耗時更久。當然有一些奇技淫巧可以解決這個問題,有興趣可自行查閱。



 

   索引的設計

 

    索引的優點:大大減少了服務器需要掃描的數據量、幫主服務器避免排序和臨時表、可以將隨機I/O變爲順序I/O;

 

 

\u201C三星系統\u201D:索引將相關的記錄放到一起則獲得一星;如果索引中的數據順序和查找中的排序順序一致則獲得二星;如果索引中的列包含了查詢中需要的全部列則獲得三星。

 

 


  •    


    • 獨立的列:索引列不能是表達式的一部分,也不能是函數的參數。索引選擇區分度高。


    • 前綴索引:如果列很長,通常索引開始的部分字符,可以大大節約索引空間,從而提高索引效率。


    • 聯合索引:當多個索引and時,通常是一個包含所有相關列的索引好過多個單獨索引;當多個索引or時,通常是分開查詢好過單次查詢。



 

     注:如果在EXPLAIN中看到有索引合併(Extra字段出現Using union),應該好好檢查一下查詢和表的結構,看是不是已經是最優的。

品略圖書館 http://www.pinlue.com/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章