不可“迷信”的row_examined

都是来自生产环境的经验教训哇

前言

row_examined通常是衡量SQL性能的一个指标,在一些场景中不能反映真实的扫描行数易产生误导

row_examined含义:

  • The number of rows examined by the server layer (not counting any processing internal to storage engines).

row_examined统计的是server层扫描的行数,不包括引擎层扫描的行数

官方文档:https://dev.mysql.com/doc/refman/8.4/en/slow-query-log.html

在生产中,以下场景不能“迷信” row_examined:

  • 使用到ICP优化
  • index merge场景
  • MySQL 8.0 行数统计、子查询row_examined缺陷

另外,存在order by且文件排序的的情况下,slow log中的row_examined会重复计算,参见:https://segmentfault.com/a/1190000022241604

验证

ICP场景

当优化器使用到ICP时,由于ICP是在引擎层过滤掉了一部分数据,这一部分数据并不会统计到row_examined中,正如官方文档中所说不包括引擎层处理

验证:

-- 表结构
mysql> show create table tt\G
*************************** 1. row ***************************
       Table: tt
Create Table: CREATE TABLE `tt` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c1` char(64) NOT NULL,
  `c2` char(64) NOT NULL,
  `c3` char(64) NOT NULL,
  `c4` char(64) NOT NULL,
  `c5` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_test` (`c1`,`c2`,`c3`)
) ENGINE=InnoDB AUTO_INCREMENT=980960 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

-- SQL执行计划
mysql> explain select * from tt where c1='a'  and  c3='aec7c' order by c5;
+----+-------------+-------+------------+------+---------------+----------+---------+-------+--------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key      | key_len | ref   | rows   | filtered | Extra                                 |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+--------+----------+---------------------------------------+
|  1 | SIMPLE      | tt    | NULL       | ref  | idx_test      | idx_test | 256     | const | 381888 |    10.00 | Using index condition; Using filesort |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+--------+----------+---------------------------------------+
1 row in set, 1 warning (0.00 sec)

mysql>  select * from tt where c1='a'  and  c3='aec7c' order by c5;
Empty set (1.69 sec)


-- 相关条件记录数
mysql> select count(*) from tt where c1='a';
+----------+
| count(*) |
+----------+
|   395216 |
+----------+
1 row in set (0.71 sec)

mysql> select count(*) from tt where c1='a' and c3='aec7c';
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (0.84 sec)


-- 由于使用了ICP,返回给server层的记录数为0,这里显示扫描行数为0 
# Time: 2024-09-11T09:43:09.484724+08:00
# User@Host: root[root] @ xxx [xxx]  Id:   350
# Query_time: 1.681396  Lock_time: 0.000193 Rows_sent: 0  Rows_examined: 0
SET timestamp=1726018989;
select * from tt where c1='a'  and  c3='aec7c' order by c5;

监控ICP的次数

-- 查看
mysql> SELECT name,subsystem,count,status FROM information_schema.innodb_metrics WHERE name LIKE '%icp%';
+------------------+-----------+-------+----------+
| name             | subsystem | count | status   |
+------------------+-----------+-------+----------+
| icp_attempts     | icp       |     0 | disabled |
| icp_no_match     | icp       |     0 | disabled |
| icp_out_of_range | icp       |     0 | disabled |
| icp_match        | icp       |     0 | disabled |
+------------------+-----------+-------+----------+
4 rows in set (0.00 sec)


-- 开启ICP监控

mysql> set global innodb_monitor_enable='icp%';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT name,subsystem,count,status FROM information_schema.innodb_metrics WHERE name LIKE '%icp%';
+------------------+-----------+-------+---------+
| name             | subsystem | count | status  |
+------------------+-----------+-------+---------+
| icp_attempts     | icp       |     0 | enabled |
| icp_no_match     | icp       |     0 | enabled |
| icp_out_of_range | icp       |     0 | enabled |
| icp_match        | icp       |     0 | enabled |
+------------------+-----------+-------+---------+
4 rows in set (0.01 sec)


-- 再次执行查询
mysql> select * from tt where c1='a'  and  c3='aec7c' order by c5;
Empty set (1.71 sec)


