MySQL 筆記整理(18) --為什麼這些SQL語句邏輯相同,性能卻差異巨大?

来源:https://www.cnblogs.com/dogtwo0214/archive/2019/04/18/10732402.html
-Advertisement-
Play Games

筆記記錄自林曉斌(丁奇)老師的《MySQL實戰45講》 (本篇內圖片均來自丁奇老師的講解,如有侵權,請聯繫我刪除) 18) --為什麼這些SQL語句邏輯相同,性能卻差異巨大? 本篇我們以三個例子來記錄。 案例一:條件欄位函數操作 一個交易系統中有這樣一個交易記錄表,假設現在已經記錄了從16年年初到1 ...


筆記記錄自林曉斌(丁奇)老師的《MySQL實戰45講》

(本篇內圖片均來自丁奇老師的講解,如有侵權,請聯繫我刪除)

18) --為什麼這些SQL語句邏輯相同,性能卻差異巨大?

  本篇我們以三個例子來記錄。

案例一:條件欄位函數操作

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;

  一個交易系統中有這樣一個交易記錄表,假設現在已經記錄了從16年年初到18年年底的全部數據,需要統計發生在所有年份中7月份的交易記錄總數,你可能會這麼寫查詢語句:

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

  由於查詢條件中的t_modified欄位上有索引,你就很放心的執行了,但是實際上執行地很慢。如果你接著這個問題查一查會發現,如果對欄位做了函數計算,就用不上索引了,這是MySQL的規定。那麼,為什麼呢?

  我們前面介紹過了,MySQL是按照B+樹的數據結構來存放索引的,實際上t_modified這個欄位的索引示意圖如下:

  

  如果你的查詢條件是where t_modified = '2018-7-1',那麼引擎就會按照上面所示的方式快速定位到這條記錄,而如果你使用了month函數來計算的話,在這個索引樹的第一層引擎就不知道應該怎麼去尋找了。

  實際上,B+樹提供這個快速定位能力,來源於同一層兄弟節點的有序性。

  因此,對索引欄位做函數操作,可能會破壞索引值的有序性,因此優化器就決定放棄走樹搜索功能。需要註意的是,優化器並不是要放棄使用這個索引。在這個例子中,放棄了樹的搜索功能,優化器可以遍歷主鍵索引,也可以遍歷索引t_modified。優化器對比索引大小之後,發現t_modified更小,遍歷這個索引更快,因此最終還是會選擇索引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');

  當然,如果你的系統上線更早,或者後面又插入了之後年份的數據,你就需要把其他年份補齊了。

  month()函數破壞了有序性因此導致查詢變慢,但實際上,MySQL的優化器確實有“偷懶”行為。例如

select * from tradlog where id+1 = 10000; 

  雖然沒有改變有序性,但是優化器還是不能利用索引快速定位到id=9999這一行。你需要手動改動查詢條件為

id = 10000-1才行。

案例二:隱式類型轉換

  我們還用剛纔那個交易記錄表舉例,來看看這條SQL語句:

mysql> select * from tradelog where tradeid=110717;

  交易編號tradeid這個欄位本來就有索引,但是explain的結果卻顯示,這條語句需要走全表掃描。你可能也發現了,tradeid的欄位類型是varchar(32),而輸入的參數確實整型,索引需要做類型轉換。那麼現在這裡就有兩個問題了:

  1. 數據類型的轉換規則是什麼?
  2. 為什麼有數據類型轉換,就需要走全表索引掃描?

  先來看第一個問題,你可能會說,資料庫里類型這麼多,這種數據類型規則更多,我記不住怎麼辦呢?有一個簡單地方法,看看select "10" > 9的結果:

  1. 如果規則是“將字元串轉成數字”,那麼就是數字比較,結果應該是1;
  2. 如果規則是“將數字轉成字元串”,那麼久做字元串比較,結果應該是0;

  實際上上面這個查詢返回的結果是1,即“將字元串轉成數字”。這時,我們再來看看案例二剛開始的查詢語句

