什麼是成本,即SQL進行查詢的花費的時間成本,包含IO成本和CPU成本。
IO成本:即將資料頁從硬碟中讀取到記憶體中的讀取時間成本。通常1頁就是1.0的成本。
CPU成本:即是讀取和檢測是否滿足條件的時間成本。0.2是每行的CPU成本。
我們對其進行分析的具體步驟如下:
計算IO成本:
Data_Length計算頁的方法:Data_Length / (頁的大小 = 16 * 1024 = 16KB)
計算CPU成本:
所以我們可以將其兩個成本相加就是全表掃描的總成本。
如果我們選擇的索引執行的條件是區間。
where key1 > 10 and key1 < 1000 # 在計算單個索引的成本時對於其他條件直接為true。
就會進入以下步驟
所以IO成本為1 + n * 1,CPU成本為n*0.2 + 0.01 + n * 0.2。
where key1 in (a,b,c,...,z)
當我們選擇的索引的條件是上述的單點區間的情況時
我們查詢n個單點區間。
最後找出成本最小的,選擇對應方法執行SQL。
我們將這樣從索引中找到最小左邊界和最大右邊界的過程計算索引的數量稱為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 就可以得到每個索引值重複行數的平均值。
我們根據每個值重複的數量,乘以單點區間的數量,就充當每個單點區間匹配的記錄數。
對於驅動表的查詢後的得到記錄條數就叫做驅動表的扇出。
對於驅動表來說計算其最後記錄的條數,當能用到索引直接使用索引計算其條數,對於用不到索引的情況呢,就只能進行猜,就是對其進行評估(啟發式規則),最後得到驅動表的扇出。
然後我們要計算連線的成本,就需要確定連線的方式。
所以流程如下:
ps:內和外連線都是一樣的,區別內連線需要確定哪個作為驅動表成本更低。
我們會知道如果兩表連線時,驅動表的每一個結果行是作為一個常數傳入被驅動表進行查詢的。所以如果在連線條件上有索引的話,就可以加快連線,否則就要進行全表掃描。
當然了被驅動表的搜尋條件能有索引那更好了。也能加快其計算出最後結果。
我在之前的總結文章中,有一個錯誤,就是我提出一個能不能將被驅動表在自身搜尋條件篩選後應該快取起來這個觀點,其實是不對的,如果沒有被驅動表自身搜尋條件進行是沒有意義的。而且因為驅動表的結果行也是作為一個引數的搜尋條件連線的,然後一條一條的進行設定引數搜尋被驅動表符合的結果行。
我們知道的從磁碟從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)
如果更新直接使用update語句即可
然後讓系統重新整理以下這個值 flush optimizer_costs;
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)
我們在上面所過全表掃描計算成本時我們需要拿出表的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)
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 |
先取出幾個葉子頁面,然後計算這幾個葉子節點行數的平均值。
然後乘以全部葉子的頁面,就是全部的葉子節點數。這就是為什麼不準確。
統計頁面數,分為兩個段,一個葉子段,一個非葉子段,從索引根節點找到兩個段,然後從段的結構找出佔用的頁面數,流程如下。
sum_of_other_index_sizes 統計類似
統計項有如下:
在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忽略掉。