与现代 ORM 相比,MS SQL 上的 SELECT * 的性能/代码可维护性问题在今天仍然相关吗?

Posted

技术标签:

【中文标题】与现代 ORM 相比,MS SQL 上的 SELECT * 的性能/代码可维护性问题在今天仍然相关吗?【英文标题】:are performance/code-maintainability concerns surrounding SELECT * on MS SQL still relevant today, with modern ORMs? 【发布时间】:2017-01-10 18:45:31 【问题描述】:

总结:出于性能和可维护性方面的考虑,我看到了很多反对在 MS SQL 中使用 SELECT * 的建议。但是,其中许多帖子都非常古老- 5 到 10 年! 似乎,从这些帖子中的许多帖子来看,性能问题实际上可能非常小,即使在他们那个时代也是如此,而且关于可维护性问题(“哦,不,如果有人更改列怎么办,并且您通过索引数组来获取数据!您的 SELECT * 会给您带来麻烦!”),现代编码实践和 ORM(例如 Dapper)似乎 - 至少在我的经验中 - 消除这种担心。

等等:SELECT * 是否存在今天仍然相关的问题?


更大的背景:我已经开始在一个有很多旧 MS 代码(ASP 脚本等)的地方工作,并且我一直在帮助对其进行现代化改造,但是:我的大部分 SQL 经验实际上来自 mysqlphp 框架和 ORM——这是我第一次使用 MS SQL——我知道两者之间存在细微的差异。另外:我的同事比我年长一些,并且有一些担忧——对我来说——似乎“年长”。 (“可空字段很慢!避免使用它们!”)但同样:在这个特定领域,他们肯定比我更有经验。

出于这个原因,我还想问一下:使用现代 ORM 的 SELECT * 在今天是否安全和理智,是否有最近的在线资源表明这一点?

谢谢! :)

【问题讨论】:

几乎没有任何 ORM 明确定义列名吗?如果您使用 ORM 来访问数据,那会不会让 SELECT * 有点争议? 我认为这不是性能问题,而是如果您的数据库架构发生变化,您的查询输出可能会发生变化。您编写了一个查询来提取某些数据,为什么要冒着在您不知情的情况下更改的风险? ORM 执行 SQL 语句。同样的问题也适用。如果您想要一列,则不要加载它。如果在只需要几个字段时加载映射对象,就会损害性能,就像使用 * 一样。这就是为什么即使使用 ORM 的标准建议是只加载您需要的内容。 @Ben 你告诉我。一旦你调用了execute方法,你会得到什么?所有列还是仅您需要的列?如果你得到 100 列而不是 2 列,这些值是从哪里来的?您通过网络传输了多少数据?您必须从磁盘而不是从缓存的索引页加载多少数据页?性能差异很小。这是普通的不良数据访问。 @Ben 你真的应该花时间去了解发生了什么。加载所有内容的界面实际上比正常工作的界面更脏,也更昂贵。您必须购买更多的 Web 服务器和更大的数据库服务器来加载无用的数据。不好,不可扩展。 【参考方案1】:

我不会在这个答案中涉及可维护性,只涉及性能部分。

在这种情况下的性能与 ORM 几乎没有关系。

对于服务器来说,它正在运行的查询是如何生成的,不管是手工编写的还是由 ORM 生成的。

选择不需要的列仍然是个坏主意。

从性能的角度来看,查询是否看起来并不重要:

SELECT * FROM Table

或所有列都明确列出,例如:

SELECT Col1, Col2, Col3 FROM Table

如果您只需要Col1,请确保您只选择Col1。无论是通过手动编写查询还是通过微调您的 ORM 来实现,都没有关系。


为什么选择不必要的列是个坏主意:

要从磁盘读取的额外字节

通过网络传输的额外字节

要在客户端解析的额外字节

