探究MySQL中SQL查詢的成本

来源:https://www.cnblogs.com/duizhangz/archive/2022/05/24/16305481.html
-Advertisement-
Play Games

成本 什麼是成本,即SQL進行查詢的花費的時間成本,包含IO成本和CPU成本。 IO成本:即將數據頁從硬碟中讀取到記憶體中的讀取時間成本。通常1頁就是1.0的成本。 CPU成本:即是讀取和檢測是否滿足條件的時間成本。0.2是每行的CPU成本。 單表查詢計算成本 我們對其進行分析的具體步驟如下: 根據搜 ...


成本

什麼是成本,即SQL進行查詢的花費的時間成本,包含IO成本和CPU成本。

IO成本:即將數據頁從硬碟中讀取到記憶體中的讀取時間成本。通常1頁就是1.0的成本。

CPU成本:即是讀取和檢測是否滿足條件的時間成本。0.2是每行的CPU成本。

單表查詢計算成本

我們對其進行分析的具體步驟如下:

  1. 根據搜索條件找出可能使用到的索引。
  2. 計算全表掃描的需要執行的成本。
  3. 計算各個索引執行所需要執行的成本。
  4. 對各個索引所需要執行的成本,找出最低的那個方案。

全表掃描的成本

計算IO成本:

  • 我們首先從表的status中找出Data_Length的大小,就是整個聚簇索引的大小,然後計算它一共有多少頁。

Data_Length計算頁的方法:Data_Length / (頁的大小 = 16 * 1024 = 16KB)

  • 然後我們就可以直接計算出它的IO成本即 頁數 * 1.0 + 1.1。(1.1是一個微調值)

計算CPU成本:

  • 首先從表的status中找到Rows的大小,Rows是一個不准確值。
  • 找到行的大小,所以CPU成本為**行數 * 0.2 + 0.01。(0.01是微調值)

所以我們可以將其兩個成本相加就是全表掃描的總成本。

利用索引查詢的成本

區間的索引條件

如果我們選擇的索引執行的條件是區間。

where key1 > 10 and key1 < 1000  # 在計算單個索引的成本時對於其他條件直接為true。

就會進入以下步驟

  1. 我們需要對二級索引的IO成本進行計算,當然呢,在Mysql中它對於一個範圍查詢的二級索引直接粗暴的定義其IO成本為讀取一個頁面的成本,就是1 * 1.0 = 1
  2. 我們就要找到需要回表的記錄行,首先找出最左邊的區間的記錄所在的頁和最右邊區間所在的頁。
    1. 如果兩個在同一頁,直接計算中間隔了幾個數據行。
    2. 如果兩個不在同一頁,就找出其所在頁的父頁,在判斷兩個記錄的父頁是否在同一頁,在同一頁就計算中間隔了幾個頁,然後乘以相應每頁的數據行的數量。如果不在就是遞歸處理在不在的問題了。
  3. 我們找到了間隔的記錄行n,這個時候讓CPU從二級索引找到這n條數據行所需的成本就是n*0.2 + 0.01
  4. 緊接著我們拿著主鍵值回表,在MySQL中設計者有直接粗暴的將回表操作的IO成本直接計算為一個頁面的IO成本,不需要計算別的比如索引頁面之類的。所以我們n條記錄回表的IO成本就是**n * 1 ** 。
  5. 然後我們需要計算每次回表後的CPU成本,我們需要對回表後完整的數據行對其進行其他條件的判斷,所以CPU成本為n * 0.2

所以IO成本為1 + n * 1,CPU成本為n*0.2 + 0.01 + n * 0.2。

單點區間

where key1 in (a,b,c,...,z)

當我們選擇的索引的條件是上述的單點區間的情況時

我們查詢n個單點區間。

  • 首先需要進行n次的IO讀取單點範圍,就相當於最小左區間和最大右區間都是一個值。就需要n * 1 的IO成本。
  • 然後就是查詢記錄,CPU成本就是總的記錄數*0.2,後面的回表流程其實是和上面一樣的。不在贅述。