mysql> select * from tradelog where tradeid=110717;

  對於優化器來說,這個語句就相當於

mysql> select * from tradelog where CAST(tradeid AS signed int) = 110717;

  因此優化器放棄了走樹搜索的功能。

  

案例三: 隱式字元編碼轉換

  假設系統里還有另一個表trade_detail用於記錄交易的操作細節。為了方便量化分析和復現,我們準備一些數據,如下:

mysql> CREATE TABLE `trade_detail` (
  `id` int(11) NOT NULL,
  `tradeid` varchar(32) DEFAULT NULL,
  `trade_step` int(11) DEFAULT NULL, /* 操作步驟 */
  `step_info` varchar(32) DEFAULT NULL, /* 步驟信息 */
  PRIMARY KEY (`id`),
  KEY `tradeid` (`tradeid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into tradelog values(1, 'aaaaaaaa', 1000, now());
insert into tradelog values(2, 'aaaaaaab', 1000, now());
insert into tradelog values(3, 'aaaaaaac', 1000, now());

insert into trade_detail values(1, 'aaaaaaaa', 1, 'add');
insert into trade_detail values(2, 'aaaaaaaa', 2, 'update');
insert into trade_detail values(3, 'aaaaaaaa', 3, 'commit');
insert into trade_detail values(4, 'aaaaaaab', 1, 'add');
insert into trade_detail values(5, 'aaaaaaab', 2, 'update');
insert into trade_detail values(6, 'aaaaaaab', 3, 'update again');
insert into trade_detail values(7, 'aaaaaaab', 4, 'commit');
insert into trade_detail values(8, 'aaaaaaac', 1, 'add');
insert into trade_detail values(9, 'aaaaaaac', 2, 'update');
insert into trade_detail values(10, 'aaaaaaac', 3, 'update again');
insert into trade_detail values(11, 'aaaaaaac', 4, 'commit');

  此時如果需要查詢id=2(tradeid = 'aaaaaaab')的交易的所有操作步驟信息,SQL語句可以這麼寫:

mysql> select d.* from tradelog l, trade_detail d where d.tradeid=l.tradeid and l.id=2; 

  這條語句的explain執行結果為:

  這個結果表明:

  1. 第一行顯示優化器會先在交易記錄表tradelog上查到id=2的行,這個步驟用上了主鍵索引,rows=1表示只掃描了1行。
  2. 第二行key=NULL,表示沒有用上交易詳情表trade_detail上的tradeid索引,進行了全表掃描。

  這個執行結果里,是從tradelog表中取tradeid欄位,再去trade_detail表裡查詢匹配欄位,因此,我們把tradelog稱為驅動表,把trade_detail稱為被驅動表,把tradeid稱為關聯欄位。接下來我們來看看explain結果表示的執行流程:

  1. 根據id在tradelog中找到L2這一行記錄。
  2. 從L2中取出tradeid欄位的值。
  3. 根據tradeid值到trade_detail表中查找條件匹配的行。explain的結果裡面第二行的key=NULL表示的就是,這個過程通過遍歷主鍵索引的方式,一個一個地判斷tradeid的值是否匹配。

  到這裡你會發現,第三步中與我們期望的執行結果不符,因為trade_detail欄位上是有索引的,我們本來是希望通過使用tradeid索引來快速定位的。這時候如果你去問DBA同學,他可能會告訴你,因為這兩個表的字元集不同,一個是utf8,另一個是utf8mb4,所以做表連接查詢的時候用不上關聯欄位的索引。但是如果你再追問一下,為什麼字元集不同就用不上索引了呢?

  如果說剛纔的執行結果問題是出在第三步,那麼如果單獨把這一步改成SQL語句的話,那就是:

mysql>select * from trade_detail where tradeid = $L2.tradeid.value; 

  其中,$L2.tradeid.value的字元集是utf8mb4。

  參照前面的例子,你肯定想到了,字元集utf8mb4是utf8的超集,所以當這兩個類型的字元串是在做比較的時候,MySQL內部的操作是,先把utf8轉化成utf8mb4字元集,再做比較。也就是說,實際上這個語句等同於下麵這個寫法:

select * from trade_detail  where CONVERT(traideid USING utf8mb4)=$L2.tradeid.value; 

  這就觸發了我們在案例一中的那種情況:對索引欄位做函數操作,優化器會放棄走樹搜索功能。

  到這裡,你應該明白了,字元集不同只是條件之一,連接過程中要求在被驅動表的索引欄位上加函數操作,是直接導致對被驅動表做全表掃描的原因。

  那麼這個語句我們應該怎麼去優化呢,一般有兩種作法:

  1. 直接把trade_detail的表的字元集也改成utf8mb4,這樣就沒有字元集轉換的問題了。
  2. 如果業務上不允許進行DDL的話,那就只能修改SQL語句了,你可以嘗試這麼寫:
    mysql> select d.* from tradelog l , trade_detail d where d.tradeid=CONVERT(l.tradeid USING utf8) and l.id=2; 
    

      

  這裡我們主動改變了l.tradeid的字元集,避免了被驅動表上字元編碼的轉換。

  今天這個三個例子,其實是在說同一件事。即:對索引欄位做函數操作,可能會破壞索引值的有序性,因此優化器就決定放棄走樹搜索功能。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.以管理員許可權 運行 cmd. 重啟電腦,再試試。本人親測可行。第一次訪問可能會有點慢。耐心等待。 2.針對原來可以訪問但後來不行的人。 可以償試 1.把 Microsoft 網路xxx 前面的鉤去掉。確定退出 2.再進去 打鉤打上。再訪問試試。 重啟共用服務的意思。 希望能幫上大家。 ...
  • . tar 將多個文件打包在一起,,並可以實現解壓打包的文件 -z # 通過gzip壓縮或解壓 -c # 創建新的jar包 -v # 顯示tar命令執行過程 -f # 指定文件壓縮名字 -t # 不解壓查看壓縮包內容 -p # 保持穩健的原有屬性 -P # 以絕對路徑打包,危險參數 --exclud ...
  • Linux虛擬機的型號是:Ubuntu 12.04 VMware:workstation 14 pro author: Xianghai Ding Date:2019.01.04 板端:Hi3536 海思編解碼晶元******************************************* ...
  • 轉載 longctw 版權聲明:只為分享、歡迎轉載^V^ https://blog.csdn.net/xulong_08/article/details/81463054 Linux是目前應用最廣泛的伺服器操作系統,基於Unix,開源免費,由於系統的穩定性和安全性,市場占有率很高,幾乎成為程式代碼運 ...
  • Linux關機命令用poweroff。 halt停止系統運行,但不關閉電源,坑啊! ...
  • . uname 顯示系統相關信息,如內核版本號,硬體架構 -a # 顯示系統所有相關信息 -m # 顯示電腦硬體架構 -n # 顯示主機名稱 -r # 顯示內核發行版本號 -s # 顯示內核名稱 -v # 顯示內核版本 -p # 顯示主機處理器類型 -o # 顯示操作系統名稱 -l # 顯示硬體平 ...
  • Step 1 . 準備工作 關閉防火牆; 安裝 MySQL依賴於libaio 庫;下載,解壓,重命名 MySQL的執行文件;新建用戶組和用戶 等準備工作在此不再贅述。 可執行文件目錄為 /data/mysql57 添加用戶和組的指令 groupadd mysql useradd mysql -g m ...
  • 1.分散式鎖介紹 在電腦系統中,鎖作為一種控制併發的機制無處不在。 單機環境下,操作系統能夠在進程或線程之間通過本地的鎖來控制併發程式的行為。而在如今的大型複雜系統中,通常採用的是分散式架構提供服務。 分散式環境下,基於本地單機的鎖無法控制分散式系統中分開部署客戶端的併發行為,此時分散式鎖就應運而 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...