NoSQL数据库(如MongoDB、Cassandra、Hbase、DynamoDB、Riak)让应用程序开发变得更简单。它们提供了相当灵活的数据模型和丰富的数据类型,而且与许多传统数据库系统相比,更易于安装和配置。但缺少原子事务支持却是一大退步。Daniel Abadi是耶鲁大学的一名副教授,主要从事数据库系统架构和实现研究。近日,他在一篇 文章 中剖析了NoSQL数据库不支持原子事务的原因,并提供了两种实现可扩展、事务型NoSQL数据库的方案。
原子事务允许对数据库中的不同数据项同时进行写操作,这些操作要么全部执行,要么全部不执行。而且,结合恰当的并发控制机制,原子性可以确保并发及后续事务要么可以看到原子事务所有已完成的写入,要么一个都看不到。缺少原子事务,应用程序开发人员就需要自己处理一组写操作仅有部分成功的情况。
也许有人会认为,NoSQL数据库比较新,还没有时间实现原子事务支持。实际上,Cassandra的“批量更新”特性可以视为向这个方向前进了一小步。不过,NoSQL数据库已经出现了将近十年,它们没有实现事务支持显然有更深层次的原因,那就是对可扩展性的关注。按照设计,大多数NoSQL系统都要能够跨多台不同的机器扩展,数据库中的数据分布在不同的机器上。一个事务中的写入操作可能会访问多个分区(在多台机器上)的数据,这就是“分布式事务”。在分布式事务中确保原子性需要参与事务的机器相互协作。每一台机器都必须确定,事务在其它机器上能够成功提交。而且,需要有一个协议,确保事务写入操作涉及的机器在写入数据状态稳定之前都不会出现故障。这个协作过程不仅会消耗大量的资源,而且会增加数据库请求延迟。更大的问题是,在协作过程完成之前,其它操作无法读取该事务写入的数据。并发事务延迟会导致其它与出现延迟的事务在时间上存在重叠的事务延迟,最终导致系统“阻塞 (cloggage)”。分布式事务所需的分布式协作会严重影响数据库系统的性能,包括事务吞吐量和事务延迟。因此,大多数NoSQ系统都选择了不支持事务。
MongoDB、Riak、Hbase和Cassandra都支持单个 键 的事务操作。这是因为单个键的所有信息都存储在单台机器上。因此,单个键的事务操作并不涉及上述复杂的分布式协作。由于分布式事务需要分布式协作,所以似乎必须在性能可扩展性和分布式事务支持之间进行权衡。事实上,许多NoSQL数据库提供商都是基于这个假设,在构建可扩展系统时,为了防止服务器性能退化,放弃支持分布式原子事务。
Daniel指出,这是 完全错误 的。可扩展系统是可以支持高性能分布式原子事务的。他们最近发表了一篇 论文 ,提出了一种在可扩展系统中支持原子事务的、新的权衡策略,具体是在公平性、隔离性和吞吐量(FIT)三者之间进行取舍。其中,公平性是指任何事务的执行都不会因为其它事务被故意延迟,而隔离性可以确保相互冲突的事务可以看到其它事务的写入操作。一个支持分布式原子事务的可扩展数据库至少可以实现上述三个属性中的两个。FIT三者之间的取舍可以产生三种支持分布式原子事务的方案:
保证公平性和隔离性而牺牲吞吐量的系统;
保证公平性和吞吐量而牺牲隔离性的系统;
保证隔离性和吞吐量而牺牲公平性的系统。
换句话说,下面两种方法都可以构建出具备高分布式事务吞吐量的可扩展系统。
放弃隔离性
如上所述,导致数据库系统阻塞的根本原因是分布式协作。更确切地说,如果一个事务正在执行,那么其它需要访问共享数据的事务必须等到分布式协作完成后才能进行。这种等待就是由强隔离性所保证的,因为它确保事务可以看到与它冲突的事务。如果放弃隔离性,那么其它事务就看不到其它事务的操作,也就不必等待分布式协作完成就可以执行和提交。而且,有一类数据库约束可以确保分布式数据库在事务弱隔离情况下的正确性。更多信息可以阅读Peter Bailis的文章《 多分区原子读(RAMP) 》。
放弃公平性
分布式协作同隔离机制在时间上存在重叠。所以,可以通过重新设定分布式协作的顺序最小化两者之间的时间重叠,从而减轻二者之间的相互影响。以此为基础构建的系统放弃了公平性,可以选择最合适的时间进行分布式协作,Daniel将这样的系统称为 “隔离性-吞吐量”系统 。比如,可以在事务之外进行协作,协作所需的时间不会增加并发事务的执行时间。