“从没有做过代码就无法使SQL Server为您做的更好”-这是设计不良的秘诀吗?


204

我在很多地方都听到过这个想法。或多或少地认识到,一旦尝试仅使用SQL解决问题就超过了一定程度的复杂性,那么您确实应该在代码中进行处理。

这个想法背后的逻辑是,在大多数情况下,数据库引擎在寻找完成任务的最有效方式方面将比在代码中做得更好。尤其是涉及将结果取决于对数据执行的操作之类的事情时。可以说,对于现代引擎而言,有效地JIT'ing +缓存查询的编译版本在表面上是有意义的。

问题是,以这种方式利用数据库引擎是否本质上是不良的设计实践(以及为什么)。当所有逻辑都存在于数据库中并且您只是通过ORM命中它时,这些界限会进一步模糊。


60
这是必须深思熟虑的一句话。每当有人发现另一位工程师在执行“从表中选择*”然后梳理结果集而不是使用where子句和指定列的方法时,它就会被淘汰。但是,如果走得太远,最终会陷入混乱。
迈克尔·科恩

154
以“从不”或“总是”开头的短语几乎总是导致不良设计的秘诀。
vsz 2012年

34
虽然当然可以尝试在SQL中做太多事情,但是老实说,在30多年的开发和咨询中,我从未见过真正的严重案例(少数)。另一方面,我确实看到过数百个严重的开发人员案例,他们试图在“代码”中做很多事情,而这本来应该在SQL中完成的。我仍然看到他们。经常...
RBarryYoung 2012年

2
@MrEdmundo转到meta。
ta.speot。是2012年

4
这个问题是二合一的-我认为应该分开。1)在SQL中应该做多少?2)在DBMS中应该做多少?存储过程位于中间。我已经看到整个应用程序都编码为存储过程。
reinierpost 2012年

Answers:


320

用外行的话来说:

这些是SQL要做的事情,无论是否相信,我都在代码中看到过:

  • 联接 -以代码方式需要复杂的数组操作
  • 过滤数据(在哪里)-以代码方式需要大量插入和删除列表中的项目
  • 选择列 -以代码方式需要大量列表或数组操作
  • 聚合函数 -逐个代码地需要数组来保存值和复杂的切换条件
  • 外键完整性 -以代码方式,插入前需要查询,并假设没有人会在应用程序外部使用数据
  • 主键完整性 -以代码方式,插入前需要查询,并假设没有人会在应用程序外部使用数据

代替依赖SQL或RDBMS 执行这些操作会导致编写大量无附加值的代码,这意味着需要调试和维护更多的代码。并且危险地假设只能通过应用程序访问数据库。


88
+10000000000,指出它危险地假设一切都只会通过应用程序发生。
HLGEM 2012年

11
@skynorth导致错误的数据库设计。上下行你最终与数据库可以只进行有意义通过该应用程序,由于所有的后期处理它访问。
Sirex 2012年

21
@skynorth如果您依靠代码来确保密钥保持完整性,那么您将从数据库中删除RDBMS的基本原理。这没有任何意义,因为访问数据库的每个应用程序都必须确保精确复制该功能。为什么不让DB处理它,因为那是它的设计目的。例如,DB可以本地防止重复密钥。
Buttle Butkus 2012年

10
不要忘记交易!
Sklivvz 2012年

24
@skynorth:tl; dr:使数据保持一致的规则应在数据库中实现。例如,对于曾经编写的99%的应用程序,数据(以及数据库)在您的应用程序死掉之后就无法生存了。几年来,我已经看过很多次了(嘿,我们需要在Windows / iPhone / Android /无论什么新事物上部署一个版本,因为{insert old platform here}快要死了,我们在此处托管或Oracle数据库并在那里创建新的UI )。这种趋势没有理由在今天或不久的将来停止。
Binary Worrier 2012年

122

我将其改写为“从不做任何代码,SQL Server可以为您做的很好 ”。

诸如字符串操作,正则表达式之类的东西都可以在SQL Server中使用(除非SQL CLR)。

