【问题标题】:Is transactional behaviour ever needed outside of databases?数据库之外是否需要事务行为?
【发布时间】:2008-11-17 21:02:48
【问题描述】:

如果没有事务,我不敢在数据库中做任何复杂的事情。几乎总是有一个简单易用的内置命令。但是,当您开始使用其他持久性数据时,您并没有得到这种简单易用的事务支持。一些例子是

  • 文件系统
  • 网络服务(我没有使用过)

即使在非持久性数据中,在出现异常后撤消工作块通常也很有用。使用一种语言获得的标准数据结构都不支持事务。

我想知道的是,为什么数据库是特殊情况?

是否有任何有用的链接指向数据库之外的事务行为主题?

【问题讨论】:

    标签: database transactions persistence unit-of-work


    【解决方案1】:

    我必须恭敬地不同意:事务系统不是自动和排他的数据库引擎,恰恰相反......

    我已经实现了一种不同于数据库事务的应用程序事务机制(在 .NET 中)。它实际上相当容易(包括单元测试套件在内的几个小时的工作)。它完全用 C# 编写,不依赖于任何数据库功能或任何其他组件。但首先是一些背景......

    这种非数据库事务特性存在于 Java 平台上的多种表现形式中,例如 EJB、ESB、JMS,并且通常与 BPM 相关联。其中一些表现形式使用底层数据库,但并非总是如此,也并非没有必要。其他平台也有类似的表现形式,例如 MSMQ。

    大多数旧版本控制系统不实现 ACID 事务语义。正如 ddaa 所说,CVS 没有,但 Subversion(它的继任者)有。 Visual Source Safe 没有。如果您研究 Subversion,您可以找到说明这一点的比较图表。

    现在关键点是,数据库事务或其等价物并不能保证安全的业务逻辑。虽然我喜欢 Subversion,但具有讽刺意味的是,它是这一事实的一个很好的例子。

    您可以虔诚地使用 Subversion 以及自动构建脚本(一个用于编译、测试和打包您的应用程序的命令),并且仍然将损坏的构建提交到源代码控制存储库。我已经多次看到了。当然,使用非 ACID 事务的源代码控制工具(如 VSS)更容易。但是很多人都震惊地发现可以使用 Subversion 之类的工具来实现这一点。

    请允许我布置场景。您和一位同事正在开发一个应用程序,并使用 Subversion 作为源代码控制存储库。你们俩都在编码,偶尔会提交到存储库。您进行一些更改,运行一个干净的构建(重新编译所有源文件),并且所有测试都通过了。因此,您提交更改并回家。您的同事一直在进行自己的更改,因此他还运行了一个干净的构建,看到所有测试通过,并提交到存储库。但是,您的同事随后从存储库更新,进行了一些更改,运行干净的构建,构建在他的脸上炸毁了!他恢复他的更改,再次从存储库更新(只是为了确定),并发现一个干净的构建仍然爆炸!您的同事在接下来的几个小时内对构建和源代码进行故障排除,最终发现您在离开之前所做的更改导致构建失败。他向您和您的共同老板发送了一封令人讨厌的电子邮件,抱怨您破坏了构建然后粗心大意地回家了。你早上到达,发现你的同事和老板在你的办公桌前等着骂你,其他人都在看着!因此,您可以快速运行一个干净的构建,并向他们展示构建没有损坏(所有测试都通过了,就像昨晚一样)。

    那么,这怎么可能呢?这是可能的,因为每个开发人员的工作站都不是 ACID 事务的一部分; Subversion 只保证存储库的内容。当您的同事从存储库更新时,他的工作站包含存储库内容(包括您的更改)和他自己未提交的更改的混合副本。当你的同事在他的工作站上运行一个干净的构建时,他正在调用一个不受 ACID 语义保护的业务事务。当他恢复更改并执行更新时,他的工作站随后与存储库匹配,但构建仍然中断。为什么?因为您的工作站也是不受 ACID 语义保护的单独业务事务的一部分,这与您对存储库的提交不同。由于在运行干净构建之前您没有更新工作站以匹配存储库,因此您实际上并没有构建存储库中存在的源文件。如果您执行了这样的更新,您会发现您的工作站上的构建也会失败。

    现在我可以阐述我的初始观点了——交易具有必须仔细考虑的范围/上下文。仅仅因为您有 ACID 事务并不意味着您的业务逻辑是安全的,除非 ACID 事务的范围/上下文和业务逻辑完全匹配。如果您依赖于某种形式的数据库 ACID 事务,但您在业务逻辑中执行了该数据库事务未涵盖的任何操作,那么您的差距可能会导致类似的灾难性错误。如果您可以强制您的业务逻辑与您的数据库事务完全匹配,那么一切都很好。如果没有,那么您可能需要单独的业务交易。根据未受保护逻辑的性质,您可能需要实现自己的事务机制。

    因此,消息传递可以是事务性的,但范围仅仅是消息。关于上面的例子,Subversion 的上下文只是对存储库的单个提交。但是,业务事务是一个干净的构建,涉及的范围要大得多。这个特定问题通常通过编写干净的构建脚本和干净的检出脚本来解决,理想情况下使用持续集成实现(例如,通过 CruiseControl 等)。在开发人员工作站上,它要求每个开发人员在干净构建之前执行完整更新(甚至是干净检查)。

    因此,回顾一下,每个事务都有一个限制其保护的范围或上下文。业务事务通常包含超出我们常用的事务机制(例如数据库引擎)范围的逻辑。您可能必须弥补差额。在极少数情况下,编写自己的事务机制甚至可能是有意义的。

    我为一家规模不大的 90 人公司构建了一个关键业务系统的重写架构。我发现实施这样的机制是必要的,而且我发现这种体验很容易、很有价值并且很有意义。我会再做一次,也许更容易一些,但我总是会质疑为什么我不能只坚持一个数据库事务。

    【讨论】:

    • 有趣的帖子。关于 SVN,您描述的场景实际上可能会发生,但恕我直言是 SVN 使用不当的结果:一个好的规则是在提交之前始终更新工作副本,并查看更新可能移植到工作副本中的所有更改,以避免这些情况。
    • 我同意你所说的一些观点,但你关于损坏构建的观点更多地与糟糕的实践有关,而不是 svn。在这种情况下,您要么需要 1) 在自己的分支中工作,要么 2) 确保不会收到会破坏代码的更新。
    • 感谢您的 cmets,但请注意您的建议模仿了我的观点——良好的源代码控制实践最终会产生一个包含源代码控制“数据库”事务的“业务”事务。
    【解决方案2】:

    我认为事务只能在数据库中看到的原因是,根据定义,提供事务的系统称为数据库。这听起来很循环,所以我必须详细说明。

    事务支持是提供ACID 属性的功能。用外行的话来说,这意味着一个事务允许 1. 将许多谨慎的操作捆绑到一个包中,这些操作要么作为一个整体成功,要么作为一个整体失败 2. 隐藏对并发用户的未提交更改,以便 3. 并发用户始终对系统有“一致”的看法。

    Filesystems 传统上提供一些锁定机制,但这与提供事务不同。但是,所有文件系统都有一些原子属性。例如,如果您有目录/a//b/,并且您同时尝试执行mv /a /b/amv /b /a/b,那么只有其中一个操作会成功,而不会影响完整性。然而,文件系统通常缺乏将多个操作捆绑到一个孤立的原子包中的能力。

    An answer 提到了颠覆。所有健全的版本控制系统都有事务。当提交多个文件时,系统要么完全应用提交,要么完全拒绝它(除了 CVS,我不认为它是理智的)。拒绝的原因总是同时发生的变化。版本控制系统的实现者非常注重维护数据库。

    Another 回答提到消息传递系统是事务性的。我没有阅读链接的材料,但答案本身只提到了消息的原子传递。那不是交易。

    Brian C. 在这里提到它之前,我从未听说过Clojure。在我看来,这确实是数据库上下文之外的事务实现。这里的重点是并发控制,而不是维护持久数据的一致性。

    因此,除了 Clojure 之外,似乎任何需要事务的系统要么使用底层数据库,要么将自身转变为数据库。

    【讨论】:

    • 更新了我的帖子,更清楚地表明消息是事务性的。
    • 错了。大多数版本控制系统没有事务(ACID)——只有最新的。 JMS 和 MSMQ 等消息传递系统的主要定义是它们将消息包装在事务中。在做出诸如“X”不存在或只有“Y”存在“X”之类的陈述时,应该非常小心。
    • @ddaa:ACID 不是事务的唯一定义,提问者也没有将话题限制为 ACID 事务。
    【解决方案3】:

    现代文件系统确实有事务。它们只是对最终用户透明。

    NTFS、XFS、JFS、EXT3 和 ReiserFS 都可以,仅举几例。

    这只是文件系统内部的。许多操作系统还支持使用独占(写入)和共享(读取)锁的文件锁定(例如,参见 *NIX 世界中的flock(2))。

    编辑: 如果您考虑一下,文件系统不像现代数据库那样具有隔离级别,因为一旦您完成读取文件,如果您没有锁定它,您通常会关闭它。然后在你想写的时候重新打开它。

    【讨论】:

    • 文件锁不是事务,因为没有提交操作会失败。降级。
    • 文件锁确实不是事务性的,是悲观的而不是乐观的,但出于性能原因,FS 通常是事务性的。
    • @ddaa:又错了,而且很严厉。
    【解决方案4】:

    Clojure 使用Software Transactional Memory,它使用事务使编写多线程程序变得容易和安全,而无需手动锁定。 Clojure 具有不可变的数据结构和对它们的可变引用,并且需要事务来更改引用。

    【讨论】:

      【解决方案5】:

      消息系统是事务性资源管理器的另一个例子。

      也就是说,您可以确保消息使用者成功处理来自队列的消息。如果处理失败,则消息留在队列中。

      此外,消息传递系统可以参与与另一个资源管理器的分布式事务。

      更多信息在

      【讨论】:

        【解决方案6】:

        Subversion 提交是事务性的:它们是真正原子的,因此中断的提交不会使存储库处于不一致的状态。

        【讨论】:

        • Subversion 有一个数据库后端。最初,它是 bdb。由于它的可靠性存在问题,他们实现了自己的后端数据库,称为 fsfs。通常,版本控制系统要么存在严重缺陷,要么受数据库支持。
        • @ddaa:这里也错了。 Subversion 的 fsfs 不是数据库引擎,其余的在事实和你的“结论”上都值得商榷。
        【解决方案7】:

        我需要将文件系统和数据库视为一个事务单元。

        就我而言,我只需要将一组文件下载到文件系统中。我通过每次创建随机目录、将数据放在那里并将目录名称存储在数据库表中来做到这一点。因此,我的所有数据库工作以及数据库表中的目录名称(= 文件系统工作)都可以在一个数据库事务中完成。

        http://www.databasesandlife.com/atomic-operations-over-filesystem-and-database/

        【讨论】:

          猜你喜欢
          • 2018-02-14
          • 2011-01-13
          • 2022-07-05
          • 1970-01-01
          • 1970-01-01
          • 2017-12-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多