SELECT *对比SELECT COLUMNS查询分析
Posted jfcat
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SELECT *对比SELECT COLUMNS查询分析相关的知识,希望对你有一定的参考价值。
对性能影响的几种情况
- 更少的字段被查询,导致更少的数据被从磁盘加载
但是这个关系并不是线性的。查询表的操作分为两个阶段:- 使用索引来查找数据行的地址
- 从选择的表中加载数据行
假设你现在只选择了哪些索引中的列,就不需要执行第二步了,大多数的数据库只会查询索引中的信息。
在参考文章中看到如下描述:
why should an index-only scan be 100 times faster?
the reason an Index-Only Scan can be 100 times faster is that an index access can easily deliver 100 rows per IO while the table access typically just fetches a few rows per IO.
其背景是说一个索引页如果索引字段不大可以容纳100条记录,因为索引是通过链表形式相互关联的。这样如果取的数据不多,而且都是索引数据,可以直接从索引页返回。
- 当使用服务器主内存时,可能接近慢5倍
虽然服务器避免存储结果在主内存,但是有些操作必须要使用主内存,例如sorting。所以如果你选择更多列,需要来使用更多内存,甚至需要使用磁盘来进行操作。 - 客户端处理无用数据,并进行垃圾收集
对于一些不需要的数据,通过加载后会被丢弃,GC需要处理这部分。
示例:
我们使用mysql8数据库,构建一张包含100W条数据的记录表,
CREATE TABLE `order0` (
`order_id` bigint NOT NULL AUTO_INCREMENT,
`product_id` mediumint NOT NULL COMMENT '产品id',
`shop_id` int NOT NULL COMMENT '店铺id',
`uid` mediumint NOT NULL COMMENT '购买用户id',
`nums` smallint NOT NULL COMMENT '购买数量',
`sale_price` decimal(9,2) NOT NULL COMMENT '销售价格',
`origin_price` decimal(9,2) NOT NULL COMMENT '原始价格',
`discount` varchar(45) DEFAULT '0.0' COMMENT '折扣',
`is_payed` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否支付',
`address` varchar(255) NOT NULL COMMENT '用户地址',
`phone` varchar(15) NOT NULL COMMENT '用户电话',
`username` varchar(45) NOT NULL COMMENT '用户名',
`province` varchar(45) NOT NULL COMMENT '省份',
`city` varchar(45) NOT NULL COMMENT '城市',
`area` varchar(45) NOT NULL COMMENT '区域',
`create_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`order_id`),
UNIQUE KEY `ordeid_UNIQUE` (`order_id`),
UNIQUE KEY `uid__index` (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
这里构建两个索引,一个是主键索引,一个是唯一索引
我们通过mysql的explain工具分析下进行select * from order0 where uid>1 limit 1 这样的sql查询,
mysql> explain select * from order0 where uid > 1 limit 1\\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: order0
partitions: NULL
type: range
possible_keys: uid__index
key: uid__index
key_len: 3
ref: NULL
rows: 495206
filtered: 100.00
Extra: Using index condition; Using MRR
1 row in set, 1 warning (0.00 sec)
select_type: SIMPLE(简单SELECT,不使用UNION或子查询等)
type:range:只检索给定范围的行,使用一个索引来选择行,能根据索引做范围的扫描
possible_keys:指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用. 查询只能使用一个索引
key:key列显示MySQL实际决定使用的键(索引)
key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度;在不损失精度的情况下,长度越短越好
rows: 表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
filtered:它指返回结果的行占需要读到的行的百分比
Extra:包含了mysql怎么处理查询的额外信息。从输出内容看主要是索引、查询条件和数据查询方式的信息。
性能从好到坏:using index>using where > using temporary | using filesort
通过这些信息我们就可以理解explain描述里面出现的概念,多出来的MRR概念可以看下相关文章 ,主要也是为了加速查询过程的。
然后我们再来执行select columns 语句
mysql> explain select uid from order0 where uid > 1 limit 1\\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: order0
partitions: NULL
type: range
possible_keys: uid__index
key: uid__index
key_len: 3
ref: NULL
rows: 495206
filtered: 100.00
Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)
这里只查询了uid字段,两者唯一区别就是extra,一个有MRR,一个有Where。看下官方描述:
A WHERE clause is used to restrict which rows to match against the next table or send to the client.
Reading rows using a range scan on a secondary index can result in many random disk accesses to the base table when the table is large and not stored in the storage engine’s cache.
从描述看起来where是来限制返回row数的,MRR是来优化通过索引来查询数据的方式,减少随机磁盘访问的。只要看到有磁盘访问,再怎么优化也比没有磁盘访问的情况要差。我们来看下查询时间情况。
mysql> show profiles;
+----------+------------+--------------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+--------------------------------------------------+
| 1 | 0.01360400 | select uid from order0 where uid > 1 limit 10000 |
| 2 | 0.02166600 | select * from order0 where uid > 1 limit 1 |
| 3 | 0.00230400 | select uid from order0 where uid > 1 limit 1 |
+----------+------------+--------------------------------------------------+
很明显,select * 比select uid低了一个量级,与分析判断情况一致。将第二个查询的columns加上几个非索引字段再看分析情况。
mysql> explain select uid,nums,sale_price,origin_price,discount,username,province,city,area,create_date,update_date from order0 where uid > 1 limit 1
-> \\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: order0
partitions: NULL
type: range
possible_keys: uid__index
key: uid__index
key_len: 3
ref: NULL
rows: 495206
filtered: 100.00
Extra: Using index condition; Using MRR
1 row in set, 1 warning (0.00 sec)
从这里也看到了MRR,其他结果和select * 差别不大,再看下profiles对比,
| 2 | 0.02166600 | select * from order0 where uid > 1 limit 1
| 11 | 0.01680800 | select SQL_NO_CACHE order_id,product_id,shop_id,uid,nums,sale_price,origin_price,discount,username,province,city,area,create_date,update_date from order0 where uid > 1 limit 1 |
从结果看select columns比select * 性能还是要好(具体原因下一篇文章再分析),所以能写columns还是写清楚吧,这样更符合mysql 优化执行的方式。
补充内容:
刚发现profile分析可以输出每个query的详细过程
mysql> show profile for query 2;
+--------------------------------+----------+
| Status | Duration |
+--------------------------------+----------+
| starting | 0.000238 |
| Executing hook on transaction | 0.000036 |
| starting | 0.000015 |
| checking permissions | 0.000011 |
| Opening tables | 0.000502 |
| init | 0.000015 |
| System lock | 0.000014 |
| optimizing | 0.000014 |
| statistics | 0.000550 |
| preparing | 0.000079 |
| executing | 0.019805 |
| end | 0.000039 |
| query end | 0.000013 |
| waiting for handler commit | 0.000116 |
| closing tables | 0.000085 |
| freeing items | 0.000046 |
| cleaning up | 0.000088 |
+--------------------------------+----------+
17 rows in set, 1 warning (0.00 sec)
这是“select * from order0 where uid > 1 limit 1” 查询的过程记录
mysql> show profile for query 10;
+--------------------------------+----------+
| Status | Duration |
+--------------------------------+----------+
| starting | 0.000316 |
| Executing hook on transaction | 0.000051 |
| starting | 0.000079 |
| checking permissions | 0.000147 |
| Opening tables | 0.000169 |
| init | 0.000048 |
| System lock | 0.000098 |
| optimizing | 0.000054 |
| statistics | 0.000248 |
| preparing | 0.000077 |
| executing | 0.015179 |
| end | 0.000030 |
| query end | 0.000013 |
| waiting for handler commit | 0.000031 |
| closing tables | 0.000039 |
| freeing items | 0.000027 |
| cleaning up | 0.000052 |
+--------------------------------+----------+
17 rows in set, 1 warning (0.03 sec)
这是“select order_id,product_id,shop_id,uid,nums,sale_price,origin_price,discount,username,province,city,area,create_date,update_date from order0 where uid > 1 limit 1”查询的过程记录
从这两个数据可以看到,比较大的数据偏差来之executing的过程。
mysql> show profile CPU,BLOCK IO for query 2;
+--------------------------------+----------+----------+------------+--------------+---------------+
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+--------------------------------+----------+----------+------------+--------------+---------------+
| starting | 0.000238 | 0.000115 | 0.000121 | 0 | 0 |
| Executing hook on transaction | 0.000036 | 0.000010 | 0.000027 | 0 | 0 |
| starting | 0.000015 | 0.000010 | 0.000004 | 0 | 0 |
| checking permissions | 0.000011 | 0.000008 | 0.000003 | 0 | 0 |
| Opening tables | 0.000502 | 0.000133 | 0.000280 | 0 | 0 |
| init | 0.000015 | 0.000008 | 0.000007 | 0 | 0 |
| System lock | 0.000014 | 0.000011 | 0.000002 | 0 | 0 |
| optimizing | 0.000014 | 0.000012 | 0.000002 | 0 | 0 |
| statistics | 0.000550 | 0.000190 | 0.000346 | 0 | 0 |
| preparing | 0.000079 | 0.000037 | 0.000042 | 0 | 0 |
| executing | 0.019805 | 0.013755 | 0.002958 | 0 | 0 |
| end | 0.000039 | 0.000010 | 0.000028 | 0 | 0 |
| query end | 0.000013 | 0.000005 | 0.000007 | 0 | 0 |
| waiting for handler commit | 0.000116 | 0.000028 | 0.000089 | 0 | 0 |
| closing tables | 0.000085 | 0.000032 | 0.000053 | 0 | 0 |
| freeing items | 0.000046 | 0.000015 | 0.000031 | 0 | 0 |
| cleaning up | 0.000088 | 0.000019 | 0.000069 | 0 | 0 |
+--------------------------------+----------+----------+------------+--------------+---------------+
17 rows in set, 1 warning (0.00 sec)
从这个分析看,executing中主要是cpu时间长。
参考:
https://use-the-index-luke.com/blog/2013-08/its-not-about-the-star-stupid
https://weblogs.asp.net/jongalloway/the-real-reason-select-queries-are-bad-index-coverage
https://use-the-index-luke.com/sql/clustering/index-only-scan-covering-index
https://www.cnblogs.com/qlqwjy/p/7767479.html
https://zhuanlan.zhihu.com/p/110154066
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html#explain-extra-information
https://dev.mysql.com/doc/refman/8.0/en/mrr-optimization.html
以上是关于SELECT *对比SELECT COLUMNS查询分析的主要内容,如果未能解决你的问题,请参考以下文章
R语言dplyr包使用select函数通过索引查询或者排除数据列实战(Select Columns by Index)