上面倾向于谈论诸如连接,设置操作和查询之类的事情。其背后的目的是将大部分繁重的工作委托给SQL Server(在它擅长的事情上),并尽可能减少IO的数量(因此,让SQL执行联接并使用WHERE子句过滤掉,返回很多较小的数据集)。


27
如果将所有SQL都比应用程序代码做的更好的事情都放到了SQL层中,那么无论好坏,都会有很多业务逻辑最终存入数据库。我已经看到了,是的,性能非常出色。但是幸运的是,开发团队都非常了解应用程序开发和SQL,因为两者之间的界限变得非常混乱。我不建议将此作为起点,而应该作为系统变得非常流行并且性能随时间下降之后的终点。
吉米·霍法

3
课程设置的马匹?
StuperUser 2012年

28
@NathanLong我不知道为什么仍然有很多人认为您无法将SQL保留在源代码控制中。最初,我们只需要在源代码管理中从头开始创建数据库所需的所有存储过程/表脚本/等,然后再使用Visual Studio数据库项目。没有这些项目就可以很好地工作,并且与他们一起工作可以更好。SQL和创建系统所需的所有其他可更改事物一样,应受版本控制!如果您将创建脚本保持在版本控制下,而不必维护差异脚本使用工具,则可以使用Redgate diff工具对大多数RDBMS进行部署
Jimmy Hoffa 2012年

3
如果您的SQL支持REGEX操作和字符串操作,那么在SQL中进行操作可能是一个不错的选择。
凯文·克莱恩

3
@NathanLong:像这样想,一个DB表是由写在文本文件中的一小段代码定义的,其语法类似于“ create table ...”。现在,您可以将文本文件存储在所需的任何SCM中,就像您使用自己喜欢的应用程序语言中的数据库表创建代码调用所需的任何API一样,并将该文本文件存储在SCM中。我认为一个问题是,有些人认为数据库在某种程度上是魔术兽,他们只知道如何编写VB代码(或其他方式),因此他们仅根据自己知道的应用程序语言来思考。
gbjbaanb 2012年

47

在代码中你可以得到SQL服务器做决不做为你(重点是我的)

答案的关键是您需要寻找SQL为您做得好,而不是简单地做某事。SQL是一种非常强大的语言。结合内置功能,它可以做很多事情。但是,您可以在SQL中执行某些操作这一事实不应成为在SQL中实际执行操作的借口。

我做出决定的具体标准是查看您获得的数据量和往返次数:是否可以通过将任务运送到服务器来减少数据量,而又不增加往返次数。跳闸,则任务属于服务器;如果数据量保持不变或增加而往返次数没有同时减少,则该任务属于您的代码。

考虑以下示例:

  • 您存储出生日期,并且需要计算一组用户的年龄。您可以让SQL Server进行减法,也可以在代码中进行。往返次数保持不变,发送回给您的数据量也增加。因此,基于代码的解决方案必胜
  • 您存储一个出生日期,并且需要查找年龄在20到30岁之间的用户。您可以将所有用户重新加载到客户端,进行减法以查找年龄,然后进行过滤,但是将逻辑传递给SQL Server将减少数据量,而无需进行额外的往返;因此,基于SQL的解决方案将获胜。

1
当我在某个地方工作时,业务逻辑在SQL中变得混乱,我们在进行多次往返时没有遇到麻烦。我们只是在一次往返中使用了多个结果集,所以该规则有点儿破了,尽管该规则的精神在瞄准黄金平均值方面非常出色
Jimmy Hoffa 2012年

2
+1这是一个很棒的答案,因为它提供了支持两个方向的具体示例。
布兰登

1
在第二个示例中。如果场景如下,您怎么说?用户和bday是缓存,并且说记录大小在1000-2000范围内。在内存中执行此操作不是更快,因为缓存了数据,因此无需进行数据库调用,因此避免了“中间” sql操作。处理过程将遍历内存中的1000多个用户列表,并查找匹配的位置。这会不会比在数据库中执行速度更快
user4677228 2015年

1
@ user4677228但尝试按比例放大:-p。如果您的代码必须扫描所有数据以计算所有年龄,而您想要的结果只是“至少有20个用户且年龄在30岁以下的用户数量?”,那么缓存将根本无法帮助您。您仍然会最终将整个表流式传输到客户端,但是数据库服务器可以在内存/缓存中完成所有操作,并且不管数据库客户端是通过本地套接字还是通过网络远程连接,都可以为您提供快速的答案。您只是愿意在WHERE条款中计算年龄。
宾基

