mysql調優之sql調優

1. 無索引、索引失效導致慢查詢

1.1 無索引、索引失效導致慢查詢

如果在一張幾千萬數據的表中以一個沒有索引的列作爲查詢條件,大部分情況下查詢會非常耗時,這種查詢毫無疑問是一個慢 SQL 查詢。所以對於大數據量的查詢,我們需要建立適合的索引來優化查詢。雖然我們很多時候建立了索引,但在一些特定的場景下,索引還有可能會失效,所以索引失效也是導致慢查詢的主要原因之一。

1.2 鎖等待

        我們常用的存儲引擎有 InnoDB 和 MyISAM,前者支持行鎖和表鎖,後者只支持表鎖。

如果數據庫操作是基於表鎖實現的,試想下,如果一張訂單表在更新時,需要鎖住整張表,那麼其它大量數據庫操作(包括查詢)都將處於等待狀態,這將嚴重影響到系統的併發性能。

這時,InnoDB 存儲引擎支持的行鎖更適合高併發場景。但在使用 InnoDB 存儲引擎時,我們要特別注意行鎖升級爲表鎖的可能。在批量更新操作時,行鎖就很可能會升級爲表鎖。

MySQL 認爲如果對一張表使用大量行鎖,會導致事務執行效率下降,從而可能造成其它事務長時間鎖等待和更多的鎖衝突問題發生,致使性能嚴重下降,所以 MySQL 會將行鎖升級爲表鎖。還有,行鎖是基於索引加的鎖,如果我們在更新操作時,條件索引失效,那麼行鎖也會升級爲表鎖。

因此,基於表鎖的數據庫操作,會導致 SQL 阻塞等待,從而影響執行速度。在一些更新操作(insert\update\delete)大於或等於讀操作的情況下,MySQL 不建議使用 MyISAM 存儲引擎。

除了鎖升級之外,行鎖相對錶鎖來說,雖然粒度更細,併發能力提升了,但也帶來了新的問題,那就是死鎖。因此,在使用行鎖時,我們要注意避免死鎖。

1.3 不恰當的sql語句

習慣使用<select *> ,<select  count(*)>語句,在大數據表中使用<limit m,n>分頁查詢,以及對非索引字段進行排序等等。

2. 優化sql語句的步驟

通常,我們在執行一條 SQL 語句時,要想知道這個 SQL 先後查詢了哪些表,是否使用了索引,這些數據從哪裏獲取到,獲取到數據遍歷了多少行數據等等,我們可以通過 EXPLAIN 命令來查看這些執行信息。這些執行信息被統稱爲執行計劃。

2.1 通過explain分析sql執行計劃

假設現在我們使用 EXPLAIN 命令查看當前 SQL 是否使用了索引,先通過 SQL EXPLAIN 導出相應的執行計劃如下:

https://static001.geekbang.org/resource/image/bd/f8/bd11fa15122956719289afea2464eff8.jpg

下面對圖示中的每一個字段進行一個說明,從中你也能收穫到很多零散的知識點。

2.2 通過 Show Profile 分析 SQL 執行性能

上述通過 EXPLAIN 分析執行計劃,僅僅是停留在分析 SQL 的外部的執行情況,如果我們想要深入到 MySQL 內核中,從執行線程的狀態和時間來分析的話,這個時候我們就可以選擇 Profile。

Profile 除了可以分析執行線程的狀態和時間,還支持進一步選擇 ALL、CPU、MEMORY、BLOCK IO、CONTEXT SWITCHES 等類型來查詢 SQL 語句在不同系統資源上所消耗的時間。

3. 常用的 SQL 優化

3.1 優化分頁查詢

通常我們是使用<limit m,n> + 合適的 order by 來實現分頁查詢,這種實現方式在沒有任何索引條件支持的情況下,需要做大量的文件排序操作(file sort),性能將會非常得糟糕。如果有對應的索引,通常剛開始的分頁查詢效率會比較理想,但越往後,分頁查詢的性能就越差。

這是因爲我們在使用 LIMIT 的時候,偏移量 M 在分頁越靠後的時候,值就越大,數據庫檢索的數據也就越多。例如 LIMIT 10000,10 這樣的查詢,數據庫需要查詢 10010 條記錄,最後返回 10 條記錄。也就是說將會有 10000 條記錄被查詢出來沒有被使用到。

