http://www.cnblogs.com/LBSer/p/3333881.html
引子:
使用MySQL建立了一張表country,總共有才3121行記錄。
但是使用explain select count(*) from country;的時候,發現行數rows達到6897,讓我大吃一驚。
mysql> explain select count(*) from country; +----+-------------+---------+------+---------------+------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+------+---------------+------+---------+------+------+-------+ | 1 | SIMPLE | country | ALL | NULL | NULL | NULL | NULL | 6897 | NULL | +----+-------------+---------+------+---------------+------+---------+------+------+-------+
問題:為什麼explain的結果和真實的結果執行不一致,並且產生這麼大的誤差?
針對這個問題,上網查了些資料,特此發博文總結下,當然自己也是剛剛使用mysql,有很多不瞭解的地方,希望多多指正。
一、explain是什麼?
通過explain可以檢視MySQL的執行計劃,從而知道MySQL是如何處理我們的SQL語句。具體來說通過explain我們能得到一系列的關鍵資訊,比如哪些索引被實際使用,查詢了多少行等等。
explain使用Rows來告知我們資料庫即將要閱讀的行數,但是實際將要閱讀的行數和explain所記載的將要閱讀的行數可能會有差異,這是因為explain並沒有真的去執行sql語句從而得出行數,而是進行了某種預估。
二、explain怎麼預估行數
找了半天得知真相的我眼淚掉下來:http://lists.mysql.com/commits/115810
1)mysql-5.5之前
首先找到查詢第一個記錄所在的page(記為PLeft),統計PLeft裡的記錄數(記為Records_PLeft),之後找到最後一個記錄所在的page(記為PRight),統計PRight的記錄數(Records_PRight),之後將Records_PLeft與Records_PRight取平均,最後乘以總共的page數目(記為Page_Num)。公式如下:
Rows = ((Records_PLeft + Records_PRight)/2)*Page_Num
統計上講這個預估方法是很有偏的。比如總共4個page:page1(999 records), page2(1 record), page3(1 record), page4(1 record),這樣預估出來的Rows=((999+1)/2)*4 = 2000,然而實際上才總共才有1002個記錄。
2)mysql-5.5之後
上述預估偏差大的關鍵在於有偏,而有偏的關鍵在於取樣的page數太少了,事實上只取樣了邊界2個,新演算法的思路很簡單,增加取樣數目,比如取樣10個page,這樣可以在一定程度上降低偏差。
具體來說,mysql除了邊界2個外,還沿著左側page往右連續查詢8個page,如果總的page數目小於等於10個,那麼預估的Rows和真實的Rows一致。
Rows = ((Records_PLeft + Records_P1 + Records_P2 + ... + Records_P8 + Records_PRight)/10)*Page_Num
上述方法只是在一定程度上緩解了有偏的問題,但是不準確還是存在的,事實上樓主的mysql版本是5.6版本,可見還是沒有解決的很好。
三、思考
為什麼是從左往右連續選8個page,而不是在首尾之間隨機選擇8個page,既然要緩解取樣有偏的問題,那麼隨機選應該更好。猜想可能有兩個原因:1)隨機選擇每次explain得到的Rows不一樣,不方便應用;2)隨機選會造成I/O開銷,尤其是資料量大的時候,畢竟explain是希望能快速得到預估結果。
我覺得應該還有更好的演算法,能實現explain效率與精度的tradeoff,希望大家能給出建議。