但是,最重要的原因是优化器可能无法生成好的计划。例如,如果有一个包含所有请求列的覆盖索引,服务器通常只会读取这个索引,但是如果您请求更多列,它会进行额外的查找或使用其他索引,或者只是扫描整个表。最终的影响可以从可忽略不计到几秒与几小时的运行时间不等。数据库越大越复杂,您就越有可能看到明显的差异。

Use the index, Luke 网站上有一篇关于此主题的详细文章Myth: Select * is bad。

现在我们已经就为什么选择 一切都对性能不利,您可能会问为什么它被列为 神话?这是因为很多人认为明星是坏东西。 此外,他们认为他们没有犯下这种罪行,因为他们的 ORM 无论如何都会按名称列出所有列。其实犯罪就是选择 所有列都没有考虑它——而且大多数 ORM 很容易提交 代表他们的用户犯下这种罪行。


我会在这里为你的 cmets 添加答案。

我不知道如何处理一个没有给我选择哪些字段的选项的 ORM。我个人会尽量不使用它。一般来说,ORM 增加了一层抽象,leaks 很糟糕。 https://en.wikipedia.org/wiki/Leaky_abstraction

这意味着你仍然需要知道如何编写 SQL 代码以及 DBMS 如何运行这段代码,还需要知道 ORM 是如何工作并生成这段代码的。如果您选择不知道 ORM 背后发生了什么,那么当您的系统变得微不足道时,您将遇到无法解释的性能问题。

您说在您之前的工作中,您将 ORM 用于大型系统而没有问题。它对你有用。好的。不过,我有一种感觉,您的数据库并不是很大(您有数十亿行吗?)并且系统的性质允许将性能问题隐藏在缓存后面(这并不总是可能的)。系统可能永远不会超过硬件容量。如果您的数据适合缓存,通常无论如何它都会相当快。只有当你跨过某个门槛时,它才开始变得重要。之后突然一切都变慢了,很难修复。

业务/项目经理通常会忽略可能永远不会发生的未来问题。企业总是有更紧迫的紧迫问题需要处理。如果在性能成为问题时业务/系统增长足够大,它要么已经积累了足够的资源来重构整个系统,要么继续以越来越低的效率工作,或者如果系统恰好对业务非常关键,那么失败并给另一家公司一个超越它的机会。

回答您的问题“是否在性能受到很大关注的应用程序中使用 ORM”。当然你可以使用 ORM。但是,您可能会发现它比不使用它更困难。考虑到 ORM 和性能,您必须手动检查 ORM 生成的 SQL 代码,并确保从性能的角度来看它是一个好的代码。因此,您仍然需要非常了解您使用的 SQL 和特定 DBMS,并且您需要非常了解您的 ORM 以确保它生成您想要的代码。为什么不直接写你想要的代码呢?

您可能认为 ORM 与原始 SQL 的这种情况有点类似于高度优化的 C++ 编译器与手动在汇编器中编写代码。好吧,事实并非如此。在大多数情况下,现代 C++ 编译器确实会生成比在汇编器中手动编写的代码更好的代码。但是,编译器非常了解处理器,优化任务的性质比数据库中的要简单得多。 ORM 不知道你的数据量,它对你的数据分布一无所知。

top-n-per-group 的简单经典示例可以通过两种方式完成,最好的方法取决于只有开发人员知道的数据分布。如果性能很重要,即使您手动编写 SQL 代码,您也必须知道 DBMS 是如何工作的并解释此 SQL 代码,并以 DBMS 以最佳方式访问数据的方式布置您的代码。 SQL 本身是一个高级抽象,可能需要微调才能获得最佳性能(例如,SQL Server 中有几十个查询提示)。 DBMS 有一些统计数据,它的优化器会尝试使用它,但这通常是不够的。

现在在此之上添加另一层 ORM 抽象。

说了这么多,“性能”是一个模糊的术语。所有这些担忧在达到一定阈值后变得重要。由于现代硬件已经相当不错,这个门槛被推得相当远,以至于很多项目都忽略了所有这些问题。

