我在一些项目中工作过,其中大多数业务逻辑是在数据库上实现的(大部分是通过存储过程实现的)。另一方面,我从一些其他程序员那里听说这是一个不好的做法(“那里有数据库来存储数据。其余的都由应用程序来完成”)。

这些方法中的哪一种通常可以更好吗?

我能想到的在数据库中实现业务逻辑的优点是:


业务逻辑的集中化;
独立性应用程序类型,编程语言,操作系统等类型;
数据库不太容易进行技术迁移或大型重构(AFAIK);
无需对应用程序技术迁移进行重做(例如:.NET到Java,Perl到Python等)。缺点:由于缺少最丰富的库和语言构造,SQL对于业务逻辑编程而言生产率较低,并且较复杂面向应用程序的语言提供;
通过库更难(如果可能的话)重用代码;
生产效率较低的IDE。

注意:我所谈论的数据库是关系数据库,热门数据库lik SQL Server,Oracle,MySql等。

谢谢!

评论

您可能会发现此问题的答案很有用。

这个论点已经被详尽讨论。我们还能在这里有意义地添加更多内容吗?

@gnat:甚至都没有。

类似于programmers.stackexchange.com/questions/158534/…

考虑数据库将远远超出应用程序的寿命。数据库甚至可能超过了您编写应用程序所使用的语言。数据本身通常是业务,数据库应该能够保护其包含的数据的完整性。因此,坦率地说,每个外键约束都是业务规则的实施。除非您摆脱关系数据库中的所有关系约束,否则您将无法真正从数据库中完全摆脱业务逻辑。

#1 楼

业务逻辑不会进入数据库
如果我们谈论的是多层应用程序,那么很显然,业务逻辑(一种运行特定企业的智能)属于业务逻辑层,而不是在数据访问层中。
数据库确实做得很好:

它们存储和检索数据
它们在不同数据实体之间建立和执行关系
它们提供查询数据以获取答案的方法
它们提供性能优化。
它们提供访问控制

现在,您当然可以在数据库中整理与对于您的业务问题,诸如税率,折扣,操作代码,类别等。但是,由于其他人已经提到的各种原因,对数据执行的业务操作通常不会编码到数据库中,尽管可以在数据库中选择操作并在其他位置执行。
当然,在那里可能是出于性能和其他原因而在数据库中执行的操作:

结算会计期
处理数字
夜间批处理
故障转移

自然,石刻上没有任何东西。存储过程仅适用于各种任务,因为它们位于数据库服务器上并具有一定的优势和优势。
存储过程无处不在
对所有数据的存储,管理和编码都具有一定的吸引力。在存储过程中检索任务,并简单地使用生成的数据服务。您肯定会从数据库服务器可以提供的最大性能和安全性优化中受益,这并不是一件小事。
但是,您要冒险做什么?

供应商锁定
对具有特殊技能的开发人员的需求
全面的Spartan编程工具
软件耦合极其紧密
没有分离的关注点

当然,如果您需要Web服务(无论如何,可能都是这样),您仍然必须构建它。
那么典型做法是什么?
我会有人说,一种典型的现代方法是使用对象关系映射器(例如Entity Framework)来创建对表进行建模的类。然后,您可以通过返回对象集合的存储库与数据库对话,任何有能力的软件开发人员都非常熟悉这种情况。 ORM动态地生成与您的数据模型和所请求的信息相对应的SQL,然后数据库服务器将处理该SQL以返回查询结果。
这工作得如何?很好,并且比编写存储过程和视图要快得多。通常,这可以满足大约80%的数据访问需求,其中大部分是CRUD。其余20%覆盖了什么?您猜对了:存储过程,所有主要的ORM都直接支持该存储过程。
您可以编写一个代码生成器,该代码生成器的功能与ORM相同,但带有存储过程吗?你当然可以。但是ORM通常独立于供应商,每个人都很好理解,并且得到了更好的支持。

评论


谢谢您的出色回答,@ Robert Harvey。但是我在考虑“供应商锁定”的论点:是否不使用特定技术(例如.NET或Java堆栈)来构建应用程序,同时还是供应商锁定?还是面向应用程序的堆栈供应商锁定与数据库锁定相比有优势吗?

–拉斐尔
13年4月10日在12:58

@RobertHarvey,但是.NET中的应用程序逻辑部分仍然锁定在.NET中。 PHP和Java也是如此。

–起搏器
2014年12月7日下午16:54