21

简而言之,应该正确地说:“永远不要在代码库中执行数据库特定的操作 ”,因为在数据库中可以更好地解决它们。

看一下设置基本操作的示例。如您所知,RDBMS是为处理常见的数据存储和操作而构建的。

另外,数据库的项目选择也起着重要的作用。拥有RDBMS(MS SQL,Oracle等)不同于RavenDB等NoSQL数据库。


永远不要在您的代码库中进行设置操作,这绝对意味着在LINQ中对集合(选择,求和,位置,单个)的所有操作(选择,求和,位置,单个)都应该在SQL中而不是在您的应用程序中完成,这会将很多业务逻辑放入数据库中。
吉米霍法

4
您描述的内容不是客户端代码。这是一个业务层,您可能在其中拥有自己的操作逻辑。但是,对1M +记录执行此逻辑将使您反感。
EL Yusubov 2012年

@JimmyHoffa:事实并非如此,有时您会生成瞬态信息,这些瞬态信息需要与应用程序内存中已有的数据进行处理。Linq为此创造了奇迹。
Fabricio Araujo 2012年

@FabricioAraujo我知道linq为什么很棒,但是这个答案指出“ 从不在应用程序代码中进行基于集合的操作”,如果您从未在应用程序代码中进行过设置操作,则您将永远不会使用linq,因为这就是linq的全部目的。我要指出的是,从不对应用程序代码进行设置操作是一个不正确的规则
Jimmy Hoffa 2012年

@JimmyHoffa:不,规则说“ RDBMS不能在应用程序中为您做的好”。我说的是瞬时信息,而不是持久存在于数据库中的信息。我在需要满足代码处理要求的系统上工作。我记得在对数据库进行大量处理之后,我曾对该数据进行额外处理以生成(非常重要的)报告的业务规则。我可以在上面使用linq(它在现已不复存在的Delphi.Net上完成)。换句话说,即使遵循该规则,也可以使用linq。
Fabricio Araujo 2012年

13

通常,与应用程序相比,您的数据库具有更多可使用的信息,并且可以更有效地执行常见数据操作。例如,您的数据库维护索引,而您的应用程序则必须动态索引搜索结果。因此,在所有其他条件相同的情况下,可以通过将工作推送到数据库而不是应用程序来减少总体工作量。

但是随着产品扩展,扩展应用程序通常比扩展数据库变得容易。在大型安装中,经常看到应用程序服务器的数量超过数据库服务器10到1或更多倍。添加更多应用服务器通常是将现有服务器克隆到新硬件上的简单问题。另一方面,在大多数情况下,添加新的数据库服务器要困难得多。

因此,这时的口头禅成了保护数据库。事实证明,通过缓存数据库结果memcached或在应用程序侧日志中对更新进行排队,或者通过一次获取数据并计算应用程序中的统计信息,您可以极大地减少数据库的工作量,从而不必再依赖于更复杂,更脆弱的数据库集群配置。


1
金钱可以解决硬件可扩展性问题,而没有任何金钱可以解决软件复杂性。
图兰斯·科尔多瓦

3
@ user1598390的确:硬件便宜,程序员昂贵。金钱可以解决软件的复杂性。花在程序员身上的钱。但是请注意,我们并不是在谈论干净代码与speghetti。我们正在谈论的是在应用程序方面还是在数据库方面进行工作。软件的复杂性仅与边缘相关,因为这两种选择都可以遵循良好的设计原则。一个更好的问题是:“ 哪种设计成本更高? ”。
tylerl 2012年

一旦您的代码库繁琐而又繁琐,其中大部分代码都是在做非商业性的工作,那么您唯一可以做的就是所有重新设计的母亲,这不仅要花费硬件,而且涉及太多不确定性,此外您将永远知道在哪里可以找到优质的硬件,但是优质的程序员则是另一回事……与此同时,您的竞争对手也正在利用他们的时间进行改进,适应变化并让客户满意。
图兰斯·科尔多瓦

