避免使用 SELECT ,即使是针对单列表 тоже这样
尽量避免使用 SELECT *,即使是在单列表上。即便你不认同这一点,也请记住。到文章结尾时,我可能会让你好好想想。
2012年的一个故事这是大约十二年前(2012年到2013年左右)我遇到的一个真实故事,那时我在处理一个客户的后端系统问题。
一个稳定运行且延迟在个位数毫秒内的后端 API。有一天,用户们遇到了卡顿和不流畅的使用体验。
我们检查了提交记录,没有什么显然的问题,大部分改动看起来没啥问题。为了谨慎起见,我们撤销了所有提交(你们中有些人应该会有同感,当你开始怪罪那些不合常理的事时,你就知道情况已经很糟糕了)。
不过,这款软件还是很慢。
在查看诊断时,我们注意到API响应时间有时达到2秒,而之前原本是500毫秒左右,响应时间仅为几毫秒。
我们知道后台没有做出任何可能引起变慢的改动,但我们开始检查数据库查询。
从一个含有3个 blob 字段的表中选择所有数据返回给后端应用,这些 blob 字段中的大数据量非常大。
发现这个表只有两个整数字段,而API执行了一个SELECT *来返回并使用这两个字段。后来,管理员又添加了三个blob字段,这些字段被另一个应用程序使用和填充了。
当那些二进制大对象(blob)字段没有返回给客户端时,后端API在处理额外字段时承受了损耗,这些字段由其他应用程序填充,导致了数据库、网络和协议的序列化的额外开销。
你知道数据库是如何读取数据的吗?
在行存数据库引擎中,行是以称为“页”的单位存储的。每一页都包含一个固定的页头,并且包含多个行,每一行前面有记录头,后面跟着相应的列数据。例如在PostgreSQL中,可以参考以下示例:
当数据库检索到一页并将其放入共享缓冲池时,我们就可以访问该页中的所有行和列。因此,一个问题出现了:如果我们已经在内存中拥有了所有列,为什么这样的 SELECT * 查询会变得缓慢且消耗资源?它真的像人们所说的那样慢吗?如果是的话,为什么它会如此呢?在本文中,我们将探讨这些问题及其他内容。
和只扫描索引说再见吧。使用 SELECT * 表示数据库优化器无法选择仅索引扫描。例如,假设你需要获取成绩高于 90 分的学生的 ID,而你在 grades 列上有索引,该索引包含学生 ID 作为非键部分,这个索引非常适合这个查询语句。
然而,因为你要求获取所有字段,数据库需要访问堆数据页来获取剩余的字段,这增加了随机读取次数,导致了更多的I/O。在这种情况下,如果你没有使用SELECT *,数据库只需要扫描成绩索引并返回ID。
反序列化费用反序列化,也就是解码,是将原始字节转换成数据类型的过程。这涉及将一串字节(通常来自文件、网络通信或其他来源)转换回更结构化的数据格式,比如编程语言中的对象和变量。
当你执行一个 SELECT *
查询时,数据库需要反序列化所有列,即使这些列对于你的特定需求不是必需的。这不仅会增加计算开销,还会降低查询性能。这样,你只选择必要的列就可以减少反序列化的成本,提高查询效率。
使用 SELECT *
查询的一个重要问题是,并非所有列都存储在页面的内联部分。例如文本和二进制大对象这样的大列通常会被存储在外部表中,并且只有在请求时才会被检索(Postgres 中的 TOAST 表就是例子之一)。这些被压缩的列,当你执行包含许多文本字段、几何数据或二进制大对象的 SELECT *
查询时,会使数据库负担加重,因为需要从外部表获取值、解压缩它们,并返回给客户端。
在查询结果发送到客户端之前,必须进行序列化,以符合数据库支持的通信协议。需要序列化的数据越多,CPU的工作量就越大。序列化后的字节通过TCP/IP进行传输。需要发送的段越多,传输成本就越高,最终会影响网络延迟。
返回所有列可能需要对大的列(例如字符串或二进制大对象)进行反序列化,而客户端可能永远都不会用到。
客户端反序列化过程
当客户端接收到原始字节时,客户端应用程序必须将数据反序列化成客户端使用的格式,增加了总的处理时间。管道中的数据越多,处理过程就越慢。
不可预测即使只有一个字段,你也可能会在客户端执行 SELECT * ,瞬间获取了两个整数字段。想想这个例子,你有一个表,里面有一个或两个字段。
然而,后来管理员决定添加一个XML字段和JSON字段、blob以及其他由其他应用程序填充并使用的字段。尽管你的代码没有任何改变,它会突然变慢下来,因为它现在需要处理那些原本并不需要的额外字段。
代码Grep另一个使用显式 SELECT 语句的优点是,你可以在代码库中搜索被使用的列,如果你想重命名或删除某一列时,这会让数据库模式的 DDL 更加容易处理。
这是总结总之,一个 SELECT *
查询涉及很多复杂的过程,因此最好只选择你需要的字段以避免不必要的负担。请记住,如果你的表只有少量简单的数据列,SELECT *
查询的开销可能可以忽略。不过,通常最好只在查询中选择你需要的列。
如果这篇文章让你喜欢,可以看看我关于Backend和Database的课程,在我的简介里有链接。
共同学习,写下你的评论
评论加载中...
作者其他优质文章