为什么ORM被认为是好的,但是“select *”被认为是不好的?
ORM通常不涉及做一些select*?
如果我有一个表,MyThing,与列A,B,C,D等,那么通常会有一个对象,MyThing属性A,B,C,D。
如果这个对象被一个看起来像这样的select语句不完全实例化,那只会得到A,B而不是C,D:
从MyThingselectA,B / *不要C和D,因为我们不需要它们* /
但总是这样做也是邪恶的:
selectA,B,C,D / *获取所有列,以便我们可以完全实例化MyThing对象* /
ORM是否假设数据库访问速度如此之快,现在您不必担心,因此您可以随时获取所有列?
或者,您是否有不同的MyThing对象,每个组合可能碰巧在select语句中?
编辑:在你回答这个问题之前,请阅读Nicholas Piasecki和Bill Karwin的答案。 我猜我的问题很糟糕,因为很多人误解了这个问题,但尼古拉斯100%都明白这一点。 像他一样,我对其他答案感兴趣。
编辑#2:与这个问题有关的链接:
为什么我们需要实体对象?
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
在我有限的经验中,事情就像你所描述的那样 – 这是一个混乱的情况,通常的阴谋“它取决于”的答案适用。
一个很好的例子就是我工作的在线商店。 它有一个Brand
对象,并在网站的主页上,所有的商店出售的品牌都列在左侧。 要显示这个品牌的菜单,所有的网站需要的是整数BrandId
和stringBrandId
。 但Brand
对象包含一大堆其他属性,最显着的是一个Description
属性,可以包含关于Brand
大量文本。 没有两种方式,加载关于品牌的所有额外信息只是为了在无序列表中吐出它的名字是(1)通常由于大的文本字段而显着地变慢,(2)当它到达时非常低效记忆的使用,build立大的string,甚至没有看到它们,然后把它们扔掉。
许多ORM提供的一个选项是懒加载一个属性。 因此,我们可以将一个Brand
对象返回给我们,但是这个耗时且耗费内存的Description
字段只有在我们尝试调用其get
访问器之后才会发生。 此时,代理对象将拦截我们的呼叫,并及时从数据库中抽取描述。 这有时足够好,但已经烧了我足够多的时间,我个人不build议这样做:
- 很容易忘记这个属性是延迟加载的,通过编写一个foreach循环引入一个SELECT N + 1问题。 谁知道当LINQ参与时会发生什么?
- 如果实时数据库调用由于传输中断或networking传出而失败,该怎么办? 我几乎可以保证,任何代码,如
string desc = brand.Description
做无害的事情没有期待这个简单的调用抛出一个DataAccessException
。 现在你刚刚以一种令人讨厌和意外的方式坠毁。 (是的,我看着我的应用程序因为这个而陷入了沉重的困境。
所以我最终做的是,在需要性能的场景或容易发生数据库死锁的情况下,我创build了一个独立的接口,网站或任何其他程序可以调用访问特定数据块的查询计划仔细审查。 架构最终看起来像这样(原谅ASCII艺术):
网站:控制器类 | | --------------------------------- + | | App Server:IDocumentService IOrderService,IInventoryService等 (数组,数据集)(定期OO对象,如品牌) | | | | | | 数据层:(原始的ADO.NET返回数组,(像NHibernate的“全奶油”ORM) 数据集,简单的类)
我曾经认为这是作弊,颠覆了OO对象模型。 但从实际意义上讲,只要你做这个显示数据的捷径,我觉得没关系。 更新/插入,还有什么你仍然通过充分水合,ORM填充域模型,这是发生的事情要less得多(在我的大部分情况下)比显示特定的数据子集。 像NHibernate这样的ORM可以让你做预测,但是到那时候我只是没有看到ORM的要点。 这可能是一个存储过程,编写ADO.NET需要两秒钟的时间。
这只是我的两分钱。 我期待着读一些其他的回应。
人们使用ORM来提高开发效率,而不是进行运行时性能优化。 这取决于项目是否最重要,以最大限度地提高开发效率或运行效率。
在实践中,可以使用ORM来获得最大的生产力,然后对应用程序进行分析,以在完成后识别瓶颈。 只有在得到最大回报的地方,才将ORM代码replace为自定义SQL查询。
如果您通常需要表中的所有列,则SELECT *
并不差。 我们不能一概而论通配符总是好的或总是不好的。
编辑:回复:doofledorfer的评论…就个人而言,我总是明确命名列中查询; 我从来没有在生产代码中使用通配符(尽pipe我在做特别查询时使用它)。 原来的问题是关于ORM的 – 事实上,ORM框架统一发出一个SELECT *
并不less见,以填充相应对象模型中的所有字段。
执行一个SELECT *
查询可能不一定表明你需要所有这些列,这并不一定意味着你对代码的疏忽。 ORM框架可能会生成SQL查询,以确保所有的字段在需要时都可用。
Linq to Sql或任何IQueryable的实现都使用最终使您控制所选数据的语法。 查询的定义也是其结果集的定义。
通过从ORM中删除数据形状职责,这个整齐地避免了select *
问题。
例如,要select所有列:
from c in data.Customers select c
要select一个子集:
from c in data.Customers select new { c.FirstName, c.LastName, c.Email }
要select组合:
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 }
有两个独立的问题需要考虑。
首先,对表和对象使用ORM有相当不同的“形状”,这是很常见的,这是许多ORM工具支持相当复杂的映射的一个原因。
一个很好的例子是,当一个表部分是非规范化的,列中包含冗余信息(通常这是为了改善查询或报告性能)。 发生这种情况时,ORM更有效地请求所需的列,而不是将所有额外的列都带回并忽略。
“Select *”为什么是邪恶的问题是分开的,答案分为两部分。
当执行“select *”时,数据库服务器没有义务以任何特定的顺序返回列,实际上可以每次以不同的顺序合理地返回列,尽pipe几乎没有数据库这样做。
问题是,当一个典型的开发人员注意到所返回的列似乎是一致的时候,假设列将始终按照这个顺序排列,然后你的代码做出了不合理的假设,只是等待失败。 更糟的是,这种失败可能不是致命的,但可能仅仅涉及使用出生年代替账户余额 。
“Select *”的另一个问题是围绕着表的所有权 – 在许多大公司中,DBA控制模式,并按照主要系统的要求进行更改。 如果您的工具正在执行“select *”,那么您只能得到当前列 – 如果DBA删除了您需要的冗余列,则不会出现错误,并且您的代码可能会错误地导致各种损坏。 通过明确地请求所需的字段,确保您的系统将会中断而不是处理错误的信息。
我不知道为什么你会想要一个部分水分的物体。 给定一类客户的名称,地址,ID属性。 我希望他们都创build一个完全填充的Customer对象。
挂在客户名单上的列表可以在大多数ORM访问时被延迟加载。 NHibernate无论如何可以让你做投影到其他对象。 因此,如果您已经在其中显示了ID和名称的简单客户列表,则可以创build一个CustomerListDisplaytypes的对象,并将您的HQL查询投影到该对象集中,并仅从数据库中获取所需的列。
朋友不要让朋友提前优化。 充分保湿你的对象,懒惰加载它的关联。 然后分析您的应用程序寻找问题,并优化问题领域。
即使ORM需要通过使用延迟加载等来避免SELECT *有效。
是的,如果你没有使用所有的数据,SELECT *通常是一个坏主意。
那么,您是否有不同种类的MyThing对象,每个组合一个? – 科里Trager(11月15日在0:37)
不,我有只读摘要对象(只包含重要信息),如查找和大量集合,并将其转换为完全水合的对象。 – 凯德Roux(11月15日在1:22)
你所描述的情况是ORM如何不是万能的一个很好的例子。 数据库主要通过SQL提供对其数据的基于需求的灵活访问。 作为开发人员,我可以根据需要轻松简单地获取所有数据(SELECT *)或某些数据(SELECT COL1,COL2)。 任何其他开发人员接pipe这个项目都会很容易理解我的这种机制。
为了从ORM获得相同的灵活性,需要做更多的工作(无论是由你还是ORM开发人员),只是为了让你回到引擎盖下的地方,你要么获得全部或部分列从数据库根据需要(见上面的优秀答案,以了解一些问题)。 而所有这些额外的东西,只是更多的东西,可以失败,使ORM系统本质上不如直接SQL调用的可靠性。
这并不是说你不应该使用ORM(我的标准免责声明是所有的deviseselect都有成本和收益,而其中一个或者另一个的select取决于) – 如果它对你有用,就自己敲一下。 我会说,我真的不明白ORM的普及程度,因为它似乎为用户创造了额外的无趣工作量。 我会坚持使用SELECT *时(等待它)我需要从表中获取每列。
一般来说,ORM不依赖于SELECT *,而是依靠更好的方法来查找像定义的数据映射文件(Hibernate,Hibernate的变体和Apache iBATIS这样做)的列。 通过查询数据库模式可以设置一些更为自动化的function,以获取表的列及其数据types的列表。 如何填充数据是特定于您正在使用的特定ORM,并且应该在那里详细logging。
select根本不使用的数据永远不是一个好主意,因为它可能会创build一个不必要的代码依赖性,而这个代码依赖性可能会在以后维护。 为了处理class级内部的数据,事情有点复杂。
一个简短的规则是总是获取默认情况下类所存储的所有数据。 在大多数情况下,less量的开销不会产生巨大的影响,因此您的主要目标是减less维护开销。 后来,当你对代码进行性能分析时,有理由相信它可以从调整行为中受益,那么现在是时候这样做了。
如果我看到一个ORM使SELECT *语句,无论是明显的或在其覆盖,然后我会寻找其他地方,以满足我的数据库集成需求。
SELECT *不错。 你有没有问过谁认为这是坏的“为什么?”。
SELECT *是一个强烈的暗示,您没有对应用程序及其模块的范围进行devise控制。 清理别人的工作的主要困难之一是,在那里没有任何目的的东西,但没有指出什么是需要和使用,什么是不是。
应用程序中的每一个数据和代码都应该出于某种目的,而且目的应该被指定,或者容易被检测到。
我们都知道并且鄙视的是,程序员们并不担心为什么事情会起作用,他们只是想尝试一些东西,直到预期的事情发生,并为下一个人结束。 SELECT *是一个很好的方法来做到这一点。
如果你觉得需要封装一个对象中的所有东西,但是需要一些包含在表中的东西,那么定义你自己的类。 直接写sql(在ORM中或者没有ORM的情况下 – 大多数情况下允许直接的sql来规避限制),并用结果填充你的对象。
但是,我只是在大多数情况下使用表的ORM表示,除非分析告诉我不要。
如果您使用查询cachingselect*可以很好。 如果每次打表时都select不同的列,则可能只是获取所有这些查询的cachingselect *。
我认为你混淆了ORM的目的。 ORM旨在将一个域模型或类似的表映射到数据库中的某个表或某个数据存储约定中。 这并不意味着使您的应用程序在计算上更有效率,甚至是预期的。