1
+1是唯一在答案中提及扩展规模的人。
马特

硬件便宜了,不再是便宜了-在数据中心,电力和硬件占运行成本的88%(由Microsoft引用),因此花更多的钱在程序员上编写高效的代码是非常划算的,直到我们得到无限的支持和廉价的融合能力。
gbjbaanb 2012年

12

我认为,不使用数据库来完成它的设计将是一个糟糕的设计。我从来没有见过任何数据库在数据良好的数据库之外强制实施规则的数据库。我查看了数百个数据库。

所以必须在数据库中完成的事情:

  • 审核(仅应用程序审核不会跟踪数据库的所有更改,因此毫无价值)。

  • 数据敏感度约束包括默认值,外键约束和必须始终应用于所有数据的规则。并非总是可以通过应用程序更改或插入所有数据,尤其是对于大型数据集,它具有一次性的数据修复功能,一次无法记录一个记录(请更新这100,000条记录,这些记录在被标记为状态1时应予以标记)归因于应用程序代码错误为2或请更新所有从客户端A到客户端B的记录,因为公司B购买了公司A)和数据导入以及其他可能涉及同一数据库的应用程序。

  • JOINS和where子句过滤(以减少通过网络发送的记录数)


6

“过早的优化是计算机编程中所有邪恶的根源(无论如何,绝大部分)”- Donald Knuth

数据库就是那个。应用程序的数据层。它的工作是为您的应用程序提供所需的数据,并存储提供给它的数据。您的应用程序是放置实际可用于数据的代码的地方。显示,验证等。

而在标题行中的情绪是令人钦佩的,并精确到一个点(滤波的基本事实,突出,分组等在绝大多数情况下留给DB)的“井”可能是一个定义订购。SQL Server可以执行的高性能任务很多,但是您可以演示的任务SQL Server以隔离,可重复的方式正确运行的知识很少。SQL Management Studio是一个很棒的数据库IDE(特别是考虑到我曾使用过的其他选项,例如TOAD),但是它有其局限性,首先是要使用它做的几乎所有事情(或在其中执行的任何过程代码)根据定义,下面的DB)是“副作用”(更改状态位于进程的内存空间范围之外)。此外,SQL Server中的过程代码只是最新的功能,可以使用最新的IDE和工具来衡量托管代码使用覆盖率度量和路径分析的方式(因此,您可以证明测试X遇到此特定if语句) ,Y和Z,而测试X旨在使条件成立并执行一半,而Y和Z执行“其他” 。相应地,假定您有一个测试,该测试可以将数据库设置为特定的启动状态,通过某些操作执行数据库过程代码,并声明期望的结果。

与大多数数据访问层提供的解决方案相比,所有这些都更加困难和复杂。假设数据层(以及DAL)知道如何在给出正确输入的情况下完成工作,然后测试您的代码是否提供正确输入。通过将诸如SP和触发器之类的过程代码保留在DB之外,而在应用程序代码中执行这些类型的操作,可以轻松实现所述应用程序代码。


等等,等等?您如何从正确性证明到测试,可以证明存在错误,但永远不能证明代码是正确的?
梅森惠勒2012年

2
存储过程不是过程代码。SP是预先计算的SQL查询,存储在DB内部并在其中运行。它不是应用程序代码。
gbjbaanb 2012年

1
如果SP仅限于SQL查询,那么您是对的。如果它是T-SQL或PL / SQL,包括条件中断,循环,游标和/或其他非查询逻辑,那么您错了。网络空间中数据库中的许多SP,功能和触发器都具有这些额外的元素。
KeithS 2012年

5

人们似乎没有意识到的一件事是,不管对代码质量的影响如何,在SQL Server上进行所有处理不一定都很好。

例如,如果您需要获取一些数据,然后从该数据中计算出一些数据,然后将该数据存储在数据库中。有两种选择:

  • 将数据获取到您的应用程序中,在您的应用程序中进行计算,然后将数据发送回数据库
  • 设计一个存储过程或类似的过程来获取数据,在其中进行计算,然后通过一次对SQL Server的调用将所有数据存储起来。

