幾個影響sql性能語句的例子


幾個影響sql語句性能的例子

  • 案例一:假設你現在維護了一個交易系統,其中交易記錄表tradelog包含交易流水號(tradeid)、交易員id(operator)、交易時間(t_modified)等字段。為了便於描述,我們先忽略其他字段。這個表的建表語句如下:

    mysql> CREATE TABLE `tradelog` (
    `id` int(11) NOT NULL,
    `tradeid` varchar(32) DEFAULT NULL,
    `operator` int(11) DEFAULT NULL,
    `t_modified` datetime DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `tradeid` (`tradeid`),
    KEY `t_modified` (`t_modified`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    

    假設,現在已經記錄了從2016年初到2018年底的所有數據,運營部門有一個需求是,要統計發生在所有年份中7月份的交易記錄總數。這個邏輯看上去並不復雜,你的SQL語句可能會這么寫:

    mysql> select count(*) from tradelog where month(t_modified)=7;
    

    由於t_modified字段上有索引,於是你就很放心地在生產庫中執行了這條語句,但卻發現執行了特別久,才返回了結果。

    如果對字段做了函數計算,就用不上索引了,這是MySQL的規定。

    可以再追問一句為什么?為什么條件是where t_modified='2018-7-1’的時候可以用上索引,而改成where month(t_modified)=7的時就不行了?

    對索引字段做函數操作,可能會破壞索引值的有序性,因此優化器就決定放棄走樹搜
    索功能。

    需要注意的是,優化器並不是要放棄使用這個索引。在這個例子里,放棄了樹搜索功能,優化器可以選擇遍歷主鍵索引,也可以選擇遍歷索引t_modified,優化器對比索引大小后發現,索引t_modified更小,遍歷這個索引比遍歷主鍵索引來得更快。因此最終還是會選擇索引t_modified。

Extra字段的Using index,表示的是使用了覆蓋索引。也就是說,由於在t_modified字段加了month()函數操作,導致了全索引掃描。

按照下面這個寫法,優化器就能按照我們預期的,用上t_modified索引的快速定位能力了。

mysql> select count(*) from tradelog where
‑> (t_modified >= '2016‑7‑1' and t_modified<'2016‑8‑1') or
‑> (t_modified >= '2017‑7‑1' and t_modified<'2017‑8‑1') or
‑> (t_modified >= '2018‑7‑1' and t_modified<'2018‑8‑1');
  • 案例二:隱式類型轉換

    我們一起看一下這條SQL語句:

    mysql> select * from tradelog where tradeid=110717;
    

    交易編號tradeid這個字段上,本來就有索引,但是explain的結果卻顯示,這條語句需要走全表掃描。你可能也發現了,tradeid的字段類型是varchar(32),而輸入的參數卻是整型,所以需要做類型轉換。

    • 數據類型轉換的規則是什么

      • 在MySQL中,字符串和數字做比較的話,是將字符串轉換成數字。
    • 為什么有數據類型轉換,就要走全索引掃描。

      • 以上sql語句相當於

        mysql> select * from tradelog where CAST(tradid AS signed int) = 110717;
        
      • 也就是說,這條語句觸發了我們上面說到的規則:對索引字段做函數操作,優化器會放棄走樹搜索功能。

    • 同樣的例子比如:,字符集utf8mb4是utf8的超集,所以當這兩個類型的字
      符串在做比較的時候,MySQL內部的操作是,先把utf8字符串轉成utf8mb4字符集,再做比較。

      • 這個設定很好理解,utf8mb4是utf8的超集。類似地,在程序設計語言里面,做自動類型轉換的時候,為了避免數據在轉換過程中由於截斷導致數據錯誤,也都
        是“按數據長度增加的方向”進行轉換的。

      • 也就是說,實際上這個語句等同於下面這個寫法:

        select * from trade_detail where CONVERT(traideid USING utf8mb4)=$L2.tradeid.value;
        
      • CONVERT()函數,在這里的意思是把輸入的字符串轉成utf8mb4字符集。這就再次觸發了我們上面說到的原則:對索引字段做函數操作,優化器會放棄走樹搜索功能。

  • 小結:

    1. 對於給不同年份中的月份加索引,我們要指定年份才能走索引,因為不同年份可能會有相同的月份,mysql優化器不知如何選擇,不會走指定字段索引。
    2. 保持字段類型一致,比如字段和數字,mysql會自動的調用函數將字符串轉化為數字,會走全表掃描。
    3. 保持字符集一致,charset=utf8,如果跨表查詢,兩張表的字符集應該相同,否則mysql內部還是會調用函數,因此會走全表掃描。
    4. 在每次代碼升級的時候我們都應該調用explain來看一下sql語句是否按索引來走的。
    5. 調用函數就不會走我們已經建好的索引字段。

查一行的sql語句執行很慢

  • 第一類 查詢長時間不返回

    • mysql> select * from t where id=1;
      
    • 執行show processlist命令,查看當前語句處於什么狀態。

    • 等釋放

      -

      • 可以看到這個語句目前的狀態是waiting for table metadata lock,表示有一個線程正在表t上請求或者持有MDL寫鎖,把select語句堵住了
    • 等行鎖

      • mysql> select * from t where id=1 lock in share mode;
        
      • 由於訪問id=1這個記錄時要加讀鎖,如果這時候已經有一個事務在這行記錄上持有一個寫鎖,我們的select語句就會被堵住。

      • 顯然,session A啟動了事務,占有寫鎖,還不提交,是導致session B被堵住的原因。

  • 第二類:查詢慢

    • mysql> select * from t where c=50000 limit 1;
      
      • 由於字段c上沒有索引,這個語句只能走id主鍵順序掃描,因此需要掃描5萬行。
    • mysql> select * from t where id=1; # 假如這條語句慢查詢日志返回的是800毫秒
      
      select * from t where id=1 lock in share mode # 這條語句返回的查詢時間是0.2毫秒
      

      為什么結果相差會這么大呢?

      • session A先用start transaction with consistent snapshot命令啟動了一個事務,之
        后session B才開始執行update 語句。

      • session B執行完100萬次update語句后,id=1這一行處於什么狀態呢?

      • session B更新完100萬次,生成了100萬個回滾日志(undo log)。

      • 帶lock in share mode的SQL語句,是當前讀,因此會直接讀到1000001這個結果,所以速度很快;而select * from t where id=1這個語句,是一致性讀,因此需要從1000001開始,依次執行undo log,執行了100萬次以后,才將1這個結果返回。所以返回查詢時間很長


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM