切忌 SELECT *,就算表只有一列

科技   2024-12-06 17:05   江苏  

将 脚本之家 设为“星标

第一时间收到文章更新

出处:Bytebase(ID:Bytebase)

原文地址 https://x.com/hnasr/status/1856745402399359315

尽量避免 SELECT *,即使在单列表上也是如此 -- 如果你现在不同意这一点,读完这篇文章,你可能就要动摇了。

2012年的一个故事

这是我 12 年前(约 2012-2013 年)在客户后台应用程序中遇到的一个真实故事。

当时,后端 API 一直稳定运行,速度仅为个位数毫秒。但突然有一天,应用程序变得非常迟钝。

我们检查了提交,没发现明显的问题,大部分改动都是良性的。即使恢复了所有提交(有人会懂,作这样无意义的尝试,就说明走投无路了),程序仍然很慢,API 响应时间从原先的几毫秒暴增到 500 毫秒至 2 秒不等。

鉴于后台没有导致速度变慢的变更,我们检查了数据库查询,发现 3 个具有超大文件的 blob 字段,它们表上的 SELECT * 正在返回到后台应用程序。

原来,这个表只有两个整数列,API 运行 SELECT * 来返回并使用这两个字段。但后来管理员添加了三个 blob 字段,由另一个应用程序使用和填充。这些 blob 字段没有返回给客户端,后端 API 却需要拉取其他应用程序填充的额外字段,造成数据库、网络和协议序列化开销。

数据库读取如何进行
在行存储数据库引擎中,行以「页面」为单位存储。每页有一个固定的页眉,包含多条记录;每条记录有一个记录页眉,后面跟着各自的列。例如 PostgreSQL 中的示例:

当数据库获取一个页面并将其放入共享缓冲池,我们就可以访问该页面中的所有行和列。如果内存中所有列都是现成的,那么 SELECT * 真的又慢又费钱吗?如果是,为什么会这样?以下将展开探讨这些问题。

跟仅索引扫描说拜拜‍‍
使用 SELECT * 意味着数据库优化器不能选择只扫描索引。
例如,假设需要 90 分以上学生的 ID,而成绩列上有一个索引包含作为非键的学生 ID,这个索引就非常适合查询。
但是由于需要所有字段,数据库需要访问堆数据页来获取剩余字段,从而增加了随机读取次数,进而增加 I/O。相对地,不使用 SELECT *,数据库只需扫描成绩索引并返回 ID 即可。

反序列化成本
反序列化或解码是将原始字节转换为数据类型。这包括获取字节序列(通常来自文件、网络通信等),并将其转换回结构化更强的数据格式,如编程语言中的对象或变量。
执行 SELECT * 查询时,数据库需要反序列化所有列,即使是特定使用情况下可能不需要的列。这会增加计算开销,降低查询性能。只选择必要的列,可以降低反序列化成本,提高查询效率。

并非所有列都是内联的‍‍‍
SELECT * 查询的一个重要问题是,并非所有列都存储在页面内。文本或 blob 等大型列可能存储在外部表中,只有请求时才会检索(例如 Postgres TOAST 表)。这些列通常都经过压缩,因此在执行包含大量文本字段、几何数据或 blob 的 SELECT * 查询时,会给数据库带来额外负担,即从外部表中获取值、解压缩并将结果返回给客户端。

网络成本
将查询结果发送到客户端前,必须根据数据库支持的通信协议对其序列化。需要序列化的数据越多,CPU 工作量越大。字节被序列化后通过 TCP/IP 传输,需要发送的段越多,传输成本越高,最终会影响网络延迟。
返回所有列可能需要反序列化字符串或 blob 等大列,即使客户可能永远不会使用这些列。
客户端反序列化

客户端收到原始字节后,应用程序必须将数据反序列化为客户端使用的任何语言,增加了整体处理时间。管道中的数据越多,这一过程就越慢。

不可预测性

即使只有一个字段,在客户端使用 SELECT * 也会带来不可预测性。

例如,对于含一到两个字段的表,程序会执行 SELECT * 快速处理两个整数字段;但是一旦添加 XML、JSON、blob 等新字段,它们就会被其他应用程序填充和使用。代码没有变化,但速度会突然变慢,因为现在程序要处理所有额外的字段。

代码搜索

显式 SELECT 的另一个优点是,可以在代码库中搜索正在使用的列,以防要重命名或删除某个列。这使得数据库 schema 变更(DDL)更加容易。

总结

总之,SELECT * 查询涉及许多复杂的过程,最好只选择需要的字段,以避免无谓的开销。如果表中的列很少,数据类型简单,SELECT * 查询的开销也许可以忽略不计;但一般来说,在查询中选择性地检索列是一种更好的做法。

  推荐阅读:
  1. 世界上最难的 5 种编程语言!网友看后惊讶道:竟不是C/C++?
  2. 面试官:数据库的隔离是什么意思?
  3. 缓存型数据库是YYDS?
  4. 停止在你的数据库中使用UUID

  5. 世界上运行时间最长的服务器是多久?

脚本之家
脚本之家(jb51.net)每天提供最新IT类资讯、原创内容、编程开发的教程与经验分享,送书福利天天在等你!
 最新文章