数据库事务--串行化

之前提到了不同的数据库隔离级别,有一些问题需要提出:

  • 隔离级别不容易理解,在各个数据库中有不同的实现和保证;
  • 从应用代码角度很难判断一段代码在特定隔离级别下是否安全;
  • 竞态条件不容易检测到;

从应用开发者角度来看,串行化隔离可以说是最高的隔离级别了。串行化指并行事务执行结果和串行执行结果一致的保证(避免各种形式的竞态条件)。

实际串行执行

最简单的串行化保证就是真实的串行执行操作。这种方式看似牺牲了多线程执行的并行能力,实际上在最近,这种想法已经被很严肃的考虑:

  • 内存价格下降,使得内存全量数据存储变得可能,大大降低了单个事务的执行时间;
  • OLTP型事务通常不会有过多读写操作;

这种思想已经被VoltDB/H-Store,Redis和Datomic所采用。
串行执行有以下特点:

  • 每个事务需要短小快速,以防止阻塞其它所有事务;
  • 写操作执行速度由cpu来决定,或者通过数据分片来提升多核单机的并发能力;
  • 如果执行了分片策略(几乎所有分布式数据存储都需要分片),跨片的事务执行效率会很低;

过于单线程串行化的数据库,Redis和VoltDB都非常值得学习。

两段锁

之前说过,锁可以有效避免脏读脏写。两段锁要求多事务可以同时访问某条没有被写锁加锁的数据,如果被写锁加锁,则:

  • 如果A事务要求读X数据,后来的B事务要求写X数据,则B必须在A完成或者回滚之后才能执行;
  • 如果A事务要求写X数据,后来的B事务要求读X数据,则B必须在A完成或者回滚之后执行;

在两段锁的具体实现上:

  • 事务读取数据需要获取数据的共享锁(shared lock),一条数据允许同时发放多个共享锁,除非它已经被互斥锁(exclusive lock)锁定;
  • 事务更新数据需要获取数据的互斥锁(exclusive lock),一条数据同一时间只能发放单个互斥锁;
  • 如果一个事务先读后写,则需要把共享锁升级成互斥锁;
  • 事务获取锁之后,只有在事务完成或者回滚之后才会释放锁(这就是两段锁的含义:一个阶段获取锁,一个阶段释放锁),需要注意的是,两段锁并不能避免死锁,死锁发生时一般会选择先回滚一个事务;

串行化快照隔离(Serializable Snapshot Isolation)

乐观并发控制VS悲观并发控制:

上面提到的两段锁就是典型的悲观锁。串行化执行等价于为每一个事务加上互斥锁,为了减少单个事务持有锁的时间,只能把事务拆分成较小的粒度。与之相反,串行快照隔离是乐观锁。串行快照隔离,字面上来讲还是在读取阶段保证数据来自于一个稳定版本的快照,加上对于线性执行的冲突检测来决策是否应该回滚某一个事务。为了避免可能出现的写倾斜,有下面两种方法:

  • 检测过去MVCC读取(Detecting stale MVCC reads)
    根据MVCC所带有的版本号来检测读取之后发生的数据变更:


    数据库事务--串行化_第1张图片
    Detecting stale MVCC reads

    如果检测到了这种情况,需要事务回滚重试。以此避免写倾斜。

  • 检测影响先前读取的写入
    这种情况如图所示:


    数据库事务--串行化_第2张图片
    图示

你可能感兴趣的:(数据库事务--串行化)