MySQL 性能調優——SQL 查詢優化

如何設計最優的數據庫表結構,如何創建最好的索引,以及如何擴展數據庫的查詢,這些對於高性能來講都是必不可少的。可是隻有這些還不夠,要得到良好的數據庫性能,咱們還要設計合理的數據庫查詢,若是查詢設計的很糟糕,即便增長再多的只讀從庫,表結構設計的再合理,索引再合適,只要查詢不能使用到這些東西,也沒法實現高性能的查詢。因此說查詢優化,索引優化,庫表結構優化須要齊頭並進。mysql

在進行庫表結構設計時,咱們要考慮到之後的查詢要如何的使用這些表,一樣,編寫 SQL 語句的時候也要考慮到如何使用到目前已經存在的索引,或是如何增長新的索引才能提升查詢的性能。sql

想要對存在性能問題的查詢進行優化,須要可以找到這些查詢,下面先看下如何獲取有性能問題的 SQL。數據庫

1.獲取有性能問題的SQL

獲取有性能問題的 SQL 的三種方法:緩存

  • 經過用戶反饋獲取存在性能問題的 SQL;
  • 經過慢查日誌獲取存在性能問題的 SQL;
  • 實時獲取存在性能問題的 SQL;

1.慢查詢日誌獲取性能問題SQL

MySQL 慢查詢日誌是一種性能開銷比較低的獲取存在性能問題 SQL 的解決方案,其主要的性能開銷在磁盤 IO 和存儲日誌所須要的磁盤空間。對於磁盤 IO 來講,因爲寫日誌是順序存儲,開銷基本上忽略不計,因此主要須要關注的仍是磁盤空間。服務器

MySQL 提供瞭如下參數用於控制慢查詢日誌:併發

slow_query_log:是否啓動慢查詢日誌,默認不啓動,on 啓動;
slow_query_log_file:指定慢查詢日誌的存儲路徑及文件,默認狀況下保存在 MySQL 的數據目錄中;
long_query_time:指定記錄慢查詢日誌 SQL 執行時間的閾值,單位秒,默認 10 秒,一般對於一個繁忙的系統來講,改成0.001秒比較合適;
log_queries_not_using_indexes:是否記錄未使用索引的 SQL;

和二進制日誌不一樣,慢查詢日誌會記錄全部符合條件的 SQL,包括查詢語句、數據修改語句、已經回滾的 SQL。函數

慢查詢日誌中記錄的內容:工具

# Query_time: 0.000220             //執行時間,能夠精確到毫秒,220毫秒
# Lock_time: 0.000120              //所使用鎖的時間,能夠精確到毫秒
# Rows_sent: 1                     //返回的數據行數
# Rows_examined: 1                 //掃描的數據行數
SET timestamp=1538323200;          //執行sql的時間戳
SELECT c FROM test1 WHERE id =100;    //sql

一般狀況下,在一個繁忙的系統中,短期內就能夠產生幾個 G 的慢查詢日誌,人工檢查幾乎是不可能的,爲了快速分析慢查詢日誌,必須藉助相關的工具。性能

經常使用的慢查詢日誌工具:優化

一、mysqldumpslow:一個經常使用的,MySQL 官方提供的慢查詢日誌分析工具,隨着 MySQL 服務器的安裝而被安裝。能夠彙總除查詢條件外其餘徹底相同的 SQL,並將分析結果按照參數中所指定的順序輸出。

二、pt-query-digest:用於分析 MySQL 慢查詢的一個工具。

2.實時獲取性能問題SQL

爲了更加及時的發現當前的性能問題,咱們還能夠經過實時的方法來獲取有性能問題的 SQL。最方便的一種方法就是利用 MySQL information_schema 數據庫下的 PROCESSLIST 表來實現實時的發現性能問題 SQL。例以下面這條 SQL 表示查詢出當前服務器中執行時間超過 1 秒的 SQL:

SELECT id,user,host,db,command,time,state,info FROM information_schema.PROCESSLIST WHERE TIME>=1

