1. 程式人生 > 其它 >MySQL基於成本的優化與成本計算

MySQL基於成本的優化與成本計算

一條查詢語句的執行成本是由下面兩個部分組成:

I/O 成本

我們的表經常使用的 MyISAM 、 InnoDB 儲存引擎都是將資料和索引都儲存到磁碟上的,當我們想查詢表中的 記錄時,需要先把資料或者索引載入到記憶體中然後再操作。這個從磁碟到記憶體這個載入的過程損耗的時間稱 之為 I/O 成本

CPU 成本

讀取以及檢測記錄是否滿足對應的搜尋條件、對結果集進行排序等這些操作損耗的時間稱之為 CPU 成本。

對於 InnoDB 儲存引擎來說,頁是磁碟和記憶體之間互動的基本單位,MySQL規定讀取一個頁面花費的 成本預設是 1.0 ,讀取以及檢測一條記錄是否符合搜尋條件的成本預設是 0.2 。 1.0 、 0.2 這些數字稱之為 成 本常數。


※不管讀取記錄時需不需要檢測是否滿足搜尋條件,其成本都算是0.2。

單表查詢

優化器優化語句的步驟

1. 根據搜尋條件,找出所有可能使用的索引

2. 計算全表掃描的代價

  全表掃描的意思就是把聚簇索引中的記錄都依次和給定的搜尋條件做一下比較,把 符合搜尋條件的記錄加入到結果集,所以需要將聚簇索引對應的頁面載入到記憶體中,然後再檢測記錄是否符合搜 索條件。

  由於查詢成本= I/O 成本+ CPU 成本,所以計算全表掃描的代價需要兩個資訊: 聚簇索引佔用的頁面數 和 該表中的記錄數

    可以使用 SHOW TABLE STATUS 語句來查看錶的統計資訊

    Rows 本選項表示表中的記錄條數。對於使用 MyISAM 儲存引擎的表來說,該值是準確的,對於使用 InnoDB 儲存引 擎的表來說,該值是一個估計值。

    Data_length 本選項表示表佔用的儲存空間位元組數。使用 MyISAM 儲存引擎的表來說,該值就是資料檔案的大小,對於使 用 InnoDB 儲存引擎的表來說,該值就相當於聚簇索引佔用的儲存空間大小,

    也就是說可以這樣計算該值的大小: Data_length = 聚簇索引的頁面數量 x 每個頁面的大小

    反向來推匯出 聚簇索引的頁面數量 : 聚簇索引的頁面數量 = Data_length÷每個頁面的大小

    此時i/o成本=聚簇索引的頁面數量 * 1.0(載入一個頁面的成本常數)+1.1(一個微調值,固定的)

    cpu成本=rows(是統計資料中表的記錄數,估計值) * 0.2(訪問一條記錄 所需的成本常數)+1.0(一個微調值,固定的)

    總成本=cpu成本+i/o成本

3. 計算使用不同索引執行查詢的代價

  MySQL 查詢優化器先分 析使用唯一二級索引的成本,再分析使用普通索引的成本,最後還要分析是否可能使用到索引合併。

  範圍區間數量

  不論某個範圍區間的二級索引到底佔用了多少頁面,查詢優化器粗暴的認為讀取索引的一個範圍區間的 I/O 成本和讀取一個頁面是相同的。

  需要回表的記錄數

  優化器需要計算二級索引的某個範圍區間到底包含多少條記錄,對於本例來說就是要計算 idx_key2 在 (10, 1000) 這個範圍區間中包含多少二級索引記錄,計算過程是這樣的:

   步驟1:先根據 key2 > 10 這個條件訪問一下 idx_key2 對應的 B+ 樹索引,找到滿足 key2 > 10 這個條 件的第一條記錄,我們把這條記錄稱之為 區間最左記錄 。我們前頭說過在 B+ 數樹中定位一條記錄的過 程是賊快的,是常數級別的,所以這個過程的效能消耗是可以忽略不計的。

  步驟2:然後再根據 key2 < 1000 這個條件繼續從 idx_key2 對應的 B+ 樹索引中找出第一條滿足這 個條件的記錄,我們把這條記錄稱之為 區間最右記錄 ,這個過程的效能消耗也可以忽略不計的。

  步驟3:如果 區間最左記錄 和 區間最右記錄 相隔不太遠(在 MySQL 5.7.21 這個版本里,只要相 隔不大於10個頁面即可),那就可以精確統計出滿足 key2 > 10 AND key2 < 1000 條件的二級索引 記錄條數。否則只沿著 區間最左記錄 向右讀10個頁面,計算平均每個頁面中包含多少記錄,然後 用這個平均值乘以 區間最左記錄 和 區間最右記錄 之間的頁面數量就可以了。

  那怎麼估計 區間最左記錄 和 區間最右記錄 之間有多少個頁面呢?

  如圖,我們假設 區間最左記錄 在 頁b 中, 區間最右記錄 在 頁c 中,那麼我們想計算 區間最左記 錄 和 區間最右記錄 之間的頁面數量就相當於計算 頁b 和 頁c 之間有多少頁面,而每一條 目錄項 記錄 都對應一個數據頁,所以計算 頁b 和 頁c 之間有多少頁面就相當於計算它們父節點(也就是 頁a)中對應的目錄項記錄之間隔著幾條記錄。在一個頁面中統計兩條記錄之間有幾條記錄的成本 就賊小了。 不過還有問題,如果 頁b 和 頁c 之間的頁面實在太多,以至於 頁b 和 頁c 對應的目錄項記錄都不 在一個頁面中該咋辦?繼續遞迴啊,也就是再統計 頁b 和 頁c 對應的目錄項記錄所在頁之間有多少 個頁面。之前我們說過一個 B+ 樹有4層高已經很了不得了,所以這個統計過程也不是很耗費效能。

  總結:索引掃描的成本計算=i/o成本+cpu成本=(範圍區間的數量+預估的二級索引記錄條數)+(讀取二級索引記錄的成本 + 讀取並檢測回表後聚簇 索引記錄的成本)

  

