ITPub博客

首页 > 数据库 > MySQL > MySQL执行计划里的rows,filtered,actual rows,examiend_rows的关系

MySQL执行计划里的rows,filtered,actual rows,examiend_rows的关系

原创 MySQL 作者:dingjun123 时间:2026-05-18 23:55:04 0 删除 编辑

MySQL执行计划里的rows,filtered,actual rows,examiend_rows的关系

rows是访问方法(全表,索引index access)的估算行数,不包括ICP

filtered是访问方法之后的过滤条件选择率,包括ICP和回表过滤,如果是树形计划

filtered的计算也是按照index dive->index statistics->histogram->default sel,如果用index hints的,则不会用index dive

actual rows是对应步骤真实返回行数,如果有回表则有单独的FILTER,单独的ICP是没有FILTER的,覆盖索引ICP也是单独FILTER,

这个和rows的计算方式差别大,rows是访问方法的,不包括ICP条件,而actaul rows不是覆盖索引的是包括ICP的,所以比较估算行数

应该按照rows*index_filter vs actaul rows要准点


examiend_rows是存储层返回给server层的行数,非覆盖索引扫描,包含了ICP不能准确反应索引扫描行数,覆盖索引因为执行计划的ICP会单独

有个类似回表过滤FILTER的才准。


filterd是type访问方式(全表或索引)之后过滤条件的选择率,对于全表扫描计算全部where条件选择率,

对于索引扫描:排除type中符合索引leftmost prefix规则的列,因为这些是直接访问索引的,ICP列要计算选择率,

所以这个filterred=icp选择率和回表过滤选择率组合。

非索引列也要计算选择。

(注意树形计划可能ICP也包括leftmost prefix列,这个要排除,按照一般规则即可)

(包括ICP列和回表过滤条件列,比如like前通配,非等值后续索引列,跳过的索引列,非索引列,这些是过滤选择率),

通过optimizer_trace可以看到filtering_effect条件,filtered就是此条件选择率。

show create table employees\G

*************************** 1. row ***************************

       Table: employees

Create Table: CREATE TABLE `employees` (

  `emp_no` int NOT NULL,

  `birth_date` date NOT NULL,

  `first_name` varchar(14) NOT NULL,

  `last_name` varchar(16) NOT NULL,

  `gender` enum('M','F') NOT NULL,

  `hire_date` date NOT NULL,

  PRIMARY KEY (`emp_no`),

  KEY `idx_emp_hire_date` (`hire_date`),

  KEY `idx1_emp` (`first_name`,`last_name`),

  KEY `idx_employees_hire_birth` (`hire_date`,`birth_date`),

  KEY `idx2_emp` (`first_name`,`last_name`,`hire_date`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 STATS_SAMPLE_PAGES=1057

1 row in set (0.01 sec)



1)MySQL普通explain执行计划的rows是访问方法扫描的行数,

如果是全表扫描,则是所有行,如果索引扫描,则是index access的行数,不包括icp,当然指标是估算的

rows * filtered则是最终结果估算的行数。


执行计划里的rows就是访问方法的,树形的也是,而actaul rows是包括ICP的结果,全表则是所有条件,索引回表有过滤还有单独的Filter,

没有回表过滤则没有单独的Filter,这行是索引访问,ICP,回表在一块的。


这个filtered可能包括index filtered和回表filtered,要看谓词情况。


所以普通explain估算的rows可以简单分析下索引扫描的数据量,一般用index dive还算准确。

如果rows很大,filtered很小,说明索引效率不高。


2)树形计划的rows和普通计划rows一样,是访问方法扫描的行数估算,如果全表就是全部行,如果走索引是index access扫描的行数估算,

不包括icp的,而真实行数是包括icp,包括回表过滤条件后的结果,这个要注意。


索引访问,索引过滤,回表是在一行上,不能判断慢在什么地方,

(不像oracle回表是分开的,pg呢?则index access,index filter放一起作为index cond)

可以结合普通执行计划,手动根据谓词情况(索引范围有access,filter谓词)分析


3)status指标:Handler_read_next和慢日志的examined_rows指标存在BUG

这个是返回给server层的行数,ICP肯定是在存储层了,所以包含ICP,只有全表扫描,覆盖索引和没有ICP的才准。


###

如果有icp,并且有回表,这两个指标不能反应真正扫描的行数,而是rows*index_filtered的结果,包括了ICP,不是*filtered,