您可能会认为第二个解决方案始终是最快的,但这绝对不是事实。即使SQL不适合解决该问题(例如正则表达式和字符串操作),我也忽略了。假设您有SQL CLR或类似的东西甚至在数据库中拥有强大的语言。如果需要1秒钟来回往返并获取数据,然后花1秒钟来存储数据,然后需要10秒钟来对它进行计算。如果您在数据库中全部都做错了,那就错了。

当然,您要剃掉2秒钟。但是,您是将100%的(至少)一个CPU内核浪费了10秒(还是至少10秒钟)还是在Web服务器上浪费了该时间?

Web服务器易于扩展,而数据库则非常昂贵,尤其是SQL数据库。大多数情况下,Web服务器也是“无状态”的,可以随时添加和删除,而无需对负载平衡器进行任何其他配置。

因此,不仅要考虑将操作减少2秒钟,还要考虑可伸缩性。当可以使用便宜得多的Web服务器资源且对性能的影响较小时,为什么要浪费数据库服务器资源之类的昂贵资源


1
您还忘记了网络旅行-您无法通过添加服务器来水平扩展而不会影响效率。因此,通过添加where子句来减少数据负载是显而易见的-但其他sql操作不一定会降低性能。通常,您的观点是正确的,但对于将DB视为愚蠢的数据存储区而言,这并不是正确的。我曾经研究过的最具扩展性的应用程序,它为每个数据调用使用了存储过程(两个复杂的查询除外)。第三个解决方案是最好的-“存储过程仅获取必要的数据”,不确定是否要“计算”。
gbjbaanb 2012年

4

我喜欢看它,因为SQL应该只处理数据本身。决定查询外观的业务规则可以在代码中发生。信息的正则表达式或验证应通过代码完成。应该保留SQL以便仅连接表,查询数据,插入干净数据等。

传递给SQL的应该是干净的数据,SQL真正不需要知道的只是存储,更新,删除或检索某些信息。我已经看到太多的开发人员希望将其业务逻辑和编码用SQL编写,因为他们将数据视为业务。将逻辑与数据脱钩,您会发现代码变得更简洁,更易于管理。

不过只有我的$ 0.02。


为什么要对数据库中已有的数据运行正则表达式或验证?约束应防止坏的数据来自曾经到达那里,和使用正则表达式的可能意味着你需要更多的有用列..
布伦丹·朗

我并不是说我将对来自数据库的数据使用正则表达式或验证。我想我应该已经澄清了那是关于去数据库的数据。我的观点是,数据在到达DAL之前应先进行清理和验证。
史丹利玻璃杯(Stanley Glass Jr)2012年

3

通常,我同意代码应控制业务逻辑,而数据库应为无逻辑哈希。但是这里有一些要点:

主,外键和必需(非null)约束可以由代码强制实施。约束是业务逻辑。是否应该将它们排除在数据库之外,因为它们重复了可以执行的代码?

您无法控制的其他各方是否接触数据库?如果是这样,则在数据附近强制执行约束是很好的。可以将访问限制为实现逻辑的Web服务,但这假定您在“第一”并且有权在其他各方上强制使用该服务。

您的ORM是否为每个对象执行单独的插入/更新?如果是,那么在批处理大数据集时将遇到严重的性能问题。设置操作是必经之路。ORM在准确建模所有可能要执行操作的联接集时会遇到麻烦。

您是否将“层”视为服务器的物理拆分还是逻辑拆分?从理论上讲,任何服务器上运行的逻辑都可能仍属于其逻辑层。您可以通过编译到不同的DLL中来组织拆分,而不是专门拆分服务器。这可以显着增加响应时间(但会牺牲吞吐量),同时保持关注点分离。拆分的DLL以后可以在没有新版本的情况下移动到其他服务器,以增加吞吐量(以响应时间为代价)。


为什么要下票?
mike30 2012年

5
我没有投票,但是任何数据库专家都会告诉您,考虑对数据库进行无逻辑散列是一个非常糟糕的主意。这会导致数据完整性问题或性能问题,或同时引起这两个问题。
HLGEM 2012年

1
@HLGEM。答案描述了将逻辑保留数据库中或放置在DB服务器上的原因。仍然没有解释。
mike30

