资讯详情

MySQL里为什么会建议不要使用SELECT *

在这里插入图片描述 “不要使用SELECT 几乎成了MySQL使用的金科玉律,甚至是阿里Java开发手册也明确规定不得使用作为查询的字段列表,这一规则得到了权威的祝福。 但是,我在开发过程中直接使用它SELECT *原因有两个:

1:由于简单,开发效率很高,如果后期经常添加或修改字段,SQL句子不需要改变; 2:我认为过早优化是一个坏习惯,除非你可以确定你最终需要什么字段,并建立适当的索引;否则,我会选择遇到麻烦SQL当然,优化的前提是麻烦不是致命的。但我们必须知道为什么不直接使用它SELECT *,本文从四个方面给出了理由。

我们知道 MySQL 本质上,用户记录存储在磁盘上,因此查询操作是一种磁盘IO行为(前提是内存中没有缓存要查询的记录)。

查询的字段越多,读取的内容就越多,所以会增加磁盘 IO 费用。特别是当某些字段是 TEXT、MEDIUMTEXT或者BLOB 效果特别明显。

那使用SELECT *会不会使MySQL占用更多内存?

理论上不是,因为对Server就层而言,它不是在内存中存储完整的结果集后立即传输给客户端,而是每次从存储引擎获得一行,它就写一个叫做net_buffer在内存空间中,该内存的大小由系统变量net_buffer_length来控制,默认是16KB;当net_buffer写满后,将内存空间写入当地网络栈socket send buffer中写数据发送给客户端,发送成功(客户端读取完成)后清空net_buffer,然后继续读取下一行并写入。

也就是说,在默认情况下,结果集占用的内存空间最大net_buffer_length只是大小,不会因为几个字段而占用额外的内存空间。

承担最后一点,虽然每次都是socket send buffer数据发送给客户端,单次数据量似乎不大,但真的有人使用*把TEXT、MEDIUMTEXT或者BLOB 也发现了类型字段,总数据量大,直接导致网络传输次数增加。

如果MySQL与应用程序不在同一台机器上的费用非常明显。MySQL服务器和客户端是在同一台机器上使用的协议还是TCP,通信也需要额外的时间。

为了说明这个问题,我们需要建一个表:

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创建了联合索引,最在表中随机初始化500W 条数据。

InnoDB将自动作为主键id创建一个叫做主键索引(也叫聚簇索引)的树B 树,这个B 树最重要的特点是叶节点包含完整的用户记录,可能是这样的。

如果我们执行这个语句:

SELECT * FROM user_innodb WHERE name = 蝉沐风; 

使用EXPLAIN检查句子的执行计划: 发现这个SQL使用语句IDX_NAME_PHONE索引,这是二级索引。二级索引的叶节点是这样的: nnoDB存储引擎将根据搜索条件在二次索引的叶节点中找到name蝉沐风的记录只记录在二级索引中name、phone和主键id字段(谁让我们用?SELECT *呢),因此InnoDB需要拿主键id在主键索引中找到这个完整的记录,这个过程叫做回表。

想想看,如果二次索引的叶节点上有我们想要的所有数据,是否不需要返回表格?是的,这就是覆盖索引。

例如,我们只是想搜索name、phone主键字段。

SELECT id, name,  phone FROM user_innodb WHERE name = "蝉沐风"; 

使用EXPLAIN检查句子的执行计划: 可以看到Extra一列显示Using index,这意味着我们的查询列表和搜索条件只包含一个索引列,即使用覆盖索引,可以直接放弃返回操作,大大提高查询效率。

我们创建了两张表t1,t2进行连接操作,解释下一个问题,并向前t1表插入100条数据t1000条数据插入2中。

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; 

若执行以下语句:

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

我在这里用过STRAIGHT_JOIN强制令t1表作为驱动表,t2表作为驱动表。

对于连接查询,驱动表只会被访问一次,而驱动表会被访问很多次。具体访问次数取决于驱动表中符合查询记录的记录条数。由于驱动表和驱动表已被强制确定,让我们谈谈两个表连接的本质:

1:t1作为驱动表,根据驱动表的过滤条件执行t1表查询。由于没有过滤条件,即获取t1表的所有数据; 2:根据连接过滤条件,将上一步获得的结果集中的每个记录分别到驱动表中查找匹配记录。

这种方法最简单,但性能也最差。这种方法称为嵌套循环连接(Nested-LoopJoin,NLJ)。如何加快连接速度?

其中一种方法是创建索引,最好是在驱动表中(t2)在涉及连接条件的字段上创建索引。毕竟被驱动表需要查询很多次,对被驱动表的访问本质上只是单表查询(因为t1.结果是固定的,每次连接t2.查询条件也定死)。

既然使用了索引,为了避免重蹈覆盖索引的覆辙,我们也应该尽量不要直接SELECT *,相反,将真正使用的字段作为查询列,并为其建立适当的索引。

但是如果我们不使用索引,MySQL真的按照嵌套循环查询的方式连接查询吗?当然不是。毕竟这种嵌套循环查询太慢了!

在MySQL8.0之前,MySQL基于块的嵌套循环连接(Block Nested-Loop Join,BLJ)方法,MySQL8.0又推出了hash join这两种方法都是为了解决一个问题,即尽量减少被驱动表的访问次数。

这两种方法都有一种叫做join buffer固定大小的内存区域存储多个驱动表结果集中的记录(两种方法的区别在于存储形式不同),因此当将驱动表的记录加载到内存时,一次性和join buffer由于匹配过程是在内存中完成的,中多个驱动表中的记录是匹配的,因此,被驱动表可以显著减少I/O从磁盘上重复加载驱动表的成本大大降低。使用join buffer如下图所示: 让我们看看上述连接查询的执行计划,发现它确实被使用了hash join(前提是不作为t2表的连接查询字段创建索引,否则将使用索引,不会使用join buffer)。

最好的情况是join buffer足够大,可以容纳驱动表结果集中的所有记录,只需访问驱动表一次即可完成连接操作。我们可以用join_buffer_size这个系统变量进行配置,默认大小为256KB。如果无法安装,则必须将驱动表的结果分批集中join buffer中间,内存对比完成后,清空join buffer然后安装下一批结果集,直到连接完成。

重点来了!并不是所有的驱动表记录列都会被放入join buffer只有查询列表中的列和过滤条件中的列才会放在中间join buffer所以再次提醒我们,最好不要*作为查询列表,我们只需要把我们关心的列放在查询列表中,这样我们就可以了join buffer在中间放置更多减少批次数,对被驱动表的访问次数自然减少。

标签: selet传感器b08系列

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台