而後咱們能夠經過腳本週期性的來執行這條 SQL,實時的發現哪些 SQL 執行的是比較慢的。

2.SQL的解析預處理及生成執行計劃

找到了那些查詢存在性能問題的 SQL,那麼下面咱們就看下,爲何這些 SQL 會存在性能問題?

爲了搞清楚這個問題,咱們先來看下 MySQL 服務器處理一條 SQL 請求所須要經歷的步驟都有哪些:

1.客戶端經過 MySQL 的接口發送 SQL 請求給服務器,這一步一般不會影響查詢性能;
2.MySQL 服務器檢查是否能夠在查詢緩存中命中該 SQL,若是命中,則當即返回存儲在緩存中的結果,不然進入下一階段;
3.MySQL 服務器進行 SQL 解析,預處理,再由 SQL 優化器生成對應的執行計劃;
4.根據執行計劃,調用存儲引擎 API 來查詢數據;
5.將結果返回給客戶端。

這就是 MySQL 服務器處理查詢請求的整個過程。在第二到第五步,都有可能對查詢的響應速度形成影響,下面來分別看下這些過程可能對查詢的響應速度有影響的因素都有些什麼:

在解析查詢語句前,若是查詢緩存是打開的,那麼 MySQL 優先檢查這個查詢是否命中查詢緩存中的數據,這個檢查是經過一個對大小寫敏感的 Hash 查找實現的。因爲 Hash 查找只能進行全值匹配,因此請求的查詢和緩存中的查詢就算只有一個字節的不一樣,那麼也不會匹配到緩存中的結果,這種狀況下,查詢就會進入到下一階段處理。若是正好命中查詢緩存,在返回查詢結果以前,MySQL 就會檢查用戶權限,也是無需解析 SQL 語句的,由於在查詢緩存中,已經存放了當前查詢所須要訪問的表的信息,若是權限沒有問題,MySQL 會跳過全部的其餘階段,直接從緩存中拿到結果,並返回給客戶端,這種狀況下查詢是不會被解析的,也不會生成查詢計劃,不會被執行。

能夠發現,從查詢緩存中直接返回結果並不容易。

查詢緩存對 SQL 性能的影響:

  • 若是查詢緩存,一旦數據更新,都要對緩存中數據進行刷新,影響性能;
  • 每次在查詢緩存中檢查 SQL 是否被命中,都要對緩存加鎖,影響性能;

對於一個讀寫頻繁的系統來講,查詢緩存極可能會下降查詢處理的效率。因此在這種狀況下建議你們不要使用查詢緩存。

對查詢緩存影響的一些系統參數:

query_cache_type: 設置查詢緩存是否可用,能夠設置爲ON、OFF、DEMAND,DEMAND表示只有在查詢語句中使用 SQL_CACHE 和 SQL_NO_CACHE 來控制是否須要緩存。
query_cache_size: 設置查詢緩存的內存大小,必須是1024字節的整數倍。 
query_cache_limit: 設置查詢緩存可用存儲的最大值,若是知道很大不會被緩存,能夠在查詢上加上 SQL_NO_CACHE 提升效率。
query_cache_wlock_invalidate: 設置數據表被鎖後是否返回緩存中的數據,默認關閉。
query_cache_min_res_unit: 設置查詢緩存分配的內存塊最小單位。

對於一個讀寫頻繁的系統來講,能夠把 query_cache_type 設置爲 OFF,而且把 query_cache_size 設置爲 0。

當查詢緩存未啓用或者未命中則會進入下一階段,也就是須要將一個 SQL 轉換成一個執行計劃,MySQL 再依據這個執行計劃和存儲引擎進行交互,這個階段包括了多個子過程:解析 SQL,預處理,優化 SQL 執行計劃。在這個過程當中,出現任何錯誤,好比語法錯誤等,都有可能停止查詢的過程。