最後找出成本最小的,選擇對應方法執行SQL。

index dive

我們將這樣從索引中找到最小左邊界和最大右邊界的過程計算索引的數量稱為index dive。

當然我們找到一個大區間進行一次index dive,但是in(a,b,c...d)這樣每一個參數都是一個單點區間,就要進行多次index dive。in裡面的參數多起來,特別是in (sql) 嵌套子查詢,就會使參數爆炸了,單點區間是導致超出index dive上線的主要原因。

MySQL有一個index dive的上限,預設值為200。

mysql> show variables like '%dive%';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| eq_range_index_dive_limit | 200   |
+---------------------------+-------+
1 row in set, 1 warning (0.00 sec)

像上面我們利用索引計算範圍的那種計算成本的方式,僅適用於區間範圍數量小的情況下,當大於index dive的上限,就不能使用index dive了,就得使用索引的數據進行估算。

如何估算?

show index from 表名;

我們首先獲得MySQL數據字典中統計的該表的Rows即行數,這個值是不准確的,是估計值。(後面解釋)

然後通過上面語句獲得的Cardinality列對應的索引的參數,即該索引列的基數,即索引列的值不重覆的列的數量。

將Rows / Cardinality 就可以得到每個索引值重覆行數的平均值。

我們根據每個值重覆的數量,乘以單點區間的數量,就充當每個單點區間匹配的記錄數。

連接查詢計算成本

對於驅動表的查詢後的得到記錄條數就叫做驅動表的扇出。

對於驅動表來說計算其最後記錄的條數,當能用到索引直接使用索引計算其條數,對於用不到索引的情況呢,就只能進行猜,就是對其進行評估(啟髮式規則),最後得到驅動表的扇出。

然後我們要計算連接的成本,就需要確定連接的方式。

  • 左,右連接。因為左右固定,所以驅動表和被驅動表是固定的。但是有時候是可以將外連接優化成內連接的。
  • 內連接。左右不固定,都可以作為驅動表,所以需要對其兩種進行成本的計算。

所以流程如下:

  1. 確定驅動表。
  2. 計算驅動表執行的最優計劃,即上文的單表查詢計算成本。
  3. 然後將驅動表的扇出 * 被驅動表的執行的最優成本。
  4. 將2,3步驟成本相加,即連接成本。

ps:內和外連接都是一樣的,區別內連接需要確定哪個作為驅動表成本更低。

我們會知道如果兩表連接時,驅動表的每一個結果行是作為一個常數傳入被驅動表進行查詢的。所以如果在連接條件上有索引的話,就可以加快連接,否則就要進行全表掃描。

當然了被驅動表的搜索條件能有索引那更好了。也能加快其計算出最後結果。

我在之前的總結文章中,有一個錯誤,就是我提出一個能不能將被驅動表在自身搜索條件篩選後應該緩存起來這個觀點,其實是不對的,如果沒有被驅動表自身搜索條件進行是沒有意義的。而且因為驅動表的結果行也是作為一個參數的搜索條件連接的,然後一條一條的進行設置參數搜索被驅動表符合的結果行。

調整成本常數

mysql.server_cost

我們知道的從磁碟從IO到記憶體的成本常數是1.0

mysql> select * from mysql.server_cost;
+------------------------------+------------+---------------------+---------+---------------+
| cost_name                    | cost_value | last_update         | comment | default_value |
+------------------------------+------------+---------------------+---------+---------------+
| disk_temptable_create_cost   |       NULL | 2020-12-17 14:54:07 | NULL    |            20 |
| disk_temptable_row_cost      |       NULL | 2020-12-17 14:54:07 | NULL    |           0.5 |
| key_compare_cost             |       NULL | 2020-12-17 14:54:07 | NULL    |          0.05 |
| memory_temptable_create_cost |       NULL | 2020-12-17 14:54:07 | NULL    |             1 |
| memory_temptable_row_cost    |       NULL | 2020-12-17 14:54:07 | NULL    |           0.1 |
| row_evaluate_cost            |       NULL | 2020-12-17 14:54:07 | NULL    |           0.1 |
+------------------------------+------------+---------------------+---------+---------------+
6 rows in set (0.00 sec)
  • disk_temptable_create_cost 磁碟中創建臨時表的成本參數
  • disk_temptable_row_cost 磁碟中的臨時表讀入頁的成本參數
  • key_compare_cost 鍵進行比較的成本參數
  • ...其他的就不介紹了差不多
  • row_evaluate_cost 這個就是CPU檢測一條記錄的成本參數,調高會讓優化器儘可能使用索引減少檢測的記錄條數。
