mysql事务特性以及隔离级别(MySQL事务隔离级别与可重复读的实现)

目录

mysql事务特性以及隔离级别(MySQL事务隔离级别与可重复读的实现)(1)

内容概览

事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都执行修改,要么都不执行,这就是事务的目的,也是事务模型区别于文件系统的重要特征之一。

MySQL Server系统结构包括三层:SQL层、存储引擎层和物理文件,事务操作是在存储引擎层完成的。

mysql事务特性以及隔离级别(MySQL事务隔离级别与可重复读的实现)(2)

MySQL Server系统结构

MySQL数据库支持多种存储引擎,有InnoDB,MyISAM,NDB,Memory等,并不是所有存储引擎都支持事务操作。

本文介绍InnoDB存储引擎对事务的支持。《MySQL技术内幕》对InnoDB存储引擎的介绍:“InnoDB通过使用多版本并发控制(MVCC)来获得高并发性,并且实现了SQL标准的4种隔离级别,默认为REPEATABLE(可重复读)级别,同时使用一种称为netx-key locking的策略来避免幻读(phantom)现象的产生。

一、事务特性(ACID)1)原子性 Atomicity

事务是一个原子操作,事务中的操作要么都执行,要么都不执行,任何一个SQL语句执行失败 ,那么执行成功的SQL也必须撤销,数据库状态应该退回到执行事务前的状态。

2)一致性 Consistency

一致性指事务操作前和操作后都必须满足业务规则约束,数据库的完整性约束没有被破坏。

3)隔离性 Isolation

隔离性也称为并发控制(concurrency control)、可串行化(serializability)、锁(locking)。多个事务之间的操作是相互隔离的,即该事务提交前对其他事务都不可见,通常使用锁来实现。

4)持久性 Durability

事务一旦提交,其结果是永久性的,即使发生宕机等故障,数据库也能将数据恢复。需要注意的是,持久性只能从事务本身的角度来保证结果的永久性,如果不是数据库本身发生故障,而是一些外部的原因,如RAID卡损坏、自然灾害等导致数据库发生问题,那么所有提交的数据可能会丢失。

二、事务并发问题

由于对数据库的操作不同,事务之间并不是顺序执行,而是并发执行的,事务并发可能会带来如下问题:

1)更新丢失 (Lost Update)

当多个事务操作同一行数据,由于每个事务都不知道其他事务的存在,更新数据时,一个事务的结果可能会被其他事务覆盖,发生丢失更新的问题。

2)脏读(Dirty Reads)

如果一个事务对数据进行了更新,但事务还没有提交,另一个事务读到了该事务没有提交的数据,那么如果第一个事务回滚,第二个事务读到的数据就是脏数据。

3)不可重复读(Non-Repeatable Reads)——针对同一数据

指在一个事务内两次读到的数据是不一样的。比如事务A读取某一数据,事务B修改了该数据,事务A为了对数据进行验证而再次读取该数据,便得到了不同的结果。

一种更易理解的说法是:在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问同一数据并修改数据。那么,在第一个事务的两次读数据之间,由于另一个事务的修改,第一个事务两次读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。

4)幻读(Phantom Reads) ——针对多条数据

幻读是指同样一个查询在整个事务过程中多次执行后,查询的结果集是不一样的,也就是事务中读取到了其他事务新增的数据,仿佛出现了幻象。不符合事务的隔离性,幻读针对的是多条记录。

三、事务隔离级别

并发事务带来的“更新丢失”问题,通常是可以完全避免的。防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决。

“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。

为了解决“隔离”与“并发”的矛盾,ANSI SQL标准定义了 4个事务隔离级别,分别是:

READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。

1)未提交读(READ UNCOMMITTED)

在一个事务中,可以读取到其他事务未提交的数据变化,这种隔离级别会造成脏读、不可重复读、幻读的问题。

2)已提交读(READ COMMITTED)

在一个事务中,可以读取到其他事务已经提交的数据变化,这种隔离级别会造成不可重复读、幻读的问题。

例如事务A读到了数据a, 事务B正好对数据a进行了更改,并且提交了。那么事务A再次读数据a时发现数据已改变。两次同样的查询可能会得到不一样的结果。

3)可重复读(REPEATABLE READ)

在一个事务中,直到事务结束前,都可以反复读取到事务刚开始时看到的数据,并一直不会发生变化,避免了脏读、不可重复读问题,但无法解决幻读问题。

4)可串行化(SERIALIZABLE)

这是最高的隔离级别,它强制事务串行执行,不会出现脏读、不可重复读、幻读问题。

5)隔离级别总览

隔离级别

数据一致性

脏读

不可重复读

幻读

未提交读

最低级别

已提交读

语句级别

可重复读

事务级别

可序列化

最高级别,事务级别

四、InnoDB存储引擎事务隔离级别

1)InnoDB存储引擎使用MVCC实现了REPEATABLE READ(可重复读),它的默认隔离级别也是REPEATABLE READ。