@Pacerier:通过供应商锁定,我指的是数据库供应商。实际上,很少替换数据库(和编程堆栈)。

–罗伯特·哈维(Robert Harvey)
2014年12月7日下午16:55

@kai:好吧,你不能两全其美。您要么使用存根和模拟并接受测试是人为的事实,要么编写真实的测试,并稍作延迟。我怀疑您的权衡是10分钟而不是30秒。

–罗伯特·哈维(Robert Harvey)
2015年10月12日15:27

也许是晚了,但我认为实现业务逻辑的存储过程属于业务逻辑层,而不是数据层。它们是一种独立的lang,不需要ORM。

–Paralife
16年5月4日在7:56

#2 楼

我坚信尽可能将业务逻辑排除在数据库之外。但是,作为我公司的绩效开发人员,我赞赏有时有必要取得良好的绩效。但是我认为这比人们所声称的要少得多。

我反对您的利弊。

您声称它集中了您的业务逻辑。相反,我认为它分散了权力。在我目前正在研究的产品中,我们将存储过程用于许多业务逻辑。我们的许多性能问题来自反复调用函数。例如,

select <whatever>
from group g
where fn_invoker_has_access_to_group(g.group_id)


这种方法的问题是通常(在某些情况下这是错误的)强制数据库将N次运行您的函数,每行一次。有时该功能很昂贵。一些数据库支持功能索引。但是您无法针对每个可能的输入为每个可能的函数建立索引。还是可以?

上述问题的常见解决方案是从函数中提取逻辑并将其合并到查询中。现在,您已经破坏了封装并重复了逻辑。

我看到的另一个问题是在循环中调用存储过程,因为无法连接或相交存储的proc结果集。

declare some_cursor
while some_cursor has rows
    exec some_other_proc
end


如果您从嵌套proc中提取代码,那么您将再次去中心化。因此,您不得不在封装和性能之间进行选择。

一般来说,我发现数据库的缺点在于:


计算
迭代(它们针对设置操作进行了优化)
负载平衡
解析

数据库擅长:


锁定和解锁
维护数据及其关系
确保完整性

通过执行昂贵的操作(例如循环和字符串解析)并将其保留在您的应用层中,您可以水平扩展应用程序以获得更好的性能。在负载均衡器后面添加多个应用服务器通常比设置数据库复制要便宜得多。

您是正确的,但是,它使您的业务逻辑与应用程序的编程语言脱钩,但是我看不到为什么这是一个优势。如果您有一个Java应用程序,那么您就有一个Java应用程序。将一堆Java代码转换为存储过程并不会改变您拥有Java应用程序的事实。

我的首选是使数据库代码专注于持久性。如何创建新的小部件?您必须插入3个表中并且它们必须处于事务中。这属于存储过程。

定义对窗口小部件可以执行的操作以及查找窗口小部件的业务规则属于您的应用程序。

评论


在SQL Server中,只需要在循环中调用写得不好的sps,就可以在参数中向其发送数据集并执行基于集的过程。

–HLGEM
13年4月9日在21:27

只要WHERE子句中有UDF,SQL Server就会生成次优查询计划。

– Jim G.
13年4月9日在23:28

似乎您的性能问题不是数据库与应用程序逻辑的错。它只是编写和架构不佳。在ORM世界中,同样的问题也会跟着您。在CRUD运营之外,ORM可能会令人头疼。如果您的系统数据量大,报告系统类型,请谨慎使用。

– Sam Yi
14年7月15日在18:06

那是真实的。我们大多数的性能问题仅是由于编写不良的代码和过于复杂的体系结构所致。但是我仍然相信我们在数据库中输入了错误的工作类型。尽可能多地将代码编码到数据库中已经导致我们做数据库不擅长的事情。

–布兰登
14年7月15日在19:24

这个例子甚至是将事务逻辑的核心部分放置在数据库中的一个论点:避免像瘟疫这样的迭代方法(代码或游标循环,而不是基于集合的表达式)。程序员倾向于以迭代方式(循环,遍历)处理对象集,这可能导致不必要的负载或许多单查询往返的SELECT N + 1问题。通过使用SQL或基于语言的表达式(例如LINQ),将尽可能地迫使它们使用基于集合的方法。

–埃里克·哈特(Erik Hart)
16-10-1在15:09

#3 楼

我曾在两家对此主题有不同见解的公司工作。