-- 可以看到发生了395217次的ICP,匹配条件的有0次
mysql> SELECT name,subsystem,count,status FROM information_schema.innodb_metrics WHERE name LIKE '%icp%';
+------------------+-----------+--------+---------+
| name             | subsystem | count  | status  |
+------------------+-----------+--------+---------+
| icp_attempts     | icp       | 395217 | enabled |
| icp_no_match     | icp       | 395216 | enabled |
| icp_out_of_range | icp       |      1 | enabled |
| icp_match        | icp       |      0 | enabled |
+------------------+-----------+--------+---------+
4 rows in set (0.00 sec)

索引合并场景

验证SQL

mysql> explain select * from test_merge where c1=1 and c2=5;
+----+-------------+------------+------------+-------------+---------------+---------------+---------+------+------+----------+---------------------------------------------+
| id | select_type | table      | partitions | type        | possible_keys | key           | key_len | ref  | rows | filtered | Extra                                       |
+----+-------------+------------+------------+-------------+---------------+---------------+---------+------+------+----------+---------------------------------------------+
|  1 | SIMPLE      | test_merge | NULL       | index_merge | idx_c1,idx_c2 | idx_c2,idx_c1 | 5,5     | NULL | 3071 |   100.00 | Using intersect(idx_c2,idx_c1); Using where |
+----+-------------+------------+------------+-------------+---------------+---------------+---------+------+------+----------+---------------------------------------------+
1 row in set, 1 warning (0.00 sec)

相关条件数据量:


mysql> select count(*) from test_merge where c1=1;
+----------+
| count(*) |
+----------+
|    53248 |
+----------+
1 row in set (0.02 sec)

mysql> select count(*) from test_merge where  c2=5;
+----------+
| count(*) |
+----------+
|     6144 |
+----------+
1 row in set (0.00 sec)


mysql> select count(*) from test_merge where c1=1 and c2=5;
+----------+
| count(*) |
+----------+
|     4096 |
+----------+
1 row in set (0.04 sec)

慢日志记录:显示扫描行数为4096

# Time: 2024-09-11T17:46:57.031698+08:00
# User@Host: root[root] @ xxx [xxx]  Id:   369
# Query_time: 0.091583  Lock_time: 0.000195 Rows_sent: 4096  Rows_examined: 4096
SET timestamp=1726048017;
select * from test_merge where c1=1 and c2=5;

通过performance_schema.table_io_waits_summary_by_index_usage监控

mysql> truncate table performance_schema.table_io_waits_summary_by_index_usage;
Query OK, 0 rows affected (0.00 sec)

mysql>  select OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,INDEX_NAME,COUNT_STAR,COUNT_READ,COUNT_WRITE,COUNT_FETCH,COUNT_INSERT,COUNT_UPDATE,COUNT_DELETE  from performance_schema.table_io_waits_summary_by_index_usage where object_schema='test'and object_name='test_merge';
+-------------+---------------+-------------+------------+------------+------------+-------------+-------------+--------------+--------------+--------------+
| OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | COUNT_STAR | COUNT_READ | COUNT_WRITE | COUNT_FETCH | COUNT_INSERT | COUNT_UPDATE | COUNT_DELETE |
+-------------+---------------+-------------+------------+------------+------------+-------------+-------------+--------------+--------------+--------------+
| TABLE       | test          | test_merge  | PRIMARY    |          0 |          0 |           0 |           0 |            0 |            0 |            0 |
| TABLE       | test          | test_merge  | idx_c1     |          0 |          0 |           0 |           0 |            0 |            0 |            0 |
| TABLE       | test          | test_merge  | idx_c2     |          0 |          0 |           0 |           0 |            0 |            0 |            0 |
| TABLE       | test          | test_merge  | NULL       |          0 |          0 |           0 |           0 |            0 |            0 |            0 |
+-------------+---------------+-------------+------------+------------+------------+-------------+-------------+--------------+--------------+--------------+
4 rows in set (0.01 sec)

mysql> select * from test_merge where c1=1 and c2=5 ;
...
+--------+------+------+------+
4096 rows in set (0.06 sec)

