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查询分析的主要内容,如果未能解决你的问题,请参考以下文章

oracle 怎样查询某用户下的所有表的表名

oracle 怎样查询某用户下的所有表的表名

R语言dplyr包使用select函数通过索引查询或者排除数据列实战(Select Columns by Index)

Flux 查询语言中 SELECT <certain columns> 的等价物是啥?

查看Oracle都有哪些表或者视图

SQL Query 或 View with columns 取决于 SELECT 查询