为什么ORM被认为是好的但“选择*”被认为是坏的?

时间:2008-11-15 00:21:04

标签: orm

ORM通常不会涉及像select *?

这样的事情

如果我有一个表MyThing,包括A,B,C,D等列,那么通常会有一个对象MyThing,其属性为A,B,C,D。

如果该对象被一个看起来像这样的select语句不完全实例化,只获取A,B,而不是C,D,那将是邪恶的:

选择A,B来自MyThing / *不要得到C和D,因为我们不需要它们* /

但总是这样做也是邪恶的:

选择A,B,C,D / *获取所有列,以便我们可以完全实例化MyThing对象* /

ORM是否假设数据库访问速度如此之快,以至于您不必担心它,因此您始终可以获取所有列?

或者,您是否有不同的MyThing对象,每个可能碰巧在select语句中的列组合对应一个?

编辑:在回答这个问题之前,请先阅读Nicholas Piasecki和Bill Karwin的回答。我想我的问题很糟糕,因为许多人误解了它,但尼古拉斯100%理解它。和他一样,我对其他答案感兴趣。


编辑#2:与此问题相关的链接:

Why do we need entity objects?

http://blogs.tedneward.com/2006/06/26/The+Vietnam+Of+Computer+Science.aspx,尤其是“部分对象问题和加载时间悖论”部分

http://groups.google.com/group/comp.object/browse_thread/thread/853fca22ded31c00/99f41d57f195f48b

http://www.martinfowler.com/bliki/AnemicDomainModel.html

http://database-programmer.blogspot.com/2008/06/why-i-do-not-use-orm.html

12 个答案:

答案 0 :(得分:65)

在我有限的经历中,事情就像你描述的那样 - 这是一个混乱的情况,而且通常的“应急”答案适用。

一个很好的例子就是我工作的在线商店。它有一个Brand对象,在网站的主页上,商店销售的所有品牌都列在左侧。要显示此品牌菜单,所有站点需求都是整数BrandId和字符串BrandName。但是Brand对象包含一大堆其他属性,最明显的是Description属性,可以包含大量关于Brand的文本。关于它的两种方式,加载关于品牌的所有额外信息只是为了在无序列表中吐出它的名称是(1)可测量且显着慢,通常是因为大文本字段和(2)当它到来时非常低效记忆用法,建立大字符串,甚至在扔掉之前都不看它们。

许多ORM提供的一个选项是延迟加载属性。所以我们可以将一个Brand对象返回给我们,但是在我们尝试调用其Description访问器之前,这个耗时且浪费内存的get字段是不可能的。此时,代理对象将拦截我们的调用并及时从数据库中删除描述。这有时候足够好,但已经烧了我足够的时间,我个人不推荐它:

  • 很容易忘记该属性是延迟加载的,仅通过编写foreach循环引入SELECT N + 1问题。谁知道当LINQ参与时会发生什么。
  • 如果即时数据库调用失败,因为传输被弄乱或网络中断了怎么办?我几乎可以保证任何像string desc = brand.Description一样无害的代码都不会期望简单地调用DataAccessException。现在你刚刚以令人讨厌和意想不到的方式坠毁。 (是的,我看到我的应用程序因此而努力下去。学到了很多东西!)

所以我最终做的是在需要性能或容易出现数据库死锁的场景中,我创建了一个单独的界面,网站或任何其他程序可以调用该界面来访问特定的数据块仔细检查了他们的查询计划。该架构最终看起来像这样(原谅ASCII艺术):

Web Site:         Controller Classes
                     |
                     |---------------------------------+
                     |                                 |
App Server:       IDocumentService               IOrderService, IInventoryService, etc
                  (Arrays, DataSets)             (Regular OO objects, like Brand)
                     |                                 |
                     |                                 |
                     |                                 |
Data Layer:       (Raw ADO.NET returning arrays, ("Full cream" ORM like NHibernate)
                   DataSets, simple classes)

