论文地址:并发控制:现代应用程序完整性的实证研究——Bailis等人。2015年

    这篇论文绝对值得一读:经验丰富的数据库系统研究人员对Ruby社区的实际应用程序进行了研究,并试图不过分鄙视他们的发现,在思考这一切对支持这些应用程序的底层数据管理系统意味着什么的同时,也有一部分责任是由数据库系统承担的。而且(如果你不是一个Rails开发人员),在你开始自鸣得意地认为这不会在你喜欢的语言/框架中发生之前,他们还表明,在Java、PHP、Python和Javascript的其他组合中也会出现同样的问题。

    作为理解这种现代ORM行为的一个视角,我们研究了Ruby on Rails(或者简单地说,“Rails”),它是为站点提供动力的现代框架中的一个核心参与者……这个非常成功的软件框架与数据库管理系统之间存在着积极的对抗关系,响应一个熟悉的“NoSQL”运动的重复:让数据库让开,让应用程序完成工作。

    数据库和现代ORM框架之间的抗阻不匹配有什么后果?
    通过避免数十年来在本机数据库并发控制解决方案方面的工作,Rails开发了一组原语,用于处理应用层构建中的应用程序完整性,从底层数据库系统的角度来看,这是一个激进的并发控制系统。我们研究了这些非常规机制的设计和使用,并通过分析它们和在实践中量化数据完整性违规来评估它们在实践中的有效性。我们的目标是了解这类不断增长的应用程序目前如何与数据库系统交互,以及作为数据库系统社区,我们如何能够积极参与这些批评,以更好地满足这些开发人员的需求。

    本文研究了67个基于Ruby on Rails和Active Record构建的开源应用程序,每个项目平均27kloc。Rails有四种主要的并发控制机制:

    • 事务:一系列操作可以包装在一个事务块中,并将在数据库事务中执行-从Rails 4.0.0开始,隔离级别可以根据每个事务进行控制。
    • 乐观和悲观的记录锁定。悲观锁基于SELECT for UPDATE,乐观锁依赖于活动记录模型中的特殊字段。
    • 应用程序级验证—支持预定义和用户定义的验证功能。每个已声明的验证都按顺序运行,如果它们都通过,则在数据库中更新记录。
    • 应用程序级关联——它们的作用类似于外键约束,但在应用程序层中维护。

    在2014年12月Rails 4.2发布之前,Rails没有为数据库支持的外键约束提供本机支持。在Rails 4.2中,外键是通过与每个模型分别声明的手动模式注释来支持的;声明一个关联并不声明相应的外键约束,反之亦然。

    应用程序级约束(验证和关联)是提升的并发控制方式:
    Rails的非常规机制验证和关联是活动记录模型的一个显著特征。相比之下,在官方的“Rails指南”中实际上既没有讨论事务,也没有讨论锁,而且通常也没有将其作为确保数据完整性的一种手段来提升。相反,Rails文档[7]更倾向于验证,因为它们“是数据库不可知的,终端用户无法绕过,而且便于测试和维护”
    因此,在检查实际应用程序时,验证和关联占主导地位
    也许在这些总体趋势中最值得注意的是,我们发现验证和关联分别是事务的13.6倍和24.2倍,数量级比锁定更常见。这些激进的机制符合这些应用程序开发人员喜欢的Rails理念。也就是说,与其采用传统的事务性编程原语,Rails应用程序编写器选择指定正确性标准,并让ORM系统代表它们执行这些标准……尽管如此,这些标准仍然是由应用程序编写器声明的,代表着对传统的、面向事务的编程的背离,我们把这项工作的大部分剩余时间都花在了研究它们到底想保存什么(以及它们是否真的足够保存)。
    除了从总体上看使用情况外,“研究单个应用程序也很有趣”,这导致了这个“人们可能期望”gem(ruby包管理工具):
    Spree只使用六个事务处理,
    1.)取消订单,
    2.)批准订单(原子性地设置用户ID和时间戳),
    3.)在履行地点(例如仓库)之间转移发货,
    4.)在发货之间转移项目,
    5.)在履行地点之间转移库存,更新订单的特定库存状态。虽然这是一组合理的交易地点,但在电子商务应用程序中,可能会有更多的场景需要交易,包括订单放置和库存调整。

    还有…

    应用程序语料库的其余部分包含了许多这样有趣的例子,说明了决定并发控制机制的通常是特别的过程。
    那么,应用程序级验证和关联指定的约束是否安全?
    首先,请记住,每个验证序列(以及模型更新,如果验证通过)都包装在数据库支持的事务中,只要数据库使用可序列化隔离,验证的预期完整性将得到保留。然而,关系数据库引擎通常默认为非序列化隔离;特别是对于Rails,PostgreSQL和MySQL实际上分别默认为较弱的读提交和可重复读隔离级别。我们没有遇到应用程序更改隔离级别的证据。Rails没有为验证配置数据库隔离级别,我们遇到的任何应用程序代码或配置都没有更改默认隔离级别(或者在文档中提到这样做)。
    鉴于验证不太可能完全隔离,Bailis等人。利用不变合流分析找出哪些不变量可以在无协调并发执行的情况下保留。在使用最流行的十个不变量中,最流行的presence在插入时是安全的,但在删除时则不是。第二个最流行的不变量unique不是I-confluent:“也就是说,如果两个用户同时插入或修改记录,他们可以引入重复项。”
    总的来说,大量的内置验证在并发操作下是安全的。在插入的情况下,86.9%的内置验证发生在I-合流。在删除的情况下,只有36.6%的事件是I-合流的。但是,关联和多记录唯一性取决于工作负载而不是I-confluent,因此可能会导致问题。

    在序列化执行的情况下,唯一性验证是安全的,但Oracle不支持这种情况(它最强的隔离级别是快照隔离),PostgresQL可序列化隔离级别(截至2015年3月)包含一个允许重复的确认错误。
    Rails文档警告说,唯一性验证可能会失败并允许重复的记录。然而,尽管有可用的补丁可以通过使用数据库内约束和/或索引来纠正这种行为,Rails在默认情况下提供了这种不正确的行为。
    在一个实验中,作者很容易用一个测试应用程序创建副本。唯一性约束将重复项的数量减少了一个数量级,与没有该约束的相同运行相比,但是仍然允许创建许多重复项(700)。在病态工作量较少的情况下,重复的可能性取决于密钥的分布,但总是存在的。

    激进关联验证机制也可能导致违规:
    考虑到完全激进的机制可以引入断开的关联,可以引入多少悬空记录?一旦记录被删除,以后的任何验证都将通过SELECT调用来观察它。然而,在最坏的情况下,在一对多关系的一侧上的野蛮级联删除可能会无限期地停止,从而允许无限数量的并发插入到关系的多方。因此,验证至少在理论上只会减少删除之前插入的悬挂记录的最坏情况数量;验证期间可能会发生任意数量的并发插入,从而导致悬挂记录的数量不受限制。
    在一个示例应用程序中也演示了这一点。
    前面的实验表明,活动记录在默认情况下是不安全的。由于对弱隔离异常的敏感性,验证容易受到数据损坏的影响…。事实上,Rails支持的应用程序中的并发和数据争用程度可能并不会导致这些并发竞争,在某种意义上,验证对于许多应用程序来说“足够好”。尽管如此,在这两种情况下,Rails的野生机制都不能很好地替代它们各自的数据库对应机制,至少在完整性方面是这样。

    但是,一个差劲的应用程序开发人员该怎么办呢?选择是在具有弱的和难以理解的隔离模型的ACID事务之间,还是在应用程序/框架中的自定义强制执行之间,后者是“一个昂贵、易出错和困难的过程,忽略了数据库社区数十年来的贡献”。”
    我们相信应用程序用户和框架作者需要一个新的数据库接口,使他们能够:

    • 用领域模型的语言表达正确性标准,摩擦最小,同时允许其自动执行。
    • 只在必要时付出协调的代价
    • 轻松部署到多个数据库后端

    总之,研究和当前实践之间的巨大差距既是一个紧迫的关注点,也是一个令人兴奋的机会,可以回顾数十年来关于可串行化替代方案的研究,着眼于当前的操作条件、应用程序需求和程序员实践。我们在这里的建议很苛刻,但是我们的数据库所服务的框架和应用程序编写器也是如此。给定正确的原语,数据库系统在确保应用程序完整性方面可能还可以发挥作用。