我个人的建议是在执行时间很重要(性能)时使用存储过程。由于存储过程是编译的,因此如果您有复杂的逻辑来查询数据,最好将其保留在数据库本身上。另外,它只会在最后发送最终数据到您的程序。

否则,我认为程序的逻辑应该始终在软件本身中。为什么?因为程序需要是可测试的,而且我认为没有一种简单的方法可以对存储过程进行单元测试。别忘了,未经测试的程序是不好的程序。

因此在需要时请谨慎使用存储过程。

评论


存储过程可以进行单元测试。有关某些技术,请参见此处。

–罗伯特·哈维(Robert Harvey)
2013年4月9日15:23



afaik,单元测试永远不要使用数据库或文件。因此,从技术上讲,“单元测试”存储过程不是单元测试,它会变得很慢。在开发过程中的任何时候,单元测试套件都应在几秒钟内运行(对于大型应用程序则可能需要数分钟)。

–让-弗朗索瓦·科特(Jean-FrançoisCôté)
13年4月9日在15:28

OP正在谈论“业务逻辑”,并且应该对业务逻辑进行单元测试。通过将其放入存储过程中,可以将其与数据库查询混合使用,这会减慢整个过程。就像我说的那样,您可以使用存储过程(这不是犯罪),但是它将模糊业务逻辑和数据库层之间的界限,这是很糟糕的。小心使用:)

–让-弗朗索瓦·科特(Jean-FrançoisCôté)
2013年4月9日15:34

如果创建数据库和必要的对象,则先进行sp,测试,然后再将其拆除,这是一个单元测试。它测试一个工作单元。

–托尼·霍普金森
13年4月9日在20:39

存储过程神话带来的性能提升是否被揭穿了?

– JeffO
2013年4月9日20:50

#4 楼

您需要找到一个中间立场。我见过可怕的项目,程序员在其中使用数据库的不过是价格过高的键/值存储。我见过其他程序员无法使用外键和索引的地方。另一方面,我看到了一些项目,其中大多数(如果不是全部)业务逻辑都在数据库代码中实现。

您已经注意到,T-SQL(或它的等效语言)其他流行的RDBMS)也不是编写复杂的业务逻辑的最佳位置。

我试图建立一个相当不错的数据模型,使用数据库的功能来保护我对该模型的假设(即, FK和约束),并谨慎使用数据库代码。当您需要数据库非常擅长的某些事情(即总和)时,数据库代码很有用,并且可以在不需要它们时避免您在网上移动大量记录。

评论


正如NoSQL从业人员所证明的那样,将数据库用作“定价过高”的键/值存储是一种完全有效的技术。

–罗伯特·哈维(Robert Harvey)
13年4月9日在15:28



@RobertHarvey您显然是正确的,但是如果您只需要键/值存储,那么我的直觉仍然坚持必须有一个比数据库更简单/更便宜/更快的解决方案。我需要了解有关NoSQL的更多信息。

–丹·皮切尔曼(Dan Pichelman)
2013年4月9日15:47

我看不到使用存储过程来解决设计不良的数据库。

– JeffO
13年4月9日在20:39

@RobertHarvey,我从字面上读了“定价过高的键/值存储库”。当有诸如MongoDB之类的选项免费提供时,为此支付Oracle或SQL Server许可就好像在浪费钱。

–拉斐尔
13年4月10日在13:34

@Raphael或者您可以使用PostgreSQL😉

–黛米
18 Mar 3 '18 at 14:19

#5 楼

如果您的业务逻辑涉及集合操作,那么数据库中最适合的地方是数据库,因为数据库系统确实擅长执行集合操作。

http://en.wikipedia.org/wiki/ Set_operations_(SQL)

如果业务逻辑涉及某种计算,则它可能属于数据库/存储过程之外,因为数据库不是真正为循环和计算而设计的。

尽管这些规则不是一成不变的,但它是一个很好的起点。

#6 楼

没有一个正确的答案。这取决于您使用数据库的目的。在企业应用程序中,您需要通过外键,约束,触发器等在数据库中提供逻辑,因为它是所有可能的应用程序共享代码的唯一位置。此外,将所需的逻辑放入代码中通常意味着数据库不一致且数据质量较差。对于仅同意GUI工作原理的应用程序开发人员来说,这似乎微不足道,但是我向您保证,尝试在合规性报告中使用数据的人们会发现,当他们因拥有不符合标准的数据而被罚款十亿美元时,这将非常烦人且代价高昂。没有正确遵守规则。

