為什麼 SQL 語句使用了索引,但卻還是慢查詢?

来源:https://www.cnblogs.com/okokabcd/archive/2022/06/09/16359004.html
-Advertisement-
Play Games

導讀: 驗證碼作為網路安全的第一道屏障,其重要程度不言而喻。當前,捲積神經網路的高速發展使得許多驗證碼的安全性大大降低,一些新型驗證碼甚至選擇犧牲可用性從而保證安全性。針對對抗樣本技術的研究,給驗證碼領域帶來了新的契機,並已應用於驗證碼反識別當中,為這場曠日持久攻防對抗註入了新的活力。 分享內容包括 ...


一、索引與慢查詢

聊一聊索引和慢查詢,經常遇到的一個問題:一個SQL語句使用了索引,為什麼還是會記錄到慢查詢日誌之中?
為了說明,創建一個表t,該表3個欄位,一個主鍵索引,一個普通索引

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`)
) ENGINE=InnoDB;

insert into t values (1, 1, 1), (2, 2, 2);

首先MySQL判斷一個語句是不是慢查詢語句,用的是語句執行時間,它把語句執行時間跟long_query_time這個系統參數做比較,如果語句執行時間比long_query_time還大,就會把這個語句記錄到慢查詢日誌里。

long_query_time這個參數它的預設值是10s,在生產上我們不會設置這麼大的值,一般會設置1s,對於一些對延遲比較敏感的業務,會設置一個比1還小的值,而對於語句是否使用了索引,它的意思是語句執行過程中有沒有用到表的索引。

具體到表象中是explain一個語句的時候,輸出結果裡面key的值不是NULL,圖1就是執行 explain select * from t; 的結果。可以看到key這一列顯示的是NULL。圖2就是執行explain select * from t where id = 2的結果,這裡key顯示的是PRIMARY,就是我們常說的使用了主鍵索引。圖3就是執行select a from t 的結果,這裡key這一列顯示的是a,表示使用了a這個索引。可以看到圖2和圖3的結果里key的欄位都不是NULL,而實際上圖3是掃描了整個索引樹a。

這個示例的表裡面只有兩行,那如果有100萬行呢,有100萬行的時候圖2的語句還是可以執行很快,但是圖3就肯定慢了,如果是更極端的情況,比如如果這個資料庫上CPU壓力非常地高,那可能第二個語句的執行時間也會超過long_query_time,會記錄到慢查詢日誌裡面,所以如果簡單地回答這個問題,是否使用索引只是表示了一個SQL語句的執行過程,而是否記錄到慢查詢日誌中是由它的執行時間決定的,而這個執行時間可能會受各種外部因素的影響,也就是說是否使用索引和是否記錄慢查詢之間沒有必然的聯繫

二、索引的過濾性

如果我們再深層次的看這個問題其實它還潛藏著一個問題需要澄清就是,什麼叫做使用了索引。我們知道InnoDB是索引組織表,所有的數據都是存儲在索引樹上面的,比如表t,這個表它包含了兩個索引,一個主鍵索引一個普通索引a,在InnoDB里數據是放在主鍵索引里的。我們來看一下這個表的數據示意圖,可以看到數據都放在主鍵索引上,如果從邏輯上說,所有的在InnoDB表上的查詢,都至少用了一個索引,現在有一個問題:如果執行explain select * from t where id > 0; 這個語句有用上索引嗎?

現在我們來看看這個語句的explain的結果,在輸出結果里,key這裡顯示的是PRIMARY,其實從數據上你是知道的這個語句一定是做了全表掃描,但是優化器認為,這個語句的執行過程中,需要根據主鍵索引定位到第一個滿足id>0的值,也算用到了索引。所以你看,即使explain結果裡面寫了key不是NULL,實際上也可能是全表掃描的,因此InnoDB裡面只有一種情況叫做沒有使用索引,那就是從主鍵索引的最左邊的葉節點開始,向右掃描整個索引樹,也就是說,沒有使用索引並不是一個準確的描述,你可以用全表掃描來表示一個查詢遍歷了整個主鍵索引樹。也可以用全索引掃描來說明,像select a from t這樣的查詢,它掃描了整個普通索引樹。而像select * from t where id = 2; 這樣的語句才是我們平時說的使用了索引,它表示的意思是我們使用了索引的快速搜索功能,並且有效的減少了掃描行數。

那麼除了全索引掃描,還有哪些是使用了索引但是執行速度不夠快的例子呢,這就要說到索引的過濾性,假設你現在維護了一個表,這個表記錄了全中國人的基本信息,然後你現在要查出年齡在10到15歲之間的小朋友的姓名和基本信息,那麼你的語句會這麼寫,select * from t_people where age between 10 and 15;你一看這個語句一定要在age欄位上建索引了,否則就是個全表掃描。但是你會發現在age上建了索引以後,這個語句還是執行慢,因為滿足這個條件的數據有超過1億行。我們來看看建立了這個索引以後這個表的組織結構圖,這個語句的執行流程是這樣的。從索引age上用樹搜索,取到第一個age等於10的記錄,得到它的主鍵ID的值,根據ID的值去主鍵索引取整行的信息,作為結果集的一部分返回,在索引age上向右掃描,取下一個ID值,到主鍵索引上取整行信息,作為結果集的一部分返回,重覆上面的步驟直到碰到第一個age>15的記錄。你看這個語句,雖然它用了索引,但是它掃描超過了一億行,而上面select * from t;這個語句雖然沒有用索引,但其實也只掃描了兩行。

所以你現在知道了,當我們討論有沒有使用索引的時候,其實我們關心的是掃描行數,對於一個大表,不止要有索引,索引的過濾性還要足夠好,像剛纔這個例子age這個索引它的過濾性就不夠好。在設計表結構的時候,我們要讓索引的過濾性足夠好,也就是區分度足夠高。那麼過濾性好了,是不是表示查詢的掃描行數就一定少呢,我們再來看一個例子。

三、索引的掃描行數

如果這個t_people表上有一個索引是姓名、年齡的聯合索引,那這個聯合索引的過濾性應該不錯,如果你的執行語句是select * from t_people where name = '張三' and age = 8; 就可以在一個索引上快速找到第一個姓名是張三並且年齡是8歲的小朋友,當然這樣的小朋友就該不多,因此向右掃描的行數很少,查詢效率就很高,但是查詢的過濾性和索引的過濾性可並不一定是一樣的。如果現在你的需求是查出所有名字第一個字是張並且年齡是8歲的所有小朋友,你的語句會怎麼寫呢?你的語句要這麼寫:select * from t_people where name like '張%' and age = 8; 在MySQL5.5和之前的版本中,這個語句的執行流程是這樣的。首先從聯合索引樹上找到第一個姓名欄位是張開頭的記錄,取出主鍵ID,然後到主鍵索引上,根據ID取出整行的值,判斷年齡欄位是否等於8如果是就作為結果集的一行返回,如果不是就丟棄,我們把根據ID到主鍵索引上查找整行數據這個動作稱為回表,在聯合索引上向右遍歷,並重覆做回表和判斷的邏輯直到碰到聯合索引樹上名字第一個字不是張的記錄為止。你可以看到這個執行過程裡面最耗費時間的步驟就是回表,假設全國名字第一個字是張的人有8000萬,那麼這個過程就要回表8000萬次,在定位第一行記錄的時候,只能使用索引和聯合索引的最左首碼,稱為最左首碼原則。那你可以看到這個執行過程它的回表次數特別多,性能不夠好,那有沒有優化的方法呢?有的在MySQL5.6版本引入了index condition pushdown的優化,我們來看看這個優化的執行流程。

首先從聯合索引樹上找到第一個姓名欄位是張開頭的記錄,判斷這個索引記錄裡面年齡的值是不是8,如果是就回表,取出整行數據作為結果集的一部分返回,如果不是就丟棄。在聯合索引樹上向右遍歷,並判斷年齡欄位後根據需要做回表,直到碰到聯合索引樹上名字的第一個字不是張的記錄為止。這個過程跟上面過程的差別是在遍歷聯合索引的過程中,將年齡等於8這個條件下推到索引遍歷的過程中,減少了回表的次數,假設全國名字第一個字是張的人裡面朋100萬個是8歲的小朋友,那麼這個查詢過程中,在聯合索引里要遍歷8000萬次,而回表只需要100萬次。可以看到,index condition pushdown 優化的效果還是很不錯的,但是這個優化,還是沒有繞開最左首碼原則的限制,因此在聯合索引里,還是要掃描8000萬行,那有沒有更進一下的優化方法呢?我們可以把名字的第一個字,和年齡做一個聯合索引來試試,這裡可以用到MySQL 5.7引入的虛擬列來實現,對應的修改表結構的SQL語句是這麼寫的。

alter table t_people add name_first varchar(2) generated always as 
(left(name, 1)), add index (name_first, age);

CREATE TABLE `t_people` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(20) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `info` varchar(255) DEFAULT NULL,
  `name_first` varchar(2) GENERATED ALWAYS AS (left(`name`, 1)) VIRTUAL,
  KEY `name_first` (`name_first`, `age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