如果更新直接使用update語句即可
然後讓系統刷新以下這個值   flush optimizer_costs;

mysql.engine_cost

mysql> select * from mysql.engine_cost;
+-------------+-------------+------------------------+------------+---------------------+---------+---------------+
| engine_name | device_type | cost_name              | cost_value | last_update         | comment | default_value |
+-------------+-------------+------------------------+------------+---------------------+---------+---------------+
| default     |           0 | io_block_read_cost     |       NULL | 2020-12-17 14:54:07 | NULL    |             1 |
| default     |           0 | memory_block_read_cost |       NULL | 2020-12-17 14:54:07 | NULL    |          0.25 |
+-------------+-------------+------------------------+------------+---------------------+---------+---------------+
2 rows in set (0.00 sec)
  • io_block_read_cost 從磁碟IO一個塊block同樣就是頁到記憶體的成本參數,提高就會讓優化器儘量減少IO即從磁碟讀的條數,即儘可能使用索引。就是我們上面計算的IO成本。
  • memory_block_read_cost 從記憶體讀塊即頁的成本參數。

MySQL統計數據

我們在上面所過全表掃描計算成本時我們需要拿出表的Rows即行數這個參數,這一些關於表的,索引的行數等等被叫做統計數據。

MySQL有兩種統計數據存儲方式

  • 基於磁碟的永久性統計數據
  • 基於記憶體的非永久統計數據

兩種模式,記憶體需要每次啟動MySQL進行數據統計,然後關閉統計數據就消失了。預設還是磁碟的永久存儲。

基於磁碟的統計數據

統計數據可以分為兩個,一個是表的統計數據,一個是索引的統計數據。

mysql> show tables from mysql like '%innodb%';
+----------------------------+
| Tables_in_mysql (%innodb%) |
+----------------------------+
| innodb_index_stats         |  // 索引的統計數據
| innodb_table_stats         |  // 表的統計數據
+----------------------------+
2 rows in set (0.13 sec)

innodb_table_stats表

mysql> select * from mysql.innodb_table_stats;
+---------------+-----------------------------------------+---------------------+--------+----------------------+--------------------------+
| database_name | table_name                              | last_update         | n_rows | clustered_index_size | sum_of_other_index_sizes |
+---------------+-----------------------------------------+---------------------+--------+----------------------+--------------------------+
| mall          | cms_help                                | 2022-04-14 15:26:26 |      0 |                    1 |                        0 |
  • database_name 資料庫名
  • table_name 表名
  • last_update 上次更新的時間
  • n_rows 即表行數
  • clustered_index_size 聚簇索引占的頁面數
  • sum_of_other_index_sizes 其他索引占用總的頁面數

n_rows統計方式

先取出幾個葉子頁面,然後計算這幾個葉子節點行數的平均值。

然後乘以全部葉子的頁面,就是全部的葉子節點數。這就是為什麼不准確。

clustered_index_size 統計方式

統計頁面數,分為兩個段,一個葉子段,一個非葉子段,從索引根節點找到兩個段,然後從段的結構找出占用的頁面數,流程如下。

  • 首先統計碎片區,碎片區占滿了就是32個頁,每個碎片區會占用一頁,沒有占滿32個就按碎片區的數量為頁面數。
  • 然後統計專屬段的區,就是直接計算鏈表中鏈的區數,然後區數直接*64頁。不管有沒有用滿,都直接算用滿了。這也是不准確的原因。