mysql>  select OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,INDEX_NAME,COUNT_STAR,COUNT_READ,COUNT_WRITE,COUNT_FETCH,COUNT_INSERT,COUNT_UPDATE,COUNT_DELETE  from performance_schema.table_io_waits_summary_by_index_usage where object_schema='test'and object_name='test_merge';
+-------------+---------------+-------------+------------+------------+------------+-------------+-------------+--------------+--------------+--------------+
| OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | COUNT_STAR | COUNT_READ | COUNT_WRITE | COUNT_FETCH | COUNT_INSERT | COUNT_UPDATE | COUNT_DELETE |
+-------------+---------------+-------------+------------+------------+------------+-------------+-------------+--------------+--------------+--------------+
| TABLE       | test          | test_merge  | PRIMARY    |          0 |          0 |           0 |           0 |            0 |            0 |            0 |
| TABLE       | test          | test_merge  | idx_c1     |      53237 |      53237 |           0 |       53237 |            0 |            0 |            0 |
| TABLE       | test          | test_merge  | idx_c2     |       6145 |       6145 |           0 |        6145 |            0 |            0 |            0 |
| TABLE       | test          | test_merge  | NULL       |       4096 |       4096 |           0 |        4096 |            0 |            0 |            0 |
+-------------+---------------+-------------+------------+------------+------------+-------------+-------------+--------------+--------------+--------------+
4 rows in set (0.01 sec)

看上去索引合并扫描的记录数没有计算,而仅计算通过索引合并取交集回表后返回给server层的记录数

向官方提交一个bug:https://bugs.mysql.com/bug.php?id=116058

8.0版本row_examined bug

验证:

-- 表行数
mysql> select count(*) from tt;
+----------+
| count(*) |
+----------+
|   786432 |
+----------+
1 row in set (0.16 sec)


-- 8.0版本

# Time: 2024-09-11T05:56:18.702333Z
# User@Host: root[root] @ xxx [xxx]  Id:  2425
# Query_time: 0.162342  Lock_time: 0.000005 Rows_sent: 1  Rows_examined: 0 Execution_time: 0.162337
SET timestamp=1726034178;
select count(*) from tt;

-- 有where条件的count查询是对的
# Time: 2024-09-11T05:57:01.883765Z
# User@Host: root[root] @ xxx [xxx]  Id:  2425
# Query_time: 0.007885  Lock_time: 0.000003 Rows_sent: 1  Rows_examined: 7966 Execution_time: 0.007882
SET timestamp=1726034221;
select count(*) from tt where id<10000;


# Time: 2024-09-11T02:47:49.094978Z
# User@Host: root[root] @ xxx [xxx]  Id:  2423
# Query_time: 16.053973  Lock_time: 0.000005 Rows_sent: 5  Rows_examined: 5 Execution_time: 16.053968
use test;
SET timestamp=1726022853;
select * from ((select * from tt order by id desc) union select * from tt ) t limit 5;

# Time: 2024-09-11T05:58:38.617579Z
# User@Host: root[root] @ xxx [xxx]  Id:  2425
# Query_time: 1.410755  Lock_time: 0.000005 Rows_sent: 5  Rows_examined: 5 Execution_time: 1.410750
SET timestamp=1726034317;
select * from ((select * from tt where id<100000 order by id desc) union select * from tt where id<100000 ) t limit 5;



-- 5.7版本正确
# Time: 2024-09-11T11:08:12.078660+08:00
# User@Host: root[root] @ xxx [xxx]  Id:   353
# Query_time: 53.095986  Lock_time: 0.001613 Rows_sent: 5  Rows_examined: 2335301
use zld;
SET timestamp=1726024092;
select * from ((select * from tt order by id desc) union select * from tt ) t limit 5;


# Time: 2024-09-11T11:10:39.379594+08:00
# User@Host: root[root] @ xxx [xxx]  Id:   353
# Query_time: 0.259323  Lock_time: 0.000143 Rows_sent: 1  Rows_examined: 778432
SET timestamp=1726024239;
select count(*) from tt;

order by场景重复计算

验证语句:

select * from test_merge where c1=9 ;
select * from test_merge where c1=9 order by c3 ;

两个sql在慢日志中的记录如下:

# Time: 2024-09-13T14:06:20.402286+08:00
# User@Host: root[root] @ xxx [xxx]  Id:   372
# Query_time: 0.006185  Lock_time: 0.000175 Rows_sent: 2048  Rows_examined: 2048
SET timestamp=1726207580;
select * from test_merge where c1=9;


# Time: 2024-09-13T14:06:23.131253+08:00
# User@Host: root[root] @ xxx [xxx]  Id:   372
# Query_time: 0.008835  Lock_time: 0.000167 Rows_sent: 2048  Rows_examined: 4096
SET timestamp=1726207583;
select * from test_merge where c1=9 order by c3;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值