Transaction的基本概念和介绍

我们已经知道了分布式系统中有很多问题,比如说我们正在读的数据也许正在被别人修改,系统有可能在我们执行一系列操作的中途crash,多个同时发生的写操作可能相互覆盖等等。我们总归需要一个好的方法来解决这个问题。而Transaction的产生就是为了解决这些问题,所谓的transaction就是应用程序把一系列操作组合成一个逻辑单元, 这个单元可以认为是一个原子操作,要么就全部成功,要么就全部失败。有了Transaction的概念之后,错误的处理就变得方便了很多。

单Object和多Object操作

在我们详细介绍之前,请大家一起先来回顾一下ACID的概念,你可以在我之前的这篇文章《SQL和NoSQL的分析》中找到详细的介绍,这里不再复述。

多Object的操作

回顾了一下ACID之后,让我们来看一个简单的例子,假如你想找到你的未读邮件的数目,有可能会使用下面这样的query

这样的query粗看起来还不错,但是有一天你突然发现假如一个人的邮件数目很多,这个query可能耗时比较长,于是你决定把未读邮件的数目存储到一个单独的域。当有邮件来了,就去把未读的数目加一,当有一个邮件被读了之后,就把未读邮件减少一。

这里有两个操作,一个就是插入新来的邮件,一个是增加未读邮件的数目,我们原本的想法这两个操作必须是一体,否则就可能出现下图所示的问题:

这里插入邮件和更新未读数量是分开的,假如在这个中间有另外一个用户正好来读未读邮件的数目,就会发现并没有未读邮件,而事实上收件箱里是有一封未读邮件的。而我们上文ACID中的Isolation就可以很好的规避这个问题,也就是说用户2要不两么都看不到(插入邮件和更新未读数量),要么可以看到两个。

而ACID中的atomic则可以解决另外一个问题,如下图所示,这个情况下邮件的插入是成功的,但是未读邮件数目的更新失败了,假如没有atomic的保证,这两者就有了冲突。Atomic会保证在这种情况下,邮件的插入也会被roll back。

上面这个例子其实就是多object的操作,这种情况下的transaction可以成为多object的transaction。一般来说,它需要知道哪些操作是属于一个transaction的,在关系性数据库中,这通常是由客户端的TCP 连接来决定的:在一个连接中,任何处于BEGIN TRANSACTION和COMMIT之间的内容被认为是同一个transaction。

单object的写

也许你会认为单object的写就没有我们上面提到的问题了,其实不然。比如说你现在要写一个20KB的JSON文档,假如你写了一般比如10KB之后突然出问题了,数据库该怎么处理,是保存你已经写了的这一半数据还是整个就都丢掉。或者说当你更新数据的时候更新了一半,这个时候又有人来读这个数据,它读到的是什么数据,一半新的一半旧的吗?

所以一般来说storage level的引擎会保证单object的atomicity和isolation。通常会使用log来进行 crash的recovery保证原子性,使用object锁来保证isolation。

这种单object的保证是很有用的,但是一般来说这个不是我们通常所说的transaction的概念,毕竟很多时候这个都是数据库内部的保证。所以通常来说我们提到的transaction的概念是指多object的组合操作。

错误和终止的处理

Transaction的一个核心功能就是当有错误发生的时候,我们能够安全的进行重试,这里一个核心思想就是一个组合中的操作假如在某一步发生了问题,那么整个组合的操作都会全部roll back。因此一般来说,对于transaction的错误处理就是进行重试。

虽然说有错误进行重试是一个很好也很有效的方法,但它仍然有可能会造成我们想不到的问题:

  • Transaction事实上是成功了,只是因为网络等原因没有能够成功告诉客户端,这个时候假如还去重试,就会有可能造成重复操作的错误。
  • 假如错误是因为负载过高造成的,你重复transaction可能会使得问题更加严重。当然我们可以限制重试的次数,特殊处理overload的错误等等来进行规避。
  • 重试对于一些transient错误是有效的,比如死锁,临时的网络故障等等。对于一些永久的错误,重试也还是会有问题。
  • 假如一个transaction会造成额外的影响,那么哪怕transaction终止了这些影响也可能会存在。这个时候two phase的commit可能会有所帮助。

总结

我们简单介绍了transaction的概念,以及当transaction发生错误时候的常见处理方法。

You may also like...

2 Responses

  1. June 6, 2021

    […] 我们在上文《Transactions的基本概念和介绍》中提到,transaction的一个作用就是可以做到隔离。比如说当一个translation在写一个数据的同时,如果有另外一个transaction在同时读或写,就有可能会有问题。而且这种问题很难发现和debug,所以一般来说这种transaction级别的隔离都是由数据库本身来保证的,理论上来说这样应用的开发者就不需要来操心具体的细节了。可惜理想很丰满,现实却很骨感。很多时候数据库本身因为各种原因(性能等因素),它提供的不是严格的隔离,而是一种弱隔离,简单来说就是能保证一些同时读写的操作,但是有一些却不能保证。这样一来开发者就有点懵了,我究竟该何去何从呢?本文就来详细给大家介绍一下常见的transaction弱隔离,相信了解了这些之后,你再去开发应用就会游刃有余了。 […]

  2. July 7, 2021

    […] Transaction是一个通用的概念,这里我们不详细介绍,大家可以参考《Transactions的基本概念和介绍》这篇文章。 […]

Leave a Reply

Your email address will not be published. Required fields are marked *