在語法解析階段,主要是經過關鍵字對 MySQL 語句進行解析,並生成一棵對應的 「解析樹」。這一階段,MySQL 解析器將使用 MySQL 語法規則驗證和解析查詢,包括檢查語法是否使用了正確的關鍵字、關鍵字的順序是否正確等。

預處理階段則是根據 MySQL 規則進一步檢查解析樹是否合法,好比檢查查詢中所涉及的表和數據列是否存在、檢查名字或別名是否存在歧義等。

若是語法檢查所有都經過了,查詢優化器就能夠生成查詢計劃了。

會形成 MySQL 生成錯誤的執行計劃的緣由:

  • 統計信息不許確;
  • 執行計劃中的成本估算不等同於實際的執行計劃的成本;
  • MySQL 查詢優化器所認爲的最優可能與你所認爲的最優不同;
  • MySQL 從不考慮其餘併發的查詢,這可能會影響當前查詢的速度;
  • MySQL 有時候也會基於一些固定的規則來生成執行計劃;
  • MySQL 不會考慮不受其控制的成本,例如存儲過程、用戶自定義的函數等。

MySQL 的查詢優化器能夠優化的 SQL 類型:

  • 從新定義表的關聯順序,優化器會根據統計信息來決定表的關聯順序;
  • 將外鏈接轉化爲內鏈接,好比 where 條件和庫表結構均可能讓一個外鏈接等價於內鏈接;
  • 使用等價變換規則,好比 (5=5 and a>5) 將被改寫爲 a>5;
  • 利用索引和列是否爲空來優化 count()、min() 和 max() 等聚合函數;
  • 將一個表達式轉換爲常數表達式;
  • 使用等價變換規則,好比覆蓋索引,當 MySQL 查詢優化器發現索引中的列包含全部查詢中所須要的信息的時候,MySQL
    就能使用索引返回須要的數據;
  • 子查詢優化,好比把子查詢轉換爲關聯查詢,減小表的查詢次數;
  • 提早終止查詢;
  • 對 in() 條件進行優化。

以上這些就是 MySQL 查詢優化器能夠自動對查詢所作的一些優化。通過查詢優化器改寫後的 SQL,查詢優化器會對其生成一個 SQL 執行計劃,而後 MySQL 服務器就能夠根據執行計劃調用存儲引擎的 API,經過存儲引擎獲取數據了。

3.肯定查詢處理各個階段的耗時

SQL 查詢優化的主要目的就是減小查詢所消耗的時間,加快查詢的響應速度。下面來介紹如何度量查詢處理各個階段所消耗的時間。

對於一個存在性能問題的 SQL 來講,必須知道在查詢的哪一階段消耗的時間最多,而後纔能有針對性的進行優化。度量查詢處理各個階段所消耗的時間,經常使用的方法有兩種:

  • 使用 profile;
  • 使用 performance_schema;

4.特定SQL的查詢優化

前面介紹的方法,已經能夠獲取一個存在性能問題的 SQL 和獲取一個 SQL 在執行的各個階段所消耗的時間了。獲得這些信息後,咱們就能夠針對性的對 SQL 進行優化了,下面舉幾個對特定 SQL 優化的案例:

1.大表的更新和刪除

對於大表的數據修改最好要分批處理,好比咱們要在一個 1000 萬行記錄的表中刪除/更新 100 萬行記錄,那麼咱們最好分多個批次進行刪除/更新,一次只刪除/更新 5000 行記錄,避免長時間的阻塞,而且爲了減小對主從複製帶來的壓力,每次刪除/修改數據後須要暫停幾秒。這裏提供一個能夠完成這樣工做的 MySQL 存儲過程的實例:

DELIMITER $$
USE 'db_name'$$
DROP PROCEDURE IF EXISTS 'p_delete_rows'$$
CREATE DEFINER='mysql'@'127.0.0.1' PROCEDURE 'p_delete_rows'()
BEGIN
        DECLARE v_rows INT;
        SET v_rows = 1;
        WHERE v_rows > 0
        DO
                DELETE FROM table_name WHERE id >= 9000 AND id <= 290000 LIMIT 5000;
                SELECT ROW_COUNT() INTO v_rows;
                SELECT SLEEP(5);
        END WHERE;