如果没有回表,覆盖索引扫描,rows_examined是准确的。


###rows_examined

其真实含义是返回给server层的行数,然后server层可能再需要过滤,这个指标如果有ICP,其实可以作为判断回表效率高低。


普通explain的filtered可能包括icp的以及回表过滤的。


index filter是在storage层做的,发生在index access索引扫描的时候,而回表过滤条件则是在server层完成的。



###

如果是covering index不回表,则这两个指标可以作为索引扫描的行数,不包括索引过滤部分。





--1. 符合leftmost prefix的,where条件两个列都能参与索引访问,而且没有其他过滤条件,所以filtered=100

explain

select * from employees where first_name='Aamer' and last_name like 'B%';


##这个Using index condition其实没有啥用

+----+-------------+-----------+------------+-------+-------------------+----------+---------+------+------+----------+-----------------------+

| id | select_type | table     | partitions | type  | possible_keys     | key      | key_len | ref  | rows | filtered | Extra                 |

+----+-------------+-----------+------------+-------+-------------------+----------+---------+------+------+----------+-----------------------+

|  1 | SIMPLE      | employees | NULL       | range | idx1_emp,idx2_emp | idx1_emp | 94      | NULL |   26 |   100.00 | Using index condition |

+----+-------------+-----------+------------+-------+-------------------+----------+---------+------+------+----------+-----------------------+

1 row in set, 1 warning (0.00 sec)


树形计划rows是rows*filtered

explain analyze

    -> select * from employees where first_name='Aamer' and last_name like 'B%'\G

*************************** 1. row ***************************

EXPLAIN: -> Index range scan on employees using idx1_emp over (first_name = 'Aamer' AND 'B\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' <= last_name <= 'B???????????????'), with index condition: ((employees.first_name = 'Aamer') and (employees.last_name like 'B%'))  (cost=31.09 rows=26) (actual time=0.037..0.168 rows=26 loops=1)



--2.last_name是前导列,不能参与索引访问,所以计算选择率11.11%,这个是默认选择率

explain

select * from employees where first_name='Aamer' and last_name like '%B%';

+----+-------------+-----------+------------+------+-------------------+----------+---------+-------+------+----------+-----------------------+

| id | select_type | table     | partitions | type | possible_keys     | key      | key_len | ref   | rows | filtered | Extra                 |

+----+-------------+-----------+------------+------+-------------------+----------+---------+-------+------+----------+-----------------------+

|  1 | SIMPLE      | employees | NULL       | ref  | idx1_emp,idx2_emp | idx1_emp | 44      | const |  228 |    11.11 | Using index condition |

+----+-------------+-----------+------------+------+-------------------+----------+---------+-------+------+----------+-----------------------+

1 row in set, 1 warning (0.00 sec)


--3.examined_rows和Handler_read_next


examined_rows和Handler_read_next实际上是传给server层的行数(非索引过滤条件是在server层),

也即rows*filtered的行数,虽然这个rows和filtered是估算的。

也即如果有ICP的话,其实examined_rows和Handler_read_next不是很准,不能反应真正索引扫描的行数。

比如真正index access扫描了很多行,但是最终ICP后过滤返回很少行,那么慢日志里的Rows_examined以及Handler_read_next

指标就不能反应性能了,可以通过普通explain估算看rows和filtered,也可以根据谓词情况写sql检查慢的原因。


select count(*) from first_name='Aamer';



show status like '%Handler_read_next%';

+-------------------+-------+

| Variable_name     | Value |

+-------------------+-------+

| Handler_read_next | 49    |

+-------------------+-------+



# Time: 2024-10-11T21:37:24.490090+08:00

# User@Host: root[root] @ localhost []  Id:     8

# Query_time: 0.028395  Lock_time: 0.000004 Rows_sent: 49  Rows_examined: 49 Thread_id: 8 Errno: 0 Killed: 0 Bytes_received: 80 Bytes_sent: 2856 Read_first: 0 Read_last: 0 Read_key: 1 Read_next: 49 Read_prev: 0 Read_rnd: 0 Read_rnd_next: 0 Sort_merge_passes: 0 Sort_range_count: 0 Sort_rows: 0 Sort_scan_count: 0 Created_tmp_disk_tables: 0 Created_tmp_tables: 0 Start: 2024-10-11T21:37:24.461695+08:00 End: 2024-10-11T21:37:24.490090+08:00

