17611538698
webmaster@21cto.com

避免使用 SELECT *,即使在单列表上

数据库 0 59 2024-12-17 11:46:12

图片

导读:避免在代码中使用 SELECT *,即使是在单列表上。即使你不愿意,也请记住这一点。相信在本文结束时,我可能会让你深思。

图片

2012 年的故事


这是我 12 年前(大约是在 2012-2013 年),在处理客户后端应用程序时遇到的真实故事。


后端 API 稳定,且运行时间仅需几毫秒。但是突然有一天,用户发现其运行速度缓慢。


我们检查了代码提交,并没有发现任何异常,大多数更改都是良性的。以防万一,我们撤销了所有提交。


但是,该应用程序仍然很慢。


查看诊断结果后,我们注意到 API 响应时间有时需要 500 毫秒到 2 秒。而以前则需要几毫秒。


我们总结后端没有任何变化导致速度变慢,便开始查看数据库查询。


我们在具有 3 个 Blob 字段的表上执行 SELECT * 操作,该操作将被返回到后端应用程序,这些 Blob 字段包含非常大的文档。


事实证明,该表只有 2 个整数列,并且 API 也正在运行 SELECT * 来返回并使用这两个字段。但后来,管理员添加了 3 个 blob 字段,这些字段由另一个应用程序使用和添加。


虽然这些 blob 字段没有返回到客户端,但后端 API 因拉取由其他应用程序填充的额外字段而受到影响,从而导致数据库、网络和协议序列化增加开销。

数据库读取的工作原理


在行存储数据库引擎中,行以Page(页)为单位进行存储。每个Page都有一个固定的标题并包含多行,每行都有一个记录标题,后面跟着相应的列。


例如,我们来考虑 PostgreSQL 中的以下示例:

图片

当数据库获取Page并将其放入共享缓冲池时,我们可以访问该页面中的所有行和列。这时问题出现了:如果我们在内存中准备好了所有列,为什么 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 和由其他应用程序添加和使用的字段。


虽然你的程序代码根本没有改变,但它会突然地变慢,因为它现在正在取得应用程序不需要的其它额外字段内容。


使用代码 Grep


使用显式 SELECT 的另一个优点是,你可以在代码库中查找正在使用的列,如果您想要重命名或删除某个列。这使得数据库架构与DDL 更改更加地容易。


结语

概括总之,SELECT * 查询涉及许多复杂的过程,因此最好只选择自己需要的字段以避免不必要的开销。


请务必记住,如果你的表中只有少量具有简单数据类型的列,则 SELECT * 查询的开销可能可以忽略不计。但是通常最好选择在查询中检索的列。


如果你喜欢这篇文章,请点赞转发其他好友~


作者:聆听音乐的鱼

评论