我曾经认为这是作弊,颠覆了OO对象模型。但从实际意义上讲,只要你使用这个显示数据的快捷方式,我认为它没问题。更新/插入以及您仍然通过完全水合,填充ORM的域模型,并且这种情况发生的频率(在我的大多数情况下)比显示特定的数据子集要少得多。像NHibernate这样的ORM可以让你做预测,但到那时我只是看不到ORM的意义。无论如何,这可能是一个存储过程,编写ADO.NET需要两秒钟。

这只是我的两分钱。我期待着阅读其他一些回复。

答案 1 :(得分:21)

人们使用ORM来提高开发效率,而不是运行时性能优化。这取决于项目是否最重要的是最大化开发效率或运行时效率。

在实践中,可以使用ORM获得最大的生产力,然后在完成后分析应用程序以识别瓶颈。将ORM代码替换为自定义SQL查询,只有在您获得最大收益的地方。

如果您通常需要表中的所有列,那么

SELECT *也不错。我们无法概括通配符总是好的或总是坏的。

编辑:Re:doofledorfer的评论...就个人而言,我总是明确地在查询中命名列;我从不在生产代码中使用通配符(尽管我在进行即席查询时使用它)。最初的问题是关于ORM - 事实上,ORM框架统一发布SELECT *并填充相应对象模型中的所有字段并不罕见。

执行SELECT *查询可能不一定表明您需要所有这些列,并不一定意味着您忽略了您的代码。可能是ORM框架正在生成SQL查询以确保所有字段都可用以防需要它们。

答案 2 :(得分:6)

Linq to SqlIQueryable的任何实现都使用最终使您控制所选数据的语法。查询的定义也是其结果集的定义。

通过从ORM中删除数据形状职责,这可以巧妙地避免select *问题。

例如,要选择所有列:

from c in data.Customers
select c

选择子集:

from c in data.Customers
select new
{
  c.FirstName,
  c.LastName,
  c.Email
}

选择组合:

from c in data.Customers
join o in data.Orders on c.CustomerId equals o.CustomerId
select new
{
  Name = c.FirstName + " " + c.LastName,
  Email = c.Email,
  Date = o.DateSubmitted
}

答案 3 :(得分:4)

我不确定为什么你会想要一个部分水合的物体。给定一类具有Name,Address,Id属性的Customer。我希望他们都能创建一个完全填充的Customer对象。

当通过大多数ORM访问时,可以延迟加载名为Orders的客户列表。无论如何NHibernate允许你对其他对象进行投影。因此,如果您说明了显示ID和名称的客户列表,则可以创建CustomerListDisplay类型的对象,并将HQL查询投影到该对象集中,并仅从数据库中获取所需的列。

朋友不要让朋友过早优化。完全保湿你的物体,懒惰加载它的关联。然后分析您的应用程序以查找问题并优化问题区域。

答案 4 :(得分:4)

需要考虑两个不同的问题。

首先,当桌面使用ORM并且对象具有完全不同的“形状”时,这是很常见的,这是许多ORM工具支持相当复杂的映射的一个原因。

一个很好的例子是当表被部分非规范化时,列包含冗余信息(通常,这样做是为了提高查询或报告性能)。发生这种情况时,ORM只需要请求所需的列就更有效率,而不是让所有额外的列被带回并被忽略。

为什么“选择*”是邪恶的问题是分开的,答案分为两半。

执行“select *”时,数据库服务器没有义务以任何特定的顺序返回列,事实上每次都可以合理地以不同的顺序返回列,尽管几乎没有数据库执行此操作。

问题是,当一个典型的开发人员观察到返回的列似乎是一致的顺序时,假设列总是按顺序排列,然后你就有了代码制作毫无根据的假设,等待失败。更糟糕的是,这种失败可能并不致命,但可能仅仅涉及使用出生年份代替账户余额

“Select *”的另一个问题围绕表所有权 - 在许多大公司中,DBA控制架构,并根据主要系统的要求进行更改。如果您的工具正在执行“select *”,那么您只获得当前列 - 如果DBA删除了您需要的冗余列,则不会出现错误,并且您的代码可能会在前方犯错,从而导致各种损坏。通过明确请求您需要的字段,您可以确保系统中断而不是处理错误的信息。