他们可能没有像我所做的那样对立,这就是为什么我没有投票。
HLGEM 2012年

3

习惯用法更多地与保持业务规则,与数据以及关系(数据,结构和关系)有关。它不是解决所有问题的一站式服务,但有助于避免手动操作如果在数据库级别可用,则维护记录计数器,手动维护关系完整性等。因此,如果其他人出现并扩展程序或编写与数据库交互的另一个程序,他们将不必从以前的代码中找出如何维护数据库完整性的方法。当其他人想要编写新程序以与同一数据库进行交互时,手动维护记录计数器的情况尤为重要。即使新创建的程序具有正确的计数器代码,原始程序和同时运行的新程序可能会破坏它。甚至还有代码可以检索记录并在写入新的或更新的记录之前(通过代码或作为单独的查询)检查条件,如果可能的话,通常可以在insert或update语句中实现。数据损坏可能再次导致。数据库引擎保证原子性;确保带有条件的更新或插入查询仅影响满足条件的记录,并且没有外部查询可以在更新进行到一半时更改数据。在许多其他情况下,最好使用数据库引擎来使用代码。这全都与数据完整性有关,而与性能无关。甚至可以在其中编写代码来检索记录并检查条件,然后再编写新的或更新的记录(以代码或作为单独的查询),如果可能,通常可以在insert或update语句中实现。数据损坏可能再次导致。数据库引擎保证原子性;确保带有条件的更新或插入查询仅影响满足条件的记录,并且没有外部查询可以在更新进行到一半时更改数据。在许多其他情况下,最好使用数据库引擎来使用代码。这全都与数据完整性有关,而与性能无关。甚至可以在其中编写代码来检索记录并检查条件,然后再编写新的或更新的记录(以代码或作为单独的查询),如果可能,通常可以在insert或update语句中实现。数据损坏可能再次导致。数据库引擎保证原子性;确保带有条件的更新或插入查询仅影响满足条件的记录,并且没有外部查询可以在更新进行到一半时更改数据。在许多其他情况下,最好使用数据库引擎来使用代码。这全都与数据完整性有关,与性能无关。数据库引擎保证原子性;确保带有条件的更新或插入查询仅影响满足条件的记录,并且没有外部查询可以在更新进行到一半时更改数据。在许多其他情况下,最好使用数据库引擎来使用代码。这全都与数据完整性有关,与性能无关。数据库引擎保证原子性;确保带有条件的更新或插入查询仅影响满足条件的记录,并且没有外部查询可以在更新进行到一半时更改数据。在许多其他情况下,最好使用数据库引擎来使用代码。这全都与数据完整性有关,与性能无关。

因此,这实际上是一个很好的设计习惯或经验法则。在数据损坏的系统中,没有任何性能可以提供帮助。


0

如前所述,目标是在数据库中尽可能少地收发数据,因为往返时间非常昂贵。一遍又一遍地发送SQL语句是浪费时间,尤其是在更复杂的查询中。

使用数据库中的存储过程可以使开发人员像API一样与数据库进行交互,而不必担心背面的复杂架构。由于仅发送名称和一些参数,因此它也减少了发送到服务器的数据。在这种情况下,大多数商务逻辑仍可以在代码中,但不能以SQL形式。该代码实质上将准备从数据库发送或请求的内容。


0

有几件事要记住:

  • 关系数据库应通过外键确保引用完整性
  • 扩展一个数据库可能既困难又昂贵。只需添加更多Web服务器,即可轻松扩展Web服务器。尝试增加SQL Server功能会很有趣。
  • 使用C#和LINQ,您可以通过代码进行“联接”之类的事情,因此在很多情况下都可以做到两全其美

0

“过早的优化是万恶之源”-Donald Knuth

使用最适合该工作的工具。为了数据完整性,通常是数据库。对于高级业务规则,这是基于规则的系统,例如JBoss Drools。对于数据可视化,这将是一个报告框架。等等

如果您有任何性能问题,则应随后查看是否可以缓存任何数据,或者是否可以更快地实现数据库中的实现。通常,购买额外服务器或额外云功能的成本将远低于增加的维护成本和额外错误的影响。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.