2)InnoDB存储引擎在REPEATABLE READ事务隔离级别下,使用Next-Key Lock的锁算法,避免了幻读的产生。InnoDB存储引擎已经能完全保证事务的隔离性要求,即达到SQL标准的SERIALIZABLE隔离级别。

实现事务隔离的方式,基本上可分为以下两种。

  • 一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改。
  • 另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。

下面分别介绍一下InnoDB存储引擎的锁和MVCC。

五、InnoDB锁1)锁的介绍

MySQL 不同的存储引擎支持不同的锁机制,有3种粒度的锁:

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

InnoDB存储引擎既支持行级锁,也支持表级锁,但默认情况下是采用行级锁。

InnoDB实现了两种类型的行锁:共享锁、排他锁。

共享锁(S)——行锁
  • 也称为读锁,允许一个事务对某一行加共享锁,其他事务仍可以再加共享锁读取此资源。多个共享锁可以共存,但共享锁和排他锁不能共存。
  • 对于普通SELECT语句,InnoDB实现时不会加任何锁。
排他锁(X)——行锁
  • 也称为写锁,允许获得排他锁的事务更新数据,其他事务不能追加相同数据集的共享锁和排他锁。
  • 对于UPDATE、DELETE和INSERT语句, InnoDB会自动给涉及数据集加排他锁。

为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks):意向共享锁、意向排他锁。这两种意向锁都是表锁,意向锁的主要作用是处理行锁和表锁之间的矛盾。

意向共享锁(IS)——表锁
  • 表示事务想要获得一张表中某几行的共享锁。
  • 事务在给一个数据行加共享锁前必须先加IS锁。
意向排他锁(IX)——表锁
  • 表示事务想要获得一张表中某几行的排他锁。
  • 事务在给一个数据行加排他锁前必须先加IX锁。
  • 一个表有IX锁,表示有事务正在锁定某行或者试图锁定某行。

举例说明:

事务A在更新行数据之前,先在表级别上加意向锁,再加X锁;加意向锁是表示此表某一行被加了X锁; 当事务B要对这个表加S锁,如果表中数据很多,那么事务B需要逐行检查表是否能加S锁,这样会很耗时;如果此时在表级别上有意向锁,那么,事务B先检查该表上是否存在意向锁,存在的意向锁是否与自己准备加的锁冲突,如果有冲突,则等待直到事务A释放,而无须逐条记录去检测,这样速度会很快。

InnoDB行锁模式兼容性

X锁

IX锁

S锁

IS锁

X锁

冲突

冲突

冲突

冲突

IX锁

冲突

兼容

冲突

兼容

S锁

冲突

冲突

兼容

兼容

IS锁

冲突

兼容

兼容

兼容

  • 如果一个事务请求的锁模式与当前的锁兼容,InnoDB 就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
  • 意向锁之间是兼容的,不会产生冲突。
  • 意向锁是InnoDB自动加的,不需用户干预。在加行锁之前,由InnoDB存储引擎加上表的IS或IX锁。
  • 意向锁存在的意义是为了更高效的获取表锁。
2)行锁的实现

InnoDB行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来对记录加锁。InnoDB存储引擎有3种行锁算法

Record lock

单个行记录上的锁,通过对索引项加锁实现,锁住的是索引,而不是记录本身。

  • 在不通过索引条件查询时,InnoDB会锁定表中的所有记录,实际效果跟表锁一样。
  • 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行。
Gap lock
  • 间隙锁。对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。锁定的是一个范围,包括间隙,但不包含记录本身。
Next-key lock
  • 是Record lock和Gap lock的组合,锁定一个范围,并且锁定记录本身。
  • InnoDB使用Next-Key锁的目的是为了防止幻读,锁定条件范围内的数据,保证事务未提交时多次查到的数据是相同的。
  • Next-key lock加锁机制也会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
六、MVCC多版本并发控制

数据库并发时,一般是读写并发、写-写并发。MVCC主要用于处理读-写冲突,MVCC可以做到不加锁,非阻塞并发读。

基本原理

MVCC是通过保存某个时间点的快照来实现的,快照数据是指该行之前的历史版本数据。 InnoDB为每次修改保存一个版本,版本与事务时间戳关联,每行记录可能有多个版本。读取快照数据是不需要上锁的,因为没有事务需要对历史数据进行修改。

对于不同的事务隔离级别,读取到的快照数据是不同的。

  • READ COMMITTED(已提交读):在一个事务中,对于快照数据,总是读取被锁定行的最新一份快照数据。
  • REPEATABLE READ(可重复读):在一个事务中,对于快照数据,总是读取事务开始时的行数据版本。
MVCC的好处
  • 在并发读写数据库时,使用快照读可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。
  • 如下图所示,在读数据时,如果读取的行被加了行锁,这时读操作不会去等待行锁的释放,而是会去读取行的快照数据。

mysql事务特性以及隔离级别(MySQL事务隔离级别与可重复读的实现)(3)

InnoDB非锁定的一致性读

参考书籍

《MySQL技术内幕:InnoDB存储引擎(第2版)》

《MySQL技术内幕——SQL编程》

《深入浅出MySQL:数据库开发、优化与管理维护(第2版)》

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页