在行存储的数据库引擎中,数据以称为页的单元进行存储。每个页都有一个固定的头部,并包含多个行,每个行都有一个记录头部,后面跟着各自的列。例如,在 PostgreSQL 中考虑以下示例:
当数据库获取并将页面放入共享缓冲池时,我们可以访问该页面中的所有行和列。因此,问题出现了:如果我们在内存中已经有了所有列,为什么 SELECT * 会变得如此缓慢和昂贵?它真的像人们所说的那样慢吗?如果确实如此,为什么会这样?在本文中,我们将探讨这些问题和更多内容。
拥抱索引扫描
使用 SELECT * 意味着数据库优化器无法选择索引扫描。 例如,假设您需要获得分数超过90分的学生的ID,并且您在分数列上有一个包括学生ID作为非键的索引,这个索引非常适合此查询。
然而,由于您请求了所有字段,数据库需要访问堆数据页以获取其余字段,从而增加了随机读取,导致更多的输入/输出操作。相比之下,如果您没有使用 SELECT *,数据库只需扫描分数索引并返回ID就可以了。
反序列化成本
反序列化,或解码,是将原始字节转换为数据类型的过程。这涉及将一系列字节(通常来自文件、网络通信或其他来源)转换回更结构化的数据格式,例如编程语言中的对象或变量。
当执行 SELECT * 查询时,数据库需要对所有列进行反序列化,即使您在特定用例中可能不需要这些列。这会增加计算开销并降低查询性能。通过仅选择必要的列,您可以减少反序列化成本,并提高查询的效率。
并非所有列都是内联的
SELECT * 查询的一个重要问题是,并非所有列都以内联方式存储在页面中。大型列,例如文本或大型二进制对象(blob),可能存储在外部表中,并在请求时才检索(例如Postgres的TOAST表)。这些列通常被压缩,因此当您执行带有许多文本字段、几何数据或大型二进制对象的 SELECT * 查询时,会增加数据库的负载,以从外部表中获取值,解压缩它们,并将结果返回给客户端。
网络开销
在将查询结果发送给客户端之前,它必须按照数据库支持的通信协议进行序列化。需要序列化的数据越多,CPU 的工作量就越大。在字节序列化之后,通过 TCP/IP 进行传输。需要发送的分段越多,传输的成本就越高,最终影响网络延迟。
返回所有列可能需要对大型列进行反序列化,例如字符串或二进制大对象 (BLOBs),而客户端可能永远不会使用这些列。
客户端反序列化
客户端一旦接收到原始字节,客户端应用程序必须将数据反序列化为客户端使用的编程语言,这增加了整体处理时间。数据传输的量越大,这个过程就越慢。
总结
总而言之,SELECT * 查询涉及许多复杂的过程,因此最好只选择需要的字段,以避免不必要的开销。请记住,如果您的表只有少数具有简单数据类型的列,则 SELECT * 查询的开销可能可以忽略不计。然而,通常最好在查询中明确选择要检索的列。
同学合作的 ChatGPT 课程,有需要的自取。