SET timestamp=1728653844;

select * from employees where first_name='Aamer' and last_name like '%B%';



--4.如果是覆盖索引不回表,则examined rows和Handler_read_next是反应实际索引扫描的行数,就算有ICP也是

explain

select first_name,last_name,emp_no from employees where first_name='Aamer' and last_name like '%B%';


+----+-------------+-----------+------------+------+-------------------+----------+---------+-------+------+----------+--------------------------+

| id | select_type | table     | partitions | type | possible_keys     | key      | key_len | ref   | rows | filtered | Extra                    |

+----+-------------+-----------+------------+------+-------------------+----------+---------+-------+------+----------+--------------------------+

|  1 | SIMPLE      | employees | NULL       | ref  | idx1_emp,idx2_emp | idx1_emp | 44      | const |  228 |    11.11 | Using where; Using index |

+----+-------------+-----------+------------+------+-------------------+----------+---------+-------+------+----------+--------------------------+

1 row in set, 1 warning (0.00 sec)


 show status like '%Handler_read_next%';

+-------------------+-------+

| Variable_name     | Value |

+-------------------+-------+

| Handler_read_next | 228   |

+-------------------+-------+


# Time: 2024-10-11T21:49:12.489018+08:00

# User@Host: root[root] @ localhost []  Id:     8

# Query_time: 0.000665  Lock_time: 0.000015 Rows_sent: 49  Rows_examined: 228 Thread_id: 8 Errno: 0 Killed: 0 Bytes_received: 106 Bytes_sent: 1471 Read_first: 0 Read_last: 0 Read_key: 1 Read_next: 228 Read_prev: 0 Read_rnd: 0 Read_rnd_next: 0 Sort_merge_passes: 0 Sort_range_count: 0 Sort_rows: 0 Sort_scan_count: 0 Created_tmp_disk_tables: 0 Created_tmp_tables: 0 Start: 2024-10-11T21:49:12.488353+08:00 End: 2024-10-11T21:49:12.489018+08:00

SET timestamp=1728654552;

select first_name,last_name,emp_no from employees where first_name='Aamer' and last_name like '%B%';



5)如果除了索引访问条件还有回表过滤条件,则filtered=index_filtered和回表filtered组合

比如现在的filtered=3.7而不是11.11,而是按照like默认选择率11.11%和birth_date<默认选择率1/3

=11.11*1/3=3.7


普通执行计划的rows还是first_name='Aamer'通过index dive估算的行数,那么

examined_rows=first_name='Aamer' and last_name like '%B%'两个索引访问和过滤之后返回给server层行数

还是49,而最终返回行数还要加上回表过滤条件birth_date < '1960-1-1',最终Rows_sent: 27 


普通执行计划的rows反应访问方法:全表或index access行数,通过index dive估算一般比较准,可以判断索引访问效率,

而有icp的,examined_rows不一定能反应索引访问效率,所以要结合普通explain,explain analyze综合分析。


explain

select first_name,last_name,emp_no,birth_date 

from employees where first_name='Aamer' and last_name like '%B%'

and birth_date < '1960-1-1';

+----+-------------+-----------+------------+------+-------------------+----------+---------+-------+------+----------+------------------------------------+

| id | select_type | table     | partitions | type | possible_keys     | key      | key_len | ref   | rows | filtered | Extra                              |

+----+-------------+-----------+------------+------+-------------------+----------+---------+-------+------+----------+------------------------------------+

|  1 | SIMPLE      | employees | NULL       | ref  | idx1_emp,idx2_emp | idx1_emp | 44      | const |  228 |     3.70 | Using index condition; Using where |

+----+-------------+-----------+------------+------+-------------------+----------+---------+-------+------+----------+------------------------------------+

1 row in set, 1 warning (0.00 sec)



# Time: 2024-10-11T22:33:05.257998+08:00

# User@Host: root[root] @ localhost []  Id:     8

