1. 事务

  • mysql
  • 事务

一、事务特性

  • 原子性: 事务所包含的一系列数据库操作要么全部成功执行,要么全部失败回滚
  • 一致性: 在事务开始以前,数据库中的数据有一个一致的状态。 在事务完成后,数据库中的事务也应该保持这种一致性。事务应该将数据从一个一致性状态转移到另一个一致性状态。 比如在银行转账操作后两个账户的总额应当不变。
  • 隔离性:同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。
  • 持久性:事务完成后,事务对数据库的所有更新将被保存到数据库,即使系统崩溃,修改的数据页不会丢失

二、事务如何实现

  • 隔离性是通过数据库锁和 MVCC 的机制实现的
  • 持久性通过 Redo log(重做日志)来实现
  • 原子性和一致性通过 Undo log(回撤日志)来实现。

日志参考: 跳转

三、事务并发问题

  • 脏读:一个事务读到了另一个未提交事务修改过的数据
  • 不可重复读:一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。
  • 幻读:在一个事务两次读取的之间,有新的数据提交

四、事务的隔离级别

  • 读未提交:能读到未提交的数据。会出现:脏读、不可重复读、幻读
  • 读已提交:只能读到已提交的数据。会出现:不可重复的、幻读
  • 可重复读(默认):在同一个事务内的查询都是事务开始时刻一致的。会出现幻读
  • 串行化:完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

MySql 使用不同的锁策略 (Locking Strategy)/MVCC 来实现四种不同的隔离级别。

读提交、可重复读的实现原理跟 MVCC 有关

读未提交和串行化跟锁有关。

五、隔离级别的实现原理

5.1 MVCC

多版本并发控制(MVCC),它是通过保存并读取历史版本的数据,来降低并发事务冲突,从而提高并发性能的一种机制。它的实现依赖于隐式字段、undo 日志、快照读 & 当前读、Read View。

5.1.1 隐式字段

InnoDB 存储引擎,每一行记录都有两个隐藏列字段:

  • DB_TRX_ID(事务id),记录每一行最近一次修改(修改 / 更新)它的事务 ID
  • DB_ROLL_PTR(回滚指针),相当于一个指针,指向回滚段的 undo 日志

版本链:多个事务并行操作某一行数据时,不同事务对该行数据的修改会产生多个版本,每个版本的数据会记录成为UndoLog,同视也携带着上面两个字段,并通过回滚指针(DB_ROLL_PTR)连一条 Undo 日志链。

5.1.2 Read View

Read View 保存着对当前事务可见的数据。

生成Read View时依赖下面四个字段:

  • 活跃的事务id列表(trx_ids): 当前系统活跃的事务id列表
  • 最小活跃事务id(up_limit_id): 活跃的事务id列表中,最小的那个id
  • 待分配的事务id(low_limit_id): 等待分配给新事务的id
  • 当前事务id(creator_trx_id): 表示生成该 ReadView 的事务的id

生成Read View时,会去读取数据所在行的版本链,会根据链上每个记录的DB_TRX_ID判断,具体规则如下:

  • DB_TRX_ID=当前事务id: 说明是当前事务修改的记录,对当前事务可见
  • DB_TRX_ID<最小活跃事务id: 说明修改该版本的记录的事务在当前事务生成Read View时就已经提交,对当前事务可见
  • DB_TRX_ID>=待分配的事务id: 说明修改该版本的记录的事务是在当前事务之后产生的,对当前事务不可见
  • 待分配的事务id>DB_TRX_ID>=最小活跃事务id: 需要判断DB_TRX_ID是否还在活跃的事务ids中,如果在,对当前事务不可见;不在,则对当前事务可见

只有在select的时候才会创建ReadView

5.2 不同级别的实现

  • 读已提交: 每次select时,都会重新生成Read View。有不可重复读的问题
  • 可重复读: 只有在事务开始时生成一次Read View。解决了不可重复读的问题

5.3 当前读&快照读

使用到read view的就叫快照读,没有使用到就是当前读,在读已提交和可重复读级别下:

  • select 语句默认是快照读,
  • select 语句加锁是当前读
  • update 语句是当前读

5.4 MVCC解决幻读了吗?

  • 快照读: 解决了。因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务第一次看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 当前读: 没有解决。当前读通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。 参考: 跳转open in new window

Loading...