這周五下班前,發現了一個奇怪問題,大概是這個背景
一張表,結構為
Create Table: CREATE TABLE `out_table` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=Innodb AUTO_INCREMENT=36865 DEFAULT CHARSET=latin1
總共有37K rows的數據,數據大概是這樣
+----+------+ | id | name | +----+------+ | 1 | a | | 2 | b | | 3 | c | | 4 | D | | 5 | c | | 6 | c | | 7 | c | | 8 | c | | 9 | c | | 10 | a | +----+------+
運行了這個SQL
mysql> select id from out_table where id >10000 limit 1; +-------+ | id | +-------+ | 10001 | +-------+
1 row in set (0.00 sec)
速度也很快。
可是在運行explain的時候
mysql> explain select id from out_table where id >10000 limit 1; +----+-------------+-----------+-------+---------------+---------+---------+------+-------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+-------+---------------+---------+---------+------+-------+--------------------------+ | 1 | SIMPLE | out_table | range | PRIMARY | PRIMARY | 4 | NULL | 26358 | Using where; Using index | +----+-------------+-----------+-------+---------------+---------+---------+------+-------+--------------------------+
發現rows居然有,26358
查看MySQL官方文檔,rows所代表的含義
Column | Meaning |
rows | Estimate of rows to be examined |
翻譯過來就是,估計需要檢測的行數。
可是從DBA的直覺來說,id字段為主鍵,且為自增屬性,另外后面有個limit 1,那么無論如何rows應該不大於1才對。
那么是否explain沒有考慮后面的limit 1呢?
繼續運行SQL驗證
mysql> explain select id from out_table where id >10000 ; +----+-------------+-----------+-------+---------------+---------+---------+------+-------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+-------+---------------+---------+---------+------+-------+--------------------------+ | 1 | SIMPLE | out_table | range | PRIMARY | PRIMARY | 4 | NULL | 26358 | Using where; Using index | +----+-------------+-----------+-------+---------------+---------+---------+------+-------+--------------------------+
果然后面的limit 1根本不影響rows的值
那么這個rows是怎么算出來的呢?我們翻看下MySQL源碼(以5.6.23為例)。
為了避免不擅長的大段落描述,我把幾個關鍵的文件和函數粘貼出來。
文件 | 關鍵部分 | 下一步 |
sql/opt_explain_traditional.cc" | push(&items, column_buffer.col_rows, nil) | col_rows |
sql/opt_explain.cc | select->quick->records | records |
sql/opt_range.cc | check_quick_select |
而check_quick_select的功能,在MySQL源碼中的注釋為
Calculate estimate of number records that will be retrieved by a range scan on given index using given SEL_ARG intervals tree.
翻譯過來就是,這個方法僅僅根據給出的關於這個索引的條件和索引本身,來判斷需要掃描多少行。顯然limit 1和這個索引是沒有直接關系的。
所以新姿勢,get!