快照隔离与可重复读的关系是什么?

1,051 阅读4分钟

前言

随着业务迭代,我们会更多地面临分布式环境带来的挑战。

数据库软件或硬件出现问题(写过程中)

应用程序崩溃(多个读写操作中)

网络延迟异常导致数据库链接

并发读写问题

多个客户端之间的竞争条件下出现的异常

事务的引入简化了上述问题的解决,但需要我们正确运用,因此理解事务就变得尤为重要,本文旨在对事务隔离级别简单说明。

相关理论

ACID:Atomicity、Consistency、Isolation、Durability

CAP:Consistency、Availability、Partition tolerance

BASE:Basically Available、Soft state、Eventually consistency

请参考:【读书笔记】分布式系统理论

事务隔离级别

未提交读(Read Uncommited)

脏读:一个事务读到了另一个事务未提交的数据

已提交读(Read Commited)

读数据:读已经提交的数据(没有脏读

  • 没有解决不可重复读(read skew, nonrepeatable read) 问题,如下图

    1位置Alice查询select balance from accounts where id = 1

    2位置Alice认为Account1 + Account2 = 900,但是她总共有1000!

image-20220228193737710

即使两个并发事务全部提交,Alice依然能看到正确的数据,但这“暂时的不一致”(temporary inconsistency)也是不可接受的。

  • 为了解决上述脏读、不可重复读的问题,快照隔离(snapshot isolation)技术被广泛应用于关系型数据库存储引擎。

快照隔离与可重复读(Snapshot Isolation and Repeatable Read)

  • 快照隔离简单来讲就是每个事务都去读取一致的快照(已提交的) ,也就是1的时间节点Alice会查到数据500。
  • 实现快照隔离的方式就是多版本并发控制(MVCC, multi-version concurrency control)。
  • 如果在Read commited级别,MVCC只需要维护(已提交版本、未提交版本)

image-20220228201252283

新增created bydeleted by,记录每个数据快照的创建事务ID与删除事务ID(update可以认为是delete & insert)。

一个小坑:快照隔离在不同的数据库有不同的实现命名,《Designing Data Intensive Applications》原文如下:

In Oracle it is called serializable, and in PostgreSQL and MySQL it is called repeatable read

  • 为了更加清楚地说明,下文就用可重复读(Repeatable Read)级别代替快照隔离

幻读问题

提交读与可重复读对只读(read-only)事务支持我们已经讨论过了,那么对写事务,特别是并发的两个写事务之间会出现什么问题呢?

image-20220228231325164

可以看到,Alice事务和Bob事务分别对shift_id=1234 and on_call=true的数据进行了筛选,并且分别对数据进行了更新,导致最后的数据出现了错误。

幻读问题特征

  1. 使用SELECT语句使用特定条件查询出多条数据;
  2. 依赖上个SELECT语句的结果,决定接下来的操作;
  3. 决定后,写数据并提交事务。

核心在于,B事务插入或删除了,满足A事务SELECT查询条件的数据,导致A事务出现”幻觉“。

串行化(Serializability)

针对上述幻读场景,需要更强的事务隔离级别约束,就是串行化。

串行化级别的实现,取决于各个数据库存储引擎的选型,但主要有以下三种:

  1. 串行化运行
  2. 2PL(Two Phase Locking)机制
  3. 乐观并发控制,例如串行化快照隔离

InnoDB如何解决”幻读“

InnoDB采用2PL机制解决”幻读“,并且在可重复读(RR) 级别实现,具体实现方式为next-key locking

2PL简介

多个并发事务允许读取相同数据,仅当这个数据没有在写入中:

A事务已经读取数据,B事务并发地写入该数据,B必须等A提交或者中断后执行;

A事务已经写入数据,B事务并发的读取该数据,B必须等A提交或者中断后执行。

  • 这里的”数据“包含范围查询出的区间

NEXT-KEY

next-key锁是一种行锁与gap锁的结合,gap锁可以对索引值范围内的行数据进行加锁。

A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.

如果我们拥有一个索引,并且值为10, 11, 13, 20;那么,next-key锁可能的加锁范围如下所示。

(negative infinity, 10]

(10, 11]

(11, 13]

(13, 20]

(20, positive infinity)

结合Bob和Alice的例子,Bob已经读取shift_id=1234 and on_call=true的行数据,那么next-key会对shift_id=1234的范围数据进行加锁,Alice写入必须等待Bob提交完成或者中断,避免幻读问题产生。

参考