sum_of_other_index_sizes 統計類似

innodb_index_stats表

image

統計項有如下:

  • n_leaf_pages: 表示該索引的葉子節點占用多少個頁面。
  • size: 表示該索引一共占用的頁面數
  • n_diff_pfxNN: 表示對應索引列不重覆的值有多少,其中的NN對於聯合索引來說就是前01就是前一個列組合有幾個不重覆值,02就是前兩個列組合有幾個不重覆值。

對於NULL的定義

在MySQL中,跟null的任何表達式都為null。

null值對於二級索引的不重覆值來說有很大影響。對於index dive 來說就需要用到不重覆值來作為評估成本的參數。

複習:當in(...)裡面的參數太多,就不會執行index dive而是直接估計,查詢不重覆值然後除以總的記錄數,就可以得到每個單點區間的大概值數。

mysql> show variables like 'innodb_stats_method';
+---------------------+-------------+
| Variable_name       | Value       |
+---------------------+-------------+
| innodb_stats_method | nulls_equal |
+---------------------+-------------+
1 row in set, 1 warning (0.08 sec)

對於null值來說,預設是認為所有的null都是相等的。

nulls_unequal : 所有null都不為相等的。

nulls_ignored : 直接把null忽略掉。


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

-Advertisement-
Play Games
更多相關文章
  • 解決 Win10 Wsl2 IP 變化問題(2021.2.10) Win10 Wsl2 的 IP 地址每次重啟後都會變化,如果經常需要在 Win10 訪問 Wsl2 內的服務的話會比較麻煩,因此筆者尋找一種解決方案併在此記錄。 1. 產生環境 WSL2; Ubuntu 20.04 focal(on ...
  • echo echo 命令是 Linux bash 和 C shell中最常用的內置命令之一,通常用於腳本語言和批處理文件,用於標準輸出以及顯示文本內容等。echo命令在生產環境腳本中還是使用的非常多的,很多時候都要查看腳本執行是否正常,以及腳本執行到哪裡,都是通過echo命令來列印來定位 。 在寫腳 ...
  • 1、作業控制技巧 Bash環境中通過命令運行一個進程的時候,使【&】 符可以使改進程進入後臺 (base) [root@localhost ~]# sh test.sh & [1] 46963 (base) [root@localhost ~]# 將該進程放入後臺並暫停執行 Ctrl+z (base ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 準備工作:兩個U盤,一個大的作為系統盤,一個小的作為引導盤。 U盤分區 為什麼分盤 我們將u盤作為啟動盤之後,u盤文件不易區分整理,萬一不小心刪除了啟動盤的文件就不好了,所以我們可以將u盤一分為二,一部分作為啟動盤,另一部分作為讀寫盤,這樣就很合 ...
  • chmod怎麼用,Linux文件許可權管理 本文翻譯自Linux官網的Linux入門文章《File Permissions - chmod》,其中一些部分自作主張做了些修改 原文鏈接:File Permissions - chmod 原文 導言 Linux從UNIX繼承了文件所有權和許可權的觀念。這是因 ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 1. 問題描述 電腦上成功安裝VMware虛擬機後,安裝Ubuntu系統。Ubuntu系統無法聯網,多方檢查發現問題:宿主機的網路連接中沒有VMware Network Adapter VMnet1和VMware Network Adapter ...
  • 要實現這個示例,必須先安裝好hadoop和hive環境,環境部署可以參考我之前的文章: 大數據Hadoop原理介紹+安裝+實戰操作(HDFS+YARN+MapReduce) 大數據Hadoop之——數據倉庫Hive 【流程圖如下】 【示例代碼如下】 #!/usr/bin/env python # - ...
  • 首先,Hadoop會把輸入數據劃分成等長的輸入分片(input split) 或分片發送到MapReduce。Hadoop為每個分片創建一個map任務,由它來運行用戶自定義的map函數以分析每個分片中的記錄。在我們的單詞計數例子中,輸入是多個文件,一般一個文件對應一個分片,如果文件太大則會劃分為多個... ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...