我們模擬一張 10 萬數量級的 order 表,進行以下分頁查詢:

通過 EXPLAIN 分析可知:該查詢使用到了索引,掃描行數爲 10020 行,但所用查詢時間爲 0.018s,相對來說時間偏長了。

https://static001.geekbang.org/resource/image/80/fe/80efe0ba8feb86baa20834fd48c302fe.jpg

https://static001.geekbang.org/resource/image/58/1c/58e2377b2adcded4c454d410bbab7d1c.jpg

利用子查詢優化分頁查詢

以上分頁查詢的問題在於,我們查詢獲取的 10020 行數據結果都返回給我們了,我們能否先查詢出所需要的 20 行數據中的最小 ID 值,然後通過偏移量返回所需要的 20 行數據給我們呢?我們可以通過索引覆蓋掃描,使用子查詢的方式來實現分頁查詢:


select  *

from `demo`.`order`

where id> (

select id from

`demo`.`order`

order by order_no limit 10000, 1

)  limit 20;

通過 EXPLAIN 分析可知:子查詢遍歷索引的範圍跟上一個查詢差不多,而主查詢掃描了更多的行數,但執行時間卻減少了,只有 0.004s。這就是因爲返回行數只有 20 行了,執行效率得到了明顯的提升。

https://static001.geekbang.org/resource/image/49/bb/492ddbbe2ef47d63a6dc797fd44c16bb.jpg

3.2 優化 SELECT COUNT(*)

COUNT() 是一個聚合函數,主要用來統計行數,有時候也用來統計某一列的行數量(不統計 NULL 值的行)。我們平時最常用的就是 COUNT(*) 和 COUNT(1) 這兩種方式了,其實兩者沒有明顯的區別,在擁有主鍵的情況下,它們都是利用主鍵列實現了行數的統計。

但 COUNT() 函數在 MyISAM 和 InnoDB 存儲引擎所執行的原理是不一樣的,通常在沒有任何查詢條件下的 COUNT(*),MyISAM 的查詢速度要明顯快於 InnoDB。

這是因爲 MyISAM 存儲引擎記錄的是整個表的行數,在 COUNT(*) 查詢操作時無需遍歷表計算,直接獲取該值即可。而在 InnoDB 存儲引擎中就需要掃描表來統計具體的行數。而當帶上 where 條件語句之後,MyISAM 跟 InnoDB 就沒有區別了,它們都需要掃描表來進行行數的統計。

如果對一張大表經常做 SELECT COUNT(*) 操作,這肯定是不明智的。那麼我們該如何對大表的 COUNT() 進行優化呢?

使用近似值

有時候某些業務場景並不需要返回一個精確的 COUNT 值,此時我們可以使用近似值來代替。我們可以使用 EXPLAIN 對錶進行估算,要知道,執行 EXPLAIN 並不會真正去執行查詢,而是返回一個估算的近似值。

增加彙總統計

如果需要一個精確的 COUNT 值,我們可以額外新增一個彙總統計表或者緩存字段來統計需要的 COUNT 值,這種方式在新增和刪除時有一定的成本,但卻可以大大提升 COUNT() 的性能。

3.3 優化 SELECT *

 

MySQL 常用的存儲引擎有 MyISAM 和 InnoDB,其中 InnoDB 在默認創建主鍵時會創建主鍵索引,而主鍵索引屬於聚簇索引,即在存儲數據時,索引是基於 B + 樹構成的,具體的行數據則存儲在葉子節點。

而 MyISAM 默認創建的主鍵索引、二級索引以及 InnoDB 的二級索引都屬於非聚簇索引,即在存儲數據時,索引是基於 B + 樹構成的,而葉子節點存儲的是主鍵值。

假設我們的訂單表是基於 InnoDB 存儲引擎創建的,且存在 order_no、status 兩列組成的組合索引。此時,我們需要根據訂單號查詢一張訂單表的 status,如果我們使用 select * from order where order_no='xxx’來查詢,則先會查詢組合索引,通過組合索引獲取到主鍵 ID,再通過主鍵 ID 去主鍵索引中獲取對應行所有列的值。

如果我們使用 select order_no, status from order where order_no='xxx’來查詢,則只會查詢組合索引,通過組合索引獲取到對應的 order_no 和 status 的值。

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