在非监管环境中,当您不太在意整个记录集,而只有一个或两个应用程序访问数据库时,也许您可​​以将其全部保留在应用程序中。

#7 楼

几年后,这个问题仍然很重要...

对我来说,简单的经验法则:如果是逻辑约束或无处不在的表达式(单条语句),请将其放在数据库中(是的,外键和检查约束也是业务逻辑!)。如果是程序性的,则通过包含循环和条件分支(实际上不能将其更改为表达式),将其放入代码中。

避免垃圾转储数据库

实际上,将所有业务逻辑放在应用程序代码中都可能会将(关系)数据库退化为垃圾箱,其中关系设计通常被完全忽略,数据可以具有任何不一致的状态,并且缺少规范化(通常主要是XML,JSON,CSV)等垃圾箱列)。

这种仅应用程序的逻辑可能是NoSQL兴起的主要原因之一-当然,缺点是应用程序必须处理所有逻辑本身,已经在关系数据库中内置了数十年。但是,NoSQL数据库更适合此类数据处理,例如,数据文档在其内部维护隐式的“关系完整性”。对于关系数据库,它只是滥用,造成更多麻烦。

表达式(基于集合)而不是过程代码

在最佳情况下,每个数据查询或操作都应编码为表达式,而不是程序代码。当编程语言支持表达式(例如.NET世界中的LINQ)时(对此,很遗憾,目前仅查询,没有任何操作)。在关系数据库方面,很长一段时间以来,人们都倾向于使用SQL语句表达式而不是过程游标循环。因此,数据库可以优化,并行执行操作或进行其他有用的操作。

利用数据库数据完整性机制

对于具有外键和检查约束的RDBMS,计算所得的列,可能的触发器和视图,这是在数据库中存储基本业务逻辑的地方。适当的规范化有助于维护数据完整性,以确保数据的唯一性和独特性。即使必须在代码和DB中复制它,也不应忽略这些基本的数据完整性机制!

存储过程?

由于如今很少需要存储过程,因为数据库保留已编译的SQL执行计划,并仅在使用不同参数的情况下再次出现相同查询时重新使用它们。因此,SP的precompile参数不再有效。一个人可以在应用程序或ORM中存储或自动生成SQL查询,这将在大多数时间找到预编译的查询计划。只要您不明确使用过程元素,SQL就是一种表达语言。因此,在最佳情况下,您将使用可以转换为SQL的代码表达式。

尽管应用程序端(包括ORM生成的SQL)不再位于数据库内部,与存储过程不同,我仍然将其计算在内作为数据库代码。因为它仍然需要SQL和数据库知识(最简单的CRUD除外),并且如果正确应用,其工作方式将与通常使用C#或Java等编程语言创建的过程代码大不相同。

#8 楼

它的确取决于业务,其文化和遗产。除了技术方面的考虑(双方都讨论过)之外,给出的答案还告诉您,它取决于人们的来源。在某些组织中,数据为王,而DBA是强大的人物。这是您典型的集中式环境,即一个数据中心,其中连接了许多终端。在这种类型的环境中的偏好是显而易见的。在数据中心发生任何变化之前,台式机可能会发生许多次根本变化,而两者之间几乎没有变化。

另一端是纯3层体系结构。或者在面向Web的业务中是多层的。您可能会在这里听到一个不同的故事。 DBA(如果有的话)只是执行一些管理任务的助手。

现代的应用程序开发人员将对第二种模型更具亲和力。如果您是在大型客户端-服务器系统上成长的,那么您很可能会在另一个阵营中。

这里经常涉及到许多与非技术环境相关的因素,因此没有一个普遍的答案。

#9 楼

术语“业务逻辑”易于解释。在构建系统时,我们要确保数据库及其内容的完整性。第一步,应该有不同的用户访问权限。作为一个非常简单的示例,让我们考虑一个ATM应用程序。

要获得帐户余额,在适当的视图上进行选择应该可以。但是,要转移资金,您需要将交易用存储过程封装。不允许业务逻辑直接更新贷方和借方金额表。在这个示例中,业务逻辑可以在请求转帐之前检查余额,或者简单地调用存储的proc以获取余额。转移并报告故障。恕我直言,在此示例中,业务逻辑应先行检查是否有足够的资金可用并且目标帐户存在,然后再调用转移资金。如果在初始步骤和存储的proc调用之间发生另一笔借记,则只有这样才会返回错误。

评论


漂亮的例子和解释。

–user251748
17-10-6在14:45