MySQL 的事物管理

MySQL 的事物管理原理

事务的特性

  • 原子性:一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样
  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏
  • 隔离性:数据库允许多个并发事务同时对数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失

隔离级别

  • 读未提交:一个事务还没提交时,它做的变更就能被别的事务看到
  • 读提交:一个事务提交之后,它做的变更才会被其他事务看到(解决脏读,Oracle默认的隔离级别)
  • 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的,而且未提交变更对其他事务也是不可见的(解决脏读和不可重复读,MySQL默认的隔离级别)
  • 串行化:对于同一行记录,写会加写锁,读会加读锁,当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行(解决脏读、不可重复读和幻读)

Redo Log与 Undo Log介绍

Redo Log—innodb存储引擎的日志文件

在MySQL中,如果每次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程IO成本、查找成本都很高。MySQL里常说的WAL技术,全称是Write-Ahead Logging,它的关键点就是先写日志,再写磁盘

redo log是InnoDB存储引擎层的日志,又称重做日志文件,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。在实例和介质失败(media failure)时,redo log文件就能派上用场,如数据库掉电,InnoDB存储引擎会使用redo log恢复到掉电前的时刻,以此来保证数据的完整性。

当有一条记录需要更新的时候,InnoDB引擎就会把记录写到redo log里面,并更新buffer pool的page,这个时候更新就算完成了

buffer pool是物理页的缓存,对InnoDB的任何修改操作都会首先在buffer pool的page上进行,然后这样的页面将被标记为脏页并被放到专门的flush list上,后续将由专门的刷脏线程阶段性的将这些页面写入磁盘
MySQL 的事物管理

InnoDB的redo log是固定大小的,比如可以配置为一组4个文件,每个文件的大小是1GB,从头开始写,写到末尾就又回到开头循环写
MySQL 的事物管理

write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。check point是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件

write pos和check point之间空着的部分,可以用来记录新的操作。如果write pos追上check point,这时候不能再执行新的更新,需要停下来擦掉一些记录,把check point推进一下

有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力成为crash-safe

Undo Log

Undo Log是为了实现事物的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log 来实现多版本并发控制(简称:MVCC)

在操作任何数据前,首先将数据备份到一个地方(这个存储数据备份的地方称为Undo Log),然后进行数据的修改,如果出现错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事物开始之前的状态

注意:Undo Log是逻辑日志,可以理解为:

  1. 当delete一条记录时,Undo Log中会记录一条对应的insert记录
  2. 当insert一条记录时,Undo Log中会记录一条对应的delete记录
  3. 当update一条记录时,它记录一条对应的反向update记录

MySQL锁技术和MVCC

mysql锁技术

当有多个请求来读取表中的数据时可以不采取任何操作,但是多个请求里有读请求,又有修改请求时必须有一种措施来进行并发控制。不然很有可能会造成不一致。

读写锁
解决上述问题很简单,只需用两种锁的组合来对读写请求进行控制即可,这两种锁被称为:

共享锁(shared lock),又叫做"读锁"
读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞。

排他锁(exclusive lock),又叫做"写锁"
写锁会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。

MVCC基础

MVCC (MultiVersion Concurrency Control) 叫做多版本并发控制。

InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间,当然存储的并不是实际的时间值,而是系统版本号。

MVCC在mysql中的实现依赖的是undo log与read view

  • undo log :undo log 中记录某行数据的多个版本的数据。
  • read view :用来判断当前版本数据的可见性

事物的实现

前面讲的重做日志,回滚日志以及锁技术就是实现事务的基础。

  • 事务的原子性是通过 undo log 来实现的
  • 事务的持久性性是通过 redo log 来实现的
  • 事务的隔离性是通过 (读写锁+MVCC)来实现的
  • 而事务的一致性是通过原子性,持久性,隔离性来实现的!!!

原子性,持久性,隔离性折腾半天的目的也是为了保障数据的一致性!

原子性实现

一个事务必须被视为不可分割的最小工作单位,一个事务中的所有操作要么全部成功提交,要么全部失败回滚,对于一个事务来说不可能只执行其中的部分操作,这就是事务的原子性。

那么数据库是怎么实现的呢? 就是通过回滚操作。

所谓回滚操作就是当发生错误异常或者显式的执行rollback语句时需要把数据还原到原先的模样,所以这时候就需要用到undo log来进行回滚,接下来看一下undo log在实现事务原子性时怎么发挥作用的

每条数据变更(insert/update/delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上,对应的逻辑如下:

  1. 当delete一条记录时,Undo Log中会记录一条对应的insert记录
  2. 当insert一条记录时,Undo Log中会记录一条对应的delete记录
  3. 当update一条记录时,它记录一条对应的反向update记录

持久性实现

MySQL的innoDB存储引擎,使用Redo log保证了事务的持久性。

当事务提交时,必须先将事务的所有日志写入日志文件进行持久化,就是我们常说的WAL(write ahead log)机制(这个技术是保障持久性的关键技术)。这样才能保证断电或宕机等情况发生后,已提交的事务不会丢失,这个能力称为 crash-safe。

隔离性实现

READ UNCOMMITED (未提交读)

读最新的数据,不管这条记录是不是已提交。不会遍历版本链,少了查找可见的版本的步骤。这样可能会导致脏读。

对写仍需要锁定,策略和读已提交类似,避免脏写。
MySQL 的事物管理

READ COMMITED (提交读)

MySQL的读已提交实际是语句级别快照。

与可重复读级别主要有两点不同:

  1. 获得ReadView的时机。每个语句开始执行时,获得ReadView,可见性判断是基于语句级别的ReadView。读的策略与可重复读类似。

  2. 写锁的使用方式。这里不需要GAP LOCK,只使用记录锁。并且事务只持有被UPDATE/DELETE记录的写锁(可重复读需要保留全部写锁直到事务结束,而读已提交只保留真正更改的)。
    MySQL 的事物管理

REPEATABLE READ (可重复读)

可重复读是MySQL默认的隔离级别,理论上说应该称作快照(Snapshot)隔离级别。读不加锁,只有写才加锁,读写互不阻塞,并发度相对于可串行化级别要高,但会有Write Skew异常。

事务在开始时创建一个ReadView,当读一条记录时,会遍历版本链表,通过当前事务的ReadView判断可见性,找到第一个对当前事务可见的版本,读这个版本(MVCC)。

对于写操作,包括Locking Read(SELECT … FOR UPDATE), UPDATE, DELETE,需要加写锁。根据谓词条件上索引使用情形,锁定有不同的方式:

有索引:对于索引上有唯一约束且为等值条件的情形,不用GAP LOCK,只锁定索引记录。对于其它情形,使用GAP LOCK,相当于谓词锁。
没有索引:由于MySQL没有实现通用的谓词锁,这时就相当于锁全表。
MySQL 的事物管理

SERIALIZABLE (序列化)

在可串行化级别上,MySQL执行S2PL并发控制协议, 一阶段申请,一阶段释放。读写都要加锁。
MySQL 的事物管理

一致性实现

通过回滚,以及恢复,和在并发环境下的隔离做到一致性。

上一篇:有限次数的Undo&Redo的C#实现


下一篇:FusionInsight MRS:你的大数据“管家”