为什么不建议使用SELECT * ?

“不要使用SELECT ”几乎已经成为数据库使用的一条金科玉律,就连很多公司也明确表示不得使用作为查询的字段列表,更是让这条规则拥有了权威的加持。

在这里插入图片描述
为什么不建议直接使用SELECT *?我们总得搞清楚这其中的原因吧,不要别人说不建议使用,我们就不使用,总得有自己的思考的。

增加磁盘开销

数据库本质上是将记录存储在磁盘上,查询操作就是一种进行磁盘IO的行为(要查询的记录在缓存中是没有的)。

我们查询的字段越多,读取的内容也就越多,对IO磁盘的开销也就会增大,特别是某些字段,如TEXT、MEDIUMTEXT或者BLOB等类型,磁盘IO开销增加会更加明显。

加重网络时延

数据每次都通过socket send buffer发送到客户端,查询一次数据量不大,如果一旦有人使用*将TEXT、MEDIUMTEXT或者BLOB 类型查出来,数据量会成指数级上升,网络传输的次数会增加,时间也就会增加。

无法使用覆盖索引

CREATE TABLE `user_innodb` 
( `id` int NOT NULL AUTO_INCREMENT, 
`name` varchar(255) DEFAULT NULL,
`gender` tinyint(1) DEFAULT NULL, 
`phone` varchar(11) DEFAULT NULL, PRIMARY KEY (`id`), 
KEY `IDX_NAME_PHONE` (`name`,`phone`) 
USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

上面我们通过创建一个表,存储引擎为InnoDB的表user_innodb,并设置id为主键,另外为name和phone创建了联合索引,同时向表中随机初始化800多万条数据。

InnoDB会自动为主键id创建一棵名为主键索引(又叫做聚簇索引)的B+树,这个B+树的特点就是叶子节点包含了完整的用户记录,B+树创建如下所示:
为什么不建议使用SELECT * ?_第1张图片
如果我们执行这种SQL语句:

SELECT * FROM user_innodb WHERE name = '将瑶';

上面的SQL语句会使用到IDX_NAME_PHONE索引,为一个二级索引。二级索引的叶子节点如下所示:
为什么不建议使用SELECT * ?_第2张图片
InnoDB存储引擎会根据搜索条件,在二级索引的叶子节点中找到name为将瑶的记录,但是二级索引中只记录了name、phone和主键id字段(因为我们用的是SELECT *,所以会全部查出来),因此InnoDB需要拿着主键id去主键索引中查找这一条完整的记录,因此这个过程叫做回表。

JOIN连接查询可能变慢

CREATE TABLE `t1` (
  `id` int NOT NULL,
  `m` int DEFAULT NULL,
  `n` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT;

CREATE TABLE `t2` (
  `id` int NOT NULL,
  `m` int DEFAULT NULL,
  `n` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT;

我们执行下面这条SQL语句:

SELECT * FROM t1 STRAIGHT_JOIN t2 ON t1.m = t2.m;

为了使结果更加明显,使用STRAIGHT_JOIN强制令t1表作为驱动表,t2表作为被驱动表。

因为在连接查询中,驱动表只会被访问一次,而被驱动表多次被访问,具体的访问次数决定于驱动表中符合查询记录的条数。由于强制确定了驱动表和被驱动表,下面讲解一下这两个表的连接本质:

t1作为驱动表,针对驱动表的过滤条件,执行对t1表的查询。因为没有过滤条件,也就是获取t1表的所有数据;

对上一步中获取到的结果集中的每一条记录,都分别到被驱动表中,根据连接过滤条件查找匹配记录。

如果用伪代码表示上述查询,代码如下:

// t1Res是针对驱动表t1过滤之后的结果集
for (t1Row : t1Res){
  // t2是完整的被驱动表
  for(t2Row : t2){
  	if (满足join条件 && 满足t2的过滤条件){
      发送给客户端
    }  
  }
}

这种方法最简单,也是最容易想到,但同时性能也是最差,这种方式被称为嵌套循环连接(Nested-LoopJoin,NLJ)。

怎样使连接速度才能变快呢?

其中一个方法是创建索引,最好是在被驱动表(t2)连接条件涉及到的字段上创建索引,因为被驱动表需要被查询多次,对被驱动表的访问本质上就是个单表查询(因为t1结果集已确定,每次连接t2的查询条件也就已确定)。

既然使用了索引,为了避免无法使用覆盖索引,我们最好也不要直接SELECT *,而是将真正用到的字段作为查询列,并为其建立适当的索引。

所以再次提醒,最好不要把*作为查询列表,只要把需要的列放到查询列表就可以了,这样可以减少分批的次数,也就减少了对被驱动表的访问次数。

你可能感兴趣的:(SQL,数据库,oracle,mysql,sqlserver,nosql,postgresql)