时序数据库是过去几年内需求成长最旺盛的一类数据库。在传统的OLTP关系型数据库领域,已经有了不少标杆型的产品,如oracle, mysql, postgresql等等,因此关于这类数据库,一些关键的设计决策已有公认的解决方案。然而与之不同的是,时序数据库作为一类新兴的数据库,目前仍可以说是百花齐放,不同的数据库产品在关键的设计决策上常常都会有区别。那么究竟哪种设计是更合理更“正确”的呢?在许多问题上,目前尚没有定论。
事务就是这样一个典型的例子。很多人认为时序数据库不需要支持事务,也因此,大部分时序数据库产品并没有支持事务;而在这方面,DolphinDB则是一个例外:DolphinDB是支持基本的事务的。那么时序数据究竟需不需要支持事务呢?为什么DolphinDB选择支持事务呢?我们邀请到了DolphinDB智臾科技研发总监周信静来谈一谈他对于这个问题的看法。
Q1 很多人认为时序数据库并不需要支持事务,或者说对事务的需求并不多,对此你怎么看?
如果是简单的应用,比如用时序数据库来做一下监控可视化,确实事务没有太大的必要。但是在很多对数据一致性要求高的场景下,不支持事务的时序数据库是没法用的。事务有很多非常好用的特性,它能够保证数据一致性,并发隔离,简化运维,避免数据丢失,甚至有些应用程序依赖这种特性来完成功能。最关键的是,如果数据库本身不支持事务,那么用户即便在应用层有这样的需求也只能束手无策。总之,时序数据库如果支持事务,不仅能处理很多简单的应用场景,还能处理更加复杂高端的应用场景,何乐而不为?
Q2 支持事务对时序数据库来说有着怎样的意义或者利弊?
时序数据库事务使得数据库可以支持更多的高端时序数据库应用场景,保证数据一致性,并发隔离,简化运维,避免数据丢失。值得指出的是,由于时序数据库需要提供持续不断的高吞吐写入,故性能对于时序数据库异常重要,因此有人可能会认为事务会影响读写性能。针对这一问题,DolphinDB利用了时序数据库批量写入的特点,用两阶段提交协议实现了分布式事务,为写入提供了高吞吐的事务支持,能很好地满足写入吞吐要求。换言之,我们的实现使得DolphinDB既支持了事务,又保证了写入性能。除此之外,时序数据库用户往往希望在写入量巨大的同时下还能够执行大规模的数据分析,这要求读写不相互影响。为了解决这个问题DolphinDB在两阶段提交基础上实现了MVCC 机制,提供了全局快照读的隔离级别,同时读取不会影响写入,写入也不会影响读取。谈到支持事务的弊端,那么对多数产品来说,一个弊端就是支持事务会使得数据库的实现变得复杂很多,而这对于我们来说不是问题,因为我们有足够强的工程能力去完成这个功能的实现。反之,如果选择不支持事务,那么对于需要支持事务的场景来说,实际上也只是把工程的复杂性转移到了用户头上,这是我们不想看见的。
Q3 可以举些例子说明支持事务在时序数据库应用中的优势吗?
如果我们只是用于可视化,一些简单的统计,事务的确不重要。但是如果希望通过数据来决策,反控我们的交易,我们的设备,就很重要。举个金融领域的例子,股票市场的报价快照数据3秒钟一批进入时序数据库,同时因为数据量很大,我们往往会把数据进行分布式存储在多台数据节点上。交易程序从时序数据库中读取报价数据,根据模型做出交易决策。如果我们在交易决策的时候,一部分股票是3秒钟前的数据,一部分股票是现在的数据,那么交易决策很大可能是错误或者不是最优的,这不是金融客户希望看到的。再举个物联网常见的例子,时序数据库被用在工业控制算法的一环里,用来反向控制设备的状态,比如给设备降升温、降升压等等。这种场景下大量传感器数据定期被写入我们的数据库,控制算法从我们的数据库中读取传感器时序数据做控制决策,如果没有事务的话,控制算法看到一部分数据是旧的,一部分是新的,在这种不一致数据下做出的错误决策可能是灾难性的,比如造成事故。因此支持事务在这些高端场景下是重要甚至是必须的。再举一个例子,在没有事务支持的时序数据库中,如果发生了宕机,可能会造成数据的丢失和不一致,没有办法恢复。在DolphinDB中,数据节点宕机不会造成数据丢失,我们的事务和恢复机制保证数据写入要么完成要么没有完成,不会因为断电宕机造成数据部分写入部分丢失,这就大大解放了运维保障工作。
DolphinDB是如何实现事务的?
事务保证了ACID,即原子性(Atomicity),一致性(Consistency,隔离性(Isolation),持久性(Durability)。
- DolphinDB通过WAL(Write Ahead Log)来保证事务的原子性和持久性,对数据的操作首先写入到WAL(Write Ahead Log),若事务进行到一半发生错误(如宕机),在重启时可以根据WAL将事务回滚(rollback),保证事务包含的操作要么全部成功,要么全部失败。
- DolphinDB通过两阶段提交协议(Two-phase commit, 2PC)来保证分布式事务的原子性以及集群数据的一致性。对于分布式事务涉及到的所有节点,要么全部节点上执行成功(commit),要么全部失败(rollback),不会出现部分成功部分失败的情况。
- DolphinDB通过多版本控制(Multiversion concurrency control, MVCC)来保证隔离性。读和写的并发事务相互隔离,读取不会影响写入,写入也不会影响读取。
- DolphinDB通过Raft来保证集群元数据的一致性。
2PC
在一个分布式事务中, 可能涉及多个数据节点,两阶段提交协议(Two-phase commit, 2PC)主要用于保证分布式事务的原子性,即要么全部节点上执行成功(commit),要么全部失败(rollback),不会出现部分成功部分失败的情况。
2PC中包含两种角色:协调者(coordinator)和参与者(participant)。协调者决定事务的全局状态以及负责推进整个事务.2PC包含两个阶段:
- Commit request (or voting) phase
协调者向所有参与者分发事务内容以及发送PREPARE
信息询问是否可以提交. 参与者执行事务中包含的操作(但不会提交), 并根据执行的结果回复YES
或者NO。
- Commit (or completion) phase
协调者根据参与者的回复来决定事务的状态是COMMIT
或ABORT
: 只有全部参与者回复YES
, 事务的状态才会是COMMIT
, 否则为ABORT
. 然后协调者将事务的状态(COMMIT
/ABORT
)发送给所有的参与者, 参与者据此提交或终止该事务。
MVCC
多版本控制(Multiversion concurrency control, MVCC)是一种并发控制协议用以保证数据库的数据一致性。如果没有并发控制, 并发的读和写可能导致读者读取到写入一半的数据,从而导致数据不一致。锁(read-write lock)是一种常用的并发控制手段, 通过创建临界区,使得并发的读写串行化,保证了并发下的正确性。但是这种方式会使得读写相互阻塞, 有一定的性能损失。
MVCC提供了快照级的隔离等级(snapshot isolation),其核心原则就是读取不会影响写入,写入也不会影响读取。MVCC维护了数据库里数据的多个版本,每一个事务看到的都是事务开始时数据的快照。当一个事务需要更新数据时,不会立即覆盖原数据,而是为数据创建一个新的版本,在该事务提交之前,对数据的更改对于其他事务是不可见的(其看到的是老版本的数据)。
支持事务,还是不支持事务?这是一个问题。DolphinDB对此的回答是:支持。支持事务,能够保证数据的一致性,能够减少运维的复杂性,同时能够大大扩展数据库的应用场景。而针对事务对于性能的影响,DolphinDB使用两阶段提交协议和MVCC以及团队强大的工程能力来保证性能。
总之,希望这篇文章能给大家带来一些启发,帮助大家思考事务对于时序数据库的意义。谢谢阅读!