这周五下班前,发现了一个奇怪问题,大概是这个背景
一张表,结构为
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!