示例。对具有数百万行的表的最佳查询会在 10 毫秒内返回。非最佳查询会在 1 秒内返回。慢了 100 倍。最终用户会注意到吗?也许,但可能并不重要。将表增加到十亿行,或者一个用户拥有 1000 个并发用户。 1 秒对 100 秒。最终用户肯定会注意到,即使比率(慢 100 倍)是相同的。实际上,该比率会随着数据的增长而增加,因为各种缓存会变得越来越没用。

【讨论】:

同意。我还会添加“额外的 CPU”,尤其是当数据被加密或压缩时 同意。大多数应用程序开发人员真的不了解可扩展性能,因为在应用程序层你可以添加更多的盒子(除非你有一个非常大的操作。)我很欣赏 OP 想要更好地理解的愿望,因为它不需要那么大的商店陷入数据问题。我建议好奇的人选择一本信誉良好的性能调优书,以便为构建真正可扩展的系统打下更好的基础。 我喜欢这个答案;这是迄今为止最好的——在我看来。我想知道 - 虽然它可能有点切题 - 如果您对如何处理 ORM 有任何建议 - 在大多数情况下 - 甚至没有给您选择多少字段的选项,例如 Symfony、Eloquent 等. (Dapper 是我使用过的第一个几乎一直都给我这个选项的人,这个问题甚至出现在我身上的原因。)你看到没有给这个选项的 ORM 是有缺陷的吗?避免? 在得到 swe 的回答后,他们提到 - 作为个人意见 - 他们不会在性能是一个大问题的应用程序中使用 ORM。 @Vladimir Baranov 你也会有这种感觉吗? 还请记住,如果您使用 SELECT * 并且存在连接,则根据定义,您将返回不需要的数据,因为连接字段中的数据将重复。【参考方案2】:

明确选择列名通常是一个更好的主意。如果一个表接收到一个额外的列,它将通过 select * 调用加载,此时不需要额外的列。

这可能有几个含义:

更多网络流量

更多 I/O(必须从磁盘读取更多数据)

可能更多的 I/O(不能使用覆盖索引 - 执行表扫描以获取数据)

可能需要更多 CPU(不能使用覆盖索引,因此需要对数据进行排序)

例外Select * is OK 的唯一位置是在 Exists 或 Not Exists 谓词子句之后的子查询中,如:

Select colA, colB
From table1 t1
Where Exists (Select * From Table2  Where column = t1.colA)

More Details -1

More Details -2

More Details -3

【讨论】:

【参考方案3】:

从 SQL-Server-Performance-Point-of-view,你永远不应该使用select *,因为这意味着 sqlserver 从磁盘或内存中读取完整的行。即使您需要所有字段,我也建议不要执行select *,因为您不知道是谁将任何数据附加到您的应用程序不需要的表中。有关详细信息,请参阅@sandip-patel 的答案

从 DBA 的角度来看:如果您准确给出您需要的那些列名,则 dbadmin 可以更好地分析和优化他的数据库。

从具有更改列名的 ORM-Point-Of-View 我建议不要使用 select *。你想知道,如果表格发生变化。如果基础表发生更改,如果您没有收到错误,您希望如何保证您的应用程序运行并给出正确的结果??

个人观点:在需要表现良好的应用程序中,我真的不使用 ORM...

【讨论】:

“个人意见:在需要表现良好的应用程序中,我真的不使用 ORM...” (我想澄清一下,我们当然要确保我们的 ORM 调用尽可能高效——我们没有愚蠢地加入等等——但除此之外,我们相信 ORM 的性能,并且从不质疑它是如何编写查询的(我很确定它总是涉及 SELECT *s)) 我认为即使在较大的应用程序中(我通常编写小型客户和特定任务的应用程序),您也必须使用某种 ORM,因为没有您没有/更难的分层分层和其他需要考虑的事情关于。但是如果你有专门的应用程序处理大量数据来解析/合并/工作和显示,你最好不要。 (如前所述:我的个人意见......) 回到你的问题:在数据库方面,过去几年数据的存储、读取或缓存方式没有任何变化,所以 5 年前给出的所有建议仍然是最新的和最新的。【参考方案4】:

可维护点。

如果您执行“从表中选择 *”

然后我更改表格并添加一列。

您的旧代码可能会崩溃,因为它现在有一个额外的列。

这为将来的修订创造了一场噩梦,因为您必须确定选择 * 的所有位置。

速度差异是如此之小,我不会担心它。使用 Varchar 和 Char 存在速度差异,Char 更快。但是速度差异是如此之小,以至于几乎不值得谈论。

Select * 的最大问题是表结构的更改(添加)。

可维护性的噩梦。初级程序员的标志,以及糟糕的项目代码。话虽如此,我仍然使用 select * 但打算在使用我的代码投入生产之前将其删除。

【讨论】:

表更改不会影响我以这种方式使用的任何 ORM(尽管可能有更敏感的 ORM)【参考方案5】:

这个问题已经有一段时间了,似乎没有人能找到,Ben 正在寻找什么......

我认为是这样,因为答案是“视情况而定”。

只有 NOT IS THE ONE 对此的答案。

例子

正如我之前指出的,如果数据库不是您的,并且可能经常更改,您无法保证性能,因为使用 select * 每行的数据量可能会爆炸 如果您使用 ITS OWN 数据库编写应用程序,没有人会更改您的数据库(希望如此)并且您需要您的列,那么 select * 有什么问题 如果您构建某种延迟加载,“主要属性”会立即加载,而其他属性会稍后加载(同一实体),则不能使用 select *,因为您得到了所有内容 如果您使用 select *,其他开发人员每次都会考虑“他是否考虑过 select *”,因为他们会尝试优化。所以你应该添加足够多的 cmets... 如果您构建 3-Tier-Application 在中间层构建大型缓存并且性能是由缓存完成的主题,您可以使用 select * 扩展 3Tier:如果您有很多并发用户和/或非常大的数据,则应该考虑每个字节,因为您必须在浪费每个字节的情况下扩展中间层(正如 cmets 中有人指出的那样之前) 如果您为 3 个用户和数千条记录构建一个小应用程序,预算可能没有时间优化速度/db-layout/something 与您的 dba 交谈...他会建议您哪些语句必须更改/优化/删除/...

我可以继续。只是没有一个答案。这取决于很多因素。

【讨论】:

其中一些听起来像是对 ORM 的担忧。例如:我知道 Doctrine 在连接的数据上使用延迟加载(除非你告诉它立即连接),而且它在幕后构建 SQL,几乎可以肯定是使用 SELECT * (或等效的),虽然我不能说肯定。但是,我的问题始于 Dapper,它不提供延迟加载(至少不是开箱即用的),并且可以让您控制 SQL 查询......这就是说您可能就在“这取决于”。这不是最激动人心的答案,但我认为这是正确的答案。感谢您的所有 cmets! 另外:我对 Symfony/Doctrine 的体验是“如果 ORM 中存在性能低下的问题,与缓存的收益相比,它们是微不足道的问题”,您在这里也谈到了这一点。但同样,将 Dapper 添加到小型 C# 项目中的环境不一样。再说一遍:这取决于。

以上是关于与现代 ORM 相比,MS SQL 上的 SELECT * 的性能/代码可维护性问题在今天仍然相关吗?的主要内容,如果未能解决你的问题,请参考以下文章

MS SQL 交叉连接性能评估

与没有函数包装器的查询相比,SQL 函数非常慢

SQL Server "<>" 运算符与具有几百万行的表上的 "=" 相比非常慢

MS Access - SQL LEFT JOIN 多个条件

JDBC中的PreparedStatement相比Statement的好处都有哪些?

如果所有 SQL 都在做 SELECT,那么使用视图与 SPROC 是不是有优势