【数据库篇】MySQL 事务篇

事务ACID的概念

原子性(Atomicity):原子性是指一个事务中的操作,要么全部成功,要么全部失败,如果失败,就回滚到事务开始前的状态。

一致性(Consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。那转账举栗子,A账户和B账户之间相互转账,无论如何操作,A、B账户的总金额都必须是不变的。

隔离性(Isolation):隔离性是当多个用户 并发的 访问数据库时,如果操作同一张表,数据库则为每一个用户都开启一个事务,且事务之间互不干扰,也就是说事务之间的并发是隔离的。再举个栗子,现有两个并发的事务T1和T2,T1要么在T2开始前执行,要么在T2结束后执行,如果T1先执行,那T2就在T1结束后在执行。关于数据的隔离性级别,将在后文讲到。

持久性(Durability):持久性就是指如果事务一旦被提交,数据库中数据的改变就是永久性的,即使断电或者宕机的情况下,也不会丢失提交的事务操作。

在这里插入图片描述

四种隔离级别

在这里插入图片描述

SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。

mysql的4种事务隔离级别,如下所示:

1、未提交读(Read Uncommitted):

允许脏读,也就是可能读取到其他会话中未提交事务修改的数据

这种事务隔离级别下,select语句不加锁,也不是快照读。

--原数据
--id    name
--1     lisi
 
--事务1
START TRANSACTION;
updata t_table set name = 'wangwu' where id = 1;    --此时事务2查询id = 1
ROLLBACK--事务2
select * from t_table where id = 1;        --查询到 id = 1, name = 'wangwu'

2、提交读(不可重复读)(Read Committed):

--原数据
--id    name
--1     lisi
 
--事务1
select * from t_table where id = 1;    -- 查询到 id = 1, name = list, 事务2在此时提交
select * from t_table where id = 1;    -- 查询到 id = 1, name = wangwu
 
--事务2
start transaction;
update t_table set name = 'wangwu' where id = 1;
COMMIT;
 

能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)

该级别下是通过快照读来防止读脏的。因为在该级别下的快照读总是能读到最新的行数据快照,当然,必须是已提交事务写入的,所以可能产生不可重复读。

而且如果是不上锁的select,可能产生不可重复读。

会产生幻读每次select都生成一个快照读。

3、可重复读(Repeated Read):

可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读

在该级别下

  • 通过快照读以及锁定区间来实现避免产生幻读和不可重复读;
  • 某个事务首次read记录的时间为T,未来不会读取到T时间之后已提交事务写入的记录,以保证连续相同的read读到相同的结果集,这可以防止不可重复读;
  • RR下是通过间隙锁,临键锁来解决幻影读问题;

4、串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

隔离性1-4 由低到高, 而并发性相反

  • 脏读、不可重复读、幻读:
    也许有很多读者会对上述隔离级别中提及到的 脏读、不可重复读、幻读 的理解有点吃力,我在这里尝试使用通俗的方式来解释这三种语义:

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

在这里插入图片描述

  • 不可重复读:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。

    也就是说,当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配,也就照应了不可重复读的语义。

幻读:一个事务读取到了别的事务插入的数据。

--原数据
--id    name
--1     lisi
 
--事务1
select * from t_table where id = 2;    --返回NULL,此时事务2提交
select * from t_table where id = 2;    --返回id = 2, name = wangwu
 
 
--事务2
insert into t_table values(2,'wangwu');
COMMIT;

在这里插入图片描述

解决:对于快照读,InnoDB 使用 MVCC 解决幻读,对于当前读,InnoDB 通过 gap locks 或 next-key locks 解决幻读。

不可重复读: 在一个事务中多次读取同一个数据时,结果出现不一致。

不可重复读和幻读比较:
两者有些相似,但是不可重复读针对的是update或delete,幻读针对的insert。
在这里插入图片描述

解决:在 MySQL InnoDB 中,Repeatable Read 隔离级别使用 MVCC 来解决不可重复读问题。

事务是如何实现?

在这里插入图片描述

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

原子性

  • 事务的原子性是通过 undo log 来实现的,(undo log 保存了事物发生之前的数据的一个版本,同时提供多版本并发控制下的读(mvcc 非锁定读))

持久性

  • 事务的持久性性是通过 redo log 来实现的,redolog是在事务开始与提交前就产生了

隔离性

  • 事务的隔离性是通过 (读写锁+MVCC)来实现的

在这里插入图片描述

一致性

而事务的终极大 boss 一致性是通过原子性,持久性,隔离性来实现的。

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

总之,ACID只是个概念,事务最终目的是要保障数据的可靠性,一致性。

一、Redo log
重做日志用来实现事务的持久性,即D特性。它由两部分组成:

①内存中的重做日志缓冲
②重做日志文件

二、Undo log
第二部分是Undo log,它可以实现如下两个功能:
1.实现事务回滚
2.实现MVCC

MVCC

MVCC概念

MVCC(Multi-Version Concurrency Control)即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。

MVCC使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能。

MVCC 为数据库解决什么问题?

在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能

同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

MVCC基本特征

  1. 每行数据都存在一个版本,每次数据更新时都更新该版本。

  2. 修改时Copy出当前版本随意修改,各个事务之间无干扰。

  3. 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)。

补充

1.MVCC手段只适用于Msyql隔离级别中的读已提交(Read committed)和可重复读(Repeatable Read);

2.Read uncimmitted由于存在脏读,即能读到未提交事务的数据行,所以不适用MVCC;

原因是MVCC的创建版本和删除版本只要在事务提交后才会产生。

3.串行化由于是会对所涉及到的表加锁,并非行锁,自然也就不存在行的版本控制问题;

4.通过以上总结,可知,MVCC主要作用于事务性的,有行锁控制的数据库模型。

实现原理

MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View(读视图) 来实现的。

3个隐式字段

在这里插入图片描述

undo log 数据链表记录

在这里插入图片描述

不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log 的链首就是最新的旧记录,链尾就是最早的旧记录.

Read View(读视图)

在这里插入图片描述

准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念

RC、RR innodb快照读有什么区别

在这里插入图片描述

当前读、 快照读

当前读

当前读的实现方式:next-key锁(行记录锁+Gap间隙锁)

select...lock in share mode (共享读锁)
select...for update
update , delete , insert

当前读, 读取的是最新版本, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题

例如,假设要update一条记录,但是另一个事务已经delete这条数据并且commit了,如果不加锁就会产生冲突。所以update的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。

快照读

像不加锁的select * from 操作就是快照读,即不加锁的非阻塞读,不涉及其他锁之间的冲突;
快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;
既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

快照读的实现方式:undolog和多版本并发控制MVCC

对于快照读,InnoDB 使用 MVCC 解决幻读,

单纯的select操作,不包括上述 select … lock in share mode, select … for update。

  • Read Committed隔离级别:每次select都生成一个快照读。

  • Read Repeatable隔离级别:开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读。

说白了MVCC就是为了实现读(select)-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。


版权声明:本文为weixin_43746433原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。