答案 5 :(得分:3)

甚至ORM也需要通过使用延迟加载等来避免SELECT *生效。

是的,如果您没有消耗所有数据,SELECT *通常是一个坏主意。

那么,你有不同类型的MyThing对象,每个列组合一个吗? - Corey Trager(11月15日0:37)

不,我有只读摘要对象(仅包含重要信息),用于查找和大量收集等内容,并根据需要将这些对象转换为完全水合的对象。 - Cade Roux(11月15日1:22)

答案 6 :(得分:2)

您描述的案例是ORM不是灵丹妙药的一个很好的例子。数据库主要通过SQL提供对数据的灵活,基于需求的访问。作为开发人员,我可以根据需要轻松简单地获取所有数据(SELECT *)或某些数据(SELECT COL1,COL2)。我接触该项目的任何其他开发人员都很容易理解我这样做的机制。

为了从ORM获得相同的灵活性,还需要做更多的工作(由您或ORM开发人员完成),只是为了让您回到引擎盖下的地方,或者您要么全部或者一些根据需要从数据库中的列(请参阅上面的优秀答案,以了解一些问题)。所有这些额外的东西只是更多可能失败的东西,使得ORM系统本质上不如直接SQL调用可靠。

这并不是说您不应该使用ORM(我的标准免责声明是所有设计选择都有成本和收益,而选择其中一个或者仅仅取决于它) - 如果它适合您,请自行解决。我会说,我真的不明白ORM的受欢迎程度,因为它似乎为其用户创造了额外的无趣工作量。我会坚持使用SELECT * when(等待它)我需要从表中获取每一列。

答案 7 :(得分:1)

ORM通常不依赖于SELECT *,而是依靠更好的方法来查找定义的数据映射文件(Hibernate,Hibernate的变体和Apache iBATIS这样做)这样的列。通过查询数据库模式以获取表的列及其数据类型列表,可以设置更自动的东西。如何填充数据是特定于您正在使用的特定ORM,并且应该在那里进行详细记录。

选择根本不使用的数据绝不是一个好主意,因为它可能会创建一个不必要的代码依赖项,以后可能会令人讨厌。对于处理类内部的数据,事情有点复杂。

一个简短的规则是始终获取默认情况下类存储的所有数据。在大多数情况下,少量开销不会产生巨大差异,因此您的主要目标是减少维护开销。之后,当您对代码进行性能分析,并有理由相信它可能会从调整行为中受益时,就是时候了。

如果我看到ORM发出SELECT *语句,无论是明显的还是在其封面下,那么我会寻找其他地方来满足我的数据库集成需求。

答案 8 :(得分:0)

SELECT *也不错。你有没有问过谁认为它不好“为什么?”。

答案 9 :(得分:0)

SELECT *强烈表明您没有设计控制应用程序及其模块的范围。清理别人工作的主要困难之一是当那里的东西没有任何目的,但没有迹象表明需要和使用什么,什么不是。

应用程序中的每一段数据和代码都应该用于某个目的,并且应该指定或轻松检测目的。

我们都知道,并且鄙视程序员,他们并不太担心为什么会有效,他们只是想尝试一些东西,直到预期的事情发生并为下一个人关闭它。 SELECT *是一个非常好的方法。

答案 10 :(得分:0)

如果您觉得需要封装对象中的所有内容,但需要使用表中包含的一小部分内容 - 定义您自己的类。编写直接sql(在ORM内部或不使用ORM - 最多允许直接sql来规避限制)并使用结果填充对象。

但是,我只是在大多数情况下使用表的ORM表示,除非分析告诉我不要。

答案 11 :(得分:0)

如果您正在使用查询缓存,则select *可以很好。如果您在每次点击表时选择不同的列类别,则可能只是为所有这些查询获取缓存的select *。

我认为你混淆了ORM的目的。 ORM旨在将域模型或类似映射到数据库中的表或某些数据存储约定。它并不意味着使您的应用程序在计算上更有效率,甚至无法实现。