# Query_time: 0.000958  Lock_time: 0.000004 Rows_sent: 27  Rows_examined: 49 Thread_id: 8 Errno: 0 Killed: 0 Bytes_received: 146 Bytes_sent: 1289 Read_first: 0 Read_last: 0 Read_key: 1 Read_next: 49 Read_prev: 0 Read_rnd: 0 Read_rnd_next: 0 Sort_merge_passes: 0 Sort_range_count: 0 Sort_rows: 0 Sort_scan_count: 0 Created_tmp_disk_tables: 0 Created_tmp_tables: 0 Start: 2024-10-11T22:33:05.257040+08:00 End: 2024-10-11T22:33:05.257998+08:00

SET timestamp=1728657185;

select first_name,last_name,emp_no,birth_date 

from employees where first_name='Aamer' and last_name like '%B%'

and birth_date < '1960-1-1';









explain select/*+index(emp1 idx16_emp1)*/ * from emp1 where first_name like 'Ar%' and last_name like 'A%' and hire_date<date'1998-1-1';


##rows=11110是按照first_name like 'Ar%'做index dive计算的,

##filtered是按照后面两个条件计算的,last_name因为使用hints直接按11.11,而hire_date有直方图按照直方图计算

##实际上examined rows是存储层返回给server层行数,有ICP,实际上包含ICP条件,像这里还有回表的,而且是估算的,通过这个判断不出大概慢日志

##的examined rows,实际上真正的examined rows应该按照扫描行数,也即rows,MySQL的设计有缺陷

##如果包含ICP,则examined rows不能反应扫描的行数,除非是覆盖索引扫描

+----+-------------+-------+------------+-------+---------------+------------+---------+------+-------+----------+------------------------------------+

| id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows  | filtered | Extra                              |

+----+-------------+-------+------------+-------+---------------+------------+---------+------+-------+----------+------------------------------------+

|  1 | SIMPLE      | emp1  | NULL       | range | idx16_emp1    | idx16_emp1 | 133     | NULL | 11110 |     3.70 | Using index condition; Using where |

+----+-------------+-------+------------+-------+---------------+------------+---------+------+-------+----------+------------------------------------+

1 row in set, 1 warning (0.00 sec)



### 树形计划里的rows和actaul rows


###可以看到回表是单独的Filter,但是这个index range scan包括ICP,但是rows只是索引访问条件估算行数,rows=11110,actual rows是包括ICP的

##所以对比准不准,还要估算行数*icp的选择率与actaul rows比较才对

explain analyze select/*+index(emp1 idx16_emp1)*/ * from emp1 where first_name like 'Ar%' and last_name like 'A%' and hire_date<date'1998-1-1'\G

*************************** 1. row ***************************

EXPLAIN: -> Filter: (emp1.hire_date < DATE'1998-01-01')  (cost=11439.95 rows=411) (actual time=0.042..4.550 rows=219 loops=1)

    -> Index range scan on emp1 using idx16_emp1 over ('Ar' <= first_name <= 'Ar????????????????????????????????????????????????????????' AND 'A' <= last_name <= 'A????????????????????????????????????????????????????????????'), with index condition: ((emp1.first_name like 'Ar%') and (emp1.last_name like 'A%'))  (cost=11439.95 rows=11110) (actual time=0.040..4.512 rows=221 loops=1)


1 row in set, 1 warning (0.01 sec)



##覆盖索引没有问题

(root@192.168.44.128) [employees_22:15:27]> explain select/*+index(emp1 idx16_emp1)*/ first_name,last_name from emp1 where first_name like 'Ar%' and last_name like 'A%';

+----+-------------+-------+------------+-------+---------------+------------+---------+------+-------+----------+--------------------------+

| id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows  | filtered | Extra                    |

+----+-------------+-------+------------+-------+---------------+------------+---------+------+-------+----------+--------------------------+

|  1 | SIMPLE      | emp1  | NULL       | range | idx16_emp1    | idx16_emp1 | 133     | NULL | 11110 |    11.11 | Using where; Using index |

+----+-------------+-------+------------+-------+---------------+------------+---------+------+-------+----------+--------------------------+

1 row in set, 1 warning (0.00 sec)


===》rows*filtered=11110*11.11/100=1234

###覆盖索引ICP有个单独的类似回表过滤的Filter

(root@192.168.44.128) [employees_22:15:55]> explain format=tree select/*+index(emp1 idx16_emp1)*/ first_name,last_name from emp1 where first_name like 'Ar%' and last_name like 'A%'\G

*************************** 1. row ***************************

