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/ ,如需转载,请注明出处,否则将追究法律责任。