END$$
DELIMITER;

你們能夠根據本身的狀況來修改這個存儲過程,或者使用本身熟悉的開發語言實現這個處理過程,使用這個存儲過程只須要修改 DELETE FROM table_name WHERE id >= 9000 AND id <= 290000 LIMIT 5000; 部分的內容便可。

2.如何修改大表的表結構

對於 InnoDB 存儲引擎來講,對錶中的列的字段類型進行修改或者改變字段的寬度時仍是會鎖表,同時也沒法解決主從數據庫延遲的問題。

解決方案:

在主服務器上創建新表,新表的結構就是修改以後的結構,再把老表的數據導入到新表中,而且在老表上創建一系列的觸發器,把老表數據的修改同步更新到新表中,當老表和新表的數據同步後,再對老表加一個排它鎖,而後從新命名新表爲老表的名字,最好刪除重命名的老表,這樣就完成了大表表結構修改的工做。這樣處理的好處是能夠儘可能減小主從延遲,以及在重命名以前不須要加任何的鎖,只須要在重命名的時候加一個短暫的鎖,這對應用一般是無影響的,缺點就是操做比較複雜。好在有工具能夠幫咱們實行這個過程,這個工具一樣是 percona 公司 MySQL 工具集中的一個,叫作 pt-online-schema-change:

pt-online-schema-change \
--alter="MODIFY c VARCHAR(150) NOT NULL DEFAULT ''" \
--user=root --password=password D=db_name,t=table_name \
--charset=utf8 --execute

這個命令就是把 db_name 數據庫下的 table_name 表中 c 列的寬度改成 VARCHAR(150)。

3.如何優化not in和<>查詢

MySQL 查詢優化器能夠自動的把一些子查詢優化爲關聯查詢,可是對於存在not in和<>這樣的子查詢語句來講,就沒法進行自動優化了,這就形成了會循環屢次來查找子表來確認是否知足過濾條件,若是子查詢剛好是一個很大的表的話,這樣作的效率會很是低,因此咱們在進行 SQL 開發時,最好把這類查詢自行改寫成關聯查詢。

改寫前:

SELECT id,name,email 
FROM customer 
WHERE id 
NOT IN(SELECT id FROM payment)

優化改寫後:

SELECT a.id,a.name,a.email 
FROM customer a 
LEFT JOIN payment b ON a.id=b.id 
WHERE b.id IS NULL

使用 LEFT JOIN 關聯替代了 NOT IN 過濾,這樣避免了對 payment 表進行屢次查詢,這是一種很是經常使用的對 NOT IN 的優化方式。

4.使用匯總表優化查詢

最多見的就是商品的評論數,若是咱們在用戶訪問頁面時,實時的訪問商品的評論數,一般來講,查詢的 SQL 會相似於下面這個樣子:

SELECT COUNT(*) FROM product_comment WHERE product_id = 10001;

這個 SQL 就是統計出全部 product_id = 10001 的評論,假設評論表中有上億條記錄,那麼這個 SQL 執行起來是很是的慢的,若是有大量的併發訪問,則會對數據庫帶來很大的壓力。對於這麼狀況,咱們一般使用匯總表的方式進行優化。所謂的彙總表就是提早把要統計的數據進行彙總並記錄到表中已備後續的查詢使用。針對這個查詢,咱們可使用下面的方式進行優化:

CREATE TABLE product_comment_cnt(product_id INT, cnt INT);   //創建彙總表

//查詢評論數
SELECT SUM(cnt) FROM(
    SELECT cnt FROM product_comment_cnt WHERE product_id = 10001
    UNION ALL
    SELECT COUNT(*) FROM product_comment WHERE product_id = 10001
    AND timestr > DATE(NOW())
);

clipboard.png

相關文章
相關標籤/搜索