上圖是這個DDL語句的執行效果,首先它在t_people上創建一個欄位叫name_first虛擬列,然後給name_first和age上創建一個聯合索引,並且讓這個虛擬列的值,總是等於name欄位的前兩個位元組,虛擬列在插入數據的時候,不能指定值,在更新的時候也不能指定修改,它的值會根據定義自動生成,在name欄位修改的時候,也會自動修改,有了這個新的聯合索引,我們再找名字第一個字是張並且年齡是8的小朋友的時候,這個SQL語句就可以這麼寫:select * from t_people where name_fist = '張' and age = 8; 這樣這個語句的執行過程,就只需要掃描聯合索引的100萬行並回表100萬次。這個優化的本質是創建了一個更緊湊的索引來加速了查詢的過程。

四、小結

今天介紹了索引的基本結構和一些查詢優化的基本思路,現在我們知道了:
1、使用索引和慢查詢沒有必然聯繫,使用索引的SQL也有可能是慢查詢語句;
2、檢查一個查詢語句的執行效率最終要看的是掃描行數,我們查詢優化的過程往往就是減少掃描行數的過程;
3、使用虛擬列和聯合索引來提升複雜查詢的執行效率。


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

-Advertisement-
Play Games
更多相關文章
  • 本文講解Linux伺服器 Ubuntu20.04 設置靜態IP方法。 ...
  • 英文原文: https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cpuacct.html CPU Accounting Controller CPU統計控制器(CPU Accounting Controller)用來分組使用cgr ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡巴巴開源鏡像站 前言 最近,學習了胡老師的《ROS入門21講》,在Ubuntu18.04上安裝ROS過程中遇到了一些問題,解決這些問題耗費了大半天,故通過本文進行詳細安裝介紹,以便其他學者在安裝這塊少花時間,把更多的精力放在研究上。 一、環境配置 我的環境:虛 ...
  • 作用:命令行多視窗顯示;命令行程式與本機脫離 1 安裝tmux (1)redhat、centos系統 yum install tmux (2)ubuntu系統 apt-get install tmux 2 使用tmux (1)啟動 首先,我們使用遠程登錄工具,登錄到遠程伺服器上,然後執行下麵的命令: ...
  • 虛擬機關鍵配置名詞解釋 虛擬⽹絡編輯器 橋接模式 可以訪問互聯⽹,配置的地址信息和物理主機⽹段地址信息相同,容易造成地址衝突 NAT模式 可以訪問互聯⽹,配置的地址信息和物理主機⽹段地址信息不同,造成不了地址衝突 僅主機模式 不可以訪問互聯⽹,獲取地址主要⽤於虛擬主機之間溝通,但不能訪問外部⽹絡 網 ...
  • 亂序問題 在業務編寫 FlinkSQL 時, 非常常見的就是亂序相關問題, 在出現問題時,非常難以排查,且無法穩定復現,這樣無論是業務方,還是平臺方,都處於一種非常尷尬的地步。 亂序問題 在業務編寫 FlinkSQL 時, 非常常見的就是亂序相關問題, 在出現問題時,非常難以排查,且無法穩定復現,這 ...
  • 本文給大家介紹資料庫中用來管理數據更新的重要概念——SQL 事務。簡單來講,事務就是需要在同一個處理單元中執行的一系列更新處理的集合。 本文重點 事務是需要在同一個處理單元中執行的一系列更新處理的集合。通過使用事務,可以對資料庫中的數據更新處理的提交和取消進行管理。 事務處理的終止指令包括 COMM ...
  • 在MySQL 5.7版本,官方稱為enhanced multi-threaded slave(簡稱MTS),複製延遲問題已經得到了極大的改進,可以說在MySQL 5.7版本後,複製延遲問題永不存在。 5.7的MTS本身就是:master基於組提交(group commit)來實現的併發事務分組,再由 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...