EXPLAIN: -> Filter: ((emp1.first_name like 'Ar%') and (emp1.last_name like 'A%'))  (cost=2402.55 rows=1234)

    -> Covering index range scan on emp1 using idx16_emp1 over ('Ar' <= first_name <= 'Ar????????????????????????????????????????????????????????' AND 'A' <= last_name <= 'A????????????????????????????????????????????????????????????')  (cost=2402.55 rows=11110)


1 row in set, 1 warning (0.00 sec)


##覆盖索引的actaul rows=6095,就是实际扫描的行数,不包括ICP的last_name条件,last_name放到Filter里了

(root@192.168.44.128) [employees_22:16:11]> explain analyze select/*+index(emp1 idx16_emp1)*/ first_name,last_name from emp1 where first_name like 'Ar%' and last_name like 'A%'\G

*************************** 1. row ***************************

EXPLAIN: -> Filter: ((emp1.first_name like 'Ar%') and (emp1.last_name like 'A%'))  (cost=2402.55 rows=1234) (actual time=0.026..3.952 rows=221 loops=1)

    -> Covering index range scan on emp1 using idx16_emp1 over ('Ar' <= first_name <= 'Ar????????????????????????????????????????????????????????' AND 'A' <= last_name <= 'A????????????????????????????????????????????????????????????')  (cost=2402.55 rows=11110) (actual time=0.023..2.769 rows=6095 loops=1)


1 row in set, 1 warning (0.00 sec)


###这里Rows_examined: 6095

# Time: 2025-12-06T22:50:18.889093+08:00

# User@Host: root[root] @ dingjun123 [192.168.44.128]  Id:     9

# Query_time: 0.006746  Lock_time: 0.000004 Rows_sent: 1  Rows_examined: 6095 Thread_id: 9 Errno: 0 Killed: 0 Bytes_received: 139 Bytes_sent: 507 Read_first: 0 Read_last: 0 Read_key: 1 Read_next: 6095 Read_prev: 0 Read_rnd: 0 Read_rnd_next: 0 Sort_merge_passes: 0 Sort_range_count: 0 Sort_rows: 0 Sort_scan_count: 0 Created_tmp_disk_tables: 0 Created_tmp_tables: 0 Start: 2025-12-06T22:50:18.882347+08:00 End: 2025-12-06T22:50:18.889093+08:00

SET timestamp=1765032618;


##可以看到不包括ICP

select count(*) from emp1 where first_name like 'Ar%';

+----------+

| count(*) |

+----------+

|     6095 |

+----------+

1 row in set (0.00 sec)



##非覆盖索引:


explain analyze select/*+index(emp1 idx16_emp1)*/ * from emp1 where first_name like 'Ar%' and last_name like 'A%'


##非覆盖索引属性计划也没有FILTER,然后examined rows包括了ICP,不准

*************************** 1. row ***************************

EXPLAIN: -> Index range scan on emp1 using idx16_emp1 over ('Ar' <= first_name <= 'Ar????????????????????????????????????????????????????????' AND 'A' <= last_name <= 'A????????????????????????????????????????????????????????????'), with index condition: ((emp1.first_name like 'Ar%') and (emp1.last_name like 'A%'))  (cost=11439.95 rows=11110) (actual time=0.101..3.457 rows=221 loops=1)


1 row in set, 1 warning (0.01 sec)



# Time: 2025-12-06T22:52:13.088223+08:00

# User@Host: root[root] @ dingjun123 [192.168.44.128]  Id:     9

# Query_time: 0.004168  Lock_time: 0.000005 Rows_sent: 1  Rows_examined: 221 Thread_id: 9 Errno: 0 Killed: 0 Bytes_received: 120 Bytes_sent: 436 Read_first: 0 Read_last: 0 Read_key: 1 Read_next: 221 Read_prev: 0 Read_rnd: 0 Read_rnd_next: 0 Sort_merge_passes: 0 Sort_range_count: 0 Sort_rows: 0 Sort_scan_count: 0 Created_tmp_disk_tables: 0 Created_tmp_tables: 0 Start: 2025-12-06T22:52:13.084055+08:00 End: 2025-12-06T22:52:13.088223+08:00

SET timestamp=1765032733;

explain analyze select/*+index(emp1 idx16_emp1)*/ * from emp1 where first_name like 'Ar%' and last_name like 'A%';


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/7655508/viewspace-3171586/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2008-05-07

  • 博文量
    45
  • 访问量
    90562