探究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
  • 移動開發(一):使用.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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...