4. 對比各種執行方案的代價,找出成本最低的那一個

多表查詢的成本(兩個表及以上)

  MySQL 中連線查詢採用的是巢狀迴圈連線演算法,驅動表會被訪問一次,被驅動表可能會被訪問多次,對於兩表連線查詢來說,它的查詢成本由下邊兩個部分構成:

    1.單次查詢驅動表的成本

    2.多次查詢被驅動表的成本(具體查詢多少次取決於對驅動表查詢的結果集中有多少條記錄)

  我們把對驅動表進行查詢後得到的記錄條數稱之為驅動表的 扇出 (英文名: fanout )。很顯然驅動表的扇出值 越小,對被驅動表的查詢次數也就越少,連線查詢的總成本也就越低。

  連線查詢總成本 = 單次訪問驅動表的成本 + 驅動表扇出數 x 單次訪問被驅動表的成本

多表連線的成本分析

  首先要考慮一下多表連線時可能產生出多少種連線順序:

   對於兩表連線,比如表A和表B連線 只有 AB、BA這兩種連線順序。其實相當於 2 × 1 = 2 種連線順序。

  對於三表連線,比如表A、表B、表C進行連線 有ABC、ACB、BAC、BCA、CAB、CBA這麼6種連線順序。其實相當於 3 × 2 × 1 = 6 種連線順序。

  對於四表連線的話,則會有 4 × 3 × 2 × 1 = 24 種連線順序。 對於 n 表連線的話,則有 n × (n-1) × (n-2) × ··· × 1 種連線順序,就是n的階乘種連線順序, 也就是 n! 。

  有 n 個表進行連線, MySQL 查詢優化器要每一種連線順序的成本都計算一遍。不過 MySQL 設計了很多辦法減少計算非常多種連線順序的成本的方法:

  1. 提前結束某種順序的成本評估

  MySQL 在計算各種連結順序的成本之前,會維護一個全域性的變數,這個變量表示當前最小的連線查詢成本。 如果在分析某個連線順序的成本時,該成本已經超過當前最小的連線查詢成本,那就壓根兒不對該連線順序 繼續往下分析了。比方說A、B、C三個表進行連線,已經得到連線順序 ABC 是當前的最小連線成本,比方 說 10.0 ,在計算連線順序 BCA 時,發現 B 和 C 的連線成本就已經大於 10.0 時,就不再繼續往後分析 BCA 這個連線順序的成本了。

  2.系統變數 optimizer_search_depth

  為了防止無窮無盡的分析各種連線順序的成本,設計 MySQL 的大叔們提出了 optimizer_search_depth 系統 變數,如果連線表的個數小於該值,那麼就繼續窮舉分析每一種連線順序的成本,否則只對與 optimizer_search_depth 值相同數量的表進行窮舉分析。很顯然,該值越大,成本分析的越精確,越容易 得到好的執行計劃,但是消耗的時間也就越長,否則得到不是很好的執行計劃,但可以省掉很多分析連線成 本的時間。

  3.根據某些規則壓根兒就不考慮某些連線順序

  即使是有上邊兩條規則的限制,但是分析多個表不同連線順序成本花費的時間還是會很長,所以 提出了一些所謂的 啟發式規則 (就是根據以往經驗指定的一些規則),凡是不滿足這些規則的 連線順序壓根兒就不分析,這樣可以極大的減少需要分析的連線順序的數量,但是也可能造成錯失最優的執 行計劃。他們提供了一個系統變數 optimizer_prune_level 來控制到底是不是用這些啟發式規則。