MySQL事务隔离机制 -- 必须说透

Posted 天罡gg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL事务隔离机制 -- 必须说透相关的知识,希望对你有一定的参考价值。

文章目录


前言

如何控制并发是数据库领域中非常重要的问题之一,mysql为了解决并发带来的问题,设计了事务隔离机制、锁机制、MVCC机制等,用一整套机制来解决并发问题,本文主要介绍事务隔离机制。


一、什么是数据库事务

事务transaction(简写tx),在数据库中,事务是指一组逻辑操作,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与结束之间执行的全部数据库操作组成

事务具有以下4大特性,简称ACID:

1、原子性(Atomicity)
事务是一个原子操作单元,是不可分割的最小工作单元,就像化学中的原子,其对数据的修改,要么全都执行,要么全都不执行

2、一致性(Consistent)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态。
一致性也称作是完整性,就是说事务的执行不能破坏数据库的一致性,在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性

3、隔离性(Isolation)
数据库提供一定的隔离机制,当多个事务并发访问时,保证事务在不受外部并发操作影响的“独立”环境执行,当然了,不同的隔离级别决定了有多“独立”。

4、持久性(Durable)
事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

小结:原子性,持久性大家基本都能秒懂,隔离性也还好大家稍微想想都能理解,就是接下来要讲的隔离级别,但对一致性很多人被官方概念绕住了,其实就是在多事务并发时要保证数据的一致和完整,不理解的话先看下面的问题。


二、事务并发带来的4类问题

1、脏读(Dirty Reads)
事务A读取到了事务B修改但尚未提交的数据,然后事务B回滚了,因此事务A基于此数据做的操作都是无效的,就是读到了“脏”数据(实际上不应该存在的数据),所以叫脏读。

2、脏写或丢失更新(Lost Update)
第一类丢失更新:撤销rollback一个事务时,把其他事务已经提交更新的数据回滚掉了。
第二类丢失更新:提交commit一个事务时,把其他事务已经提交更新的数据覆盖掉了。

3、不可重复读(Non-Repeatable Reads)
在事务A中,按相同的条件执行查询过的sql,返回结果发生了修改或被删除了,这种现象就叫做“不可重复读”。

4、幻读(Phantom Reads)
在事务A中,按相同的条件执行查询过的sql,返回结果中有新数据(是其它事务插入的),就好像发生了幻觉一样,这就叫“幻读”。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改或删除,幻读侧重于新增。就这个结论网上很多博主写的是不可重复读侧重于修改,幻读侧重于新增或删除 (包括一些知名博主),大家请注意删除不属于幻读,我会在第四段演示时求证。另外后面我会单独安排一篇介绍MVCC机制,到时你自然会明白这里的原因。


三、事务4种隔离级别

在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些是在事务内和事务间可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。

每种隔离级别可能出现的问题如下:

事务隔离级别脏读不可重复读幻读
读未提交(RU)可能可能可能
读已提交(RC)不可能可能可能
可重复读(RR)不可能不可能可能
串行化(Serializable)不可能不可能不可能

1、读未提交(Read uncommitted,RU)

事务中的修改,即使没有提交,对其他事务也都是可见的,由于可能造成脏读,所以在实际应用中一般很少使用。

2、读已提交(Read committed,RC)

事务A修改并提交的数据才会对其它事务可见,所以解决脏读的问题。大多数数据库系统的默认隔离级别都是读已提交(像Oracle、PostgreSQL、SqlServer,但Mysql不是)。

3、可重复读(Repeatable read,RR)
Mysql默认的事务隔离级别。它保证了在同一个事务中多次读取相同记录的结果是一致的,所以解决了脏读和不可重复读问题,但未彻底解决幻读。

4、串行化(Serializable)

可串行化是最高的隔离级别。它通过强制事务串行执行,避免了前面所说的脏读,不可重复读和幻读问题。简单来说,可串行化会在读取的每一行数据上都加上锁,所以可能导致大量的超时和锁争用问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑用该级别。


四、Mysql演示4种隔离级别

在MySQL中,InnoDB引擎支持事务,MyISAM引擎不支持事务,所以本文实验的测试环境:Windows 10 + MySQL5.7 + InnoDB。
Mysql5.7可以通过命令select @@tx_isolation;语句来查看:

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+

设置4种隔离级别的命令:

set tx_isolation='read-uncommitted';
set tx_isolation='read-committed';
set tx_isolation='repeatable-read';
set tx_isolation='serializable';

开始事务:

begin;

提交事务:

commit;

回滚事务:

rollback;

1、读未提交(RU)

set tx_isolation='read-uncommitted';
select @@tx_isolation;
begin;

验证脏读

2、读已提交(RC)

set tx_isolation='read-committed';
select @@tx_isolation;
begin;

验证解决脏读,存在不可重复读


验证幻读

3、可重复读(RR)

set tx_isolation='repeatable-read';
select @@tx_isolation;
begin;

验证解决脏读和不可重复读
上面说的不可重复读侧重于修改或删除,求证请看下面的截图,

验证幻读
Mysql的RR级别在一定程度上解决了幻读问题,但不够彻底,至于为什么这么说?还是那句话,得深入理解MVCC机制,这个会单独安排一篇,敬请期待!

4、串行化(Serializable)

set tx_isolation='serializable';
select @@tx_isolation;
begin;


如果长时间阻塞,最后会锁等待超时,抛出异常:ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
换成是在事务A执行,结果也是一样会阻塞,直到其它事务完成或超时抛出同样的异常。
对于update也是同样的道理,详细如下图:


总结

通过本文我们已经详细说明了:

  1. 事务4大特性:ACID
  2. 事务并发带来的4类问题:脏读、脏写、不可重复读、幻读
  3. 事务的4种隔离级别:读未提交、读已提交、可重复读、串行化
  4. Mysql提供的4种隔离级别演示

剩下的MySQL锁机制和MVCC机制,我们下文见!
如果感觉不错,请关注我 天罡gg 分享更多干货,主页地址:https://blog.csdn.net/scm_2008
大家的「收藏 + 点赞 + 关注 + 评论」就是我创作的最大动力!


相关博文:
Mysql事务隔离机制
MySql事务隔离机制
MySQL事务隔离级别详解
透彻解读mysql的可重复读、幻读及实现原理


一文带你理解脏读,幻读,不可重复读与mysql的锁,事务隔离机制

首先说一下数据库事务的四大特性

1 ACID

事务的四大特性是ACID(不是"酸"....)

(1) A:原子性(Atomicity)

原子性指的是事务要么完全执行,要么完全不执行.

(2) C:一致性(Consistency)

事务完成时,数据必须处于一致的状态.若事务执行途中出错,会回滚到之前的事务没有执行前的状态,这样数据就处于一致的状态.若事务出错后没有回滚,部分修改的内容写入到了数据库中,这时数据就是不一致的状态.

(3) I:隔离性(Isolation)

同时处理多个事务时,一个事务的执行不能被另一个事务所干扰,事务的内部操作与其他并发事务隔离.

(4) D:持久性(Durability)

事务提交后,对数据的修改是永久性的.

2 Mysql的锁

Mysql的锁其实可以按很多种形式分类:

  • 按加锁机制分,可分为乐观锁与悲观锁.
  • 按兼容性来分,可分为X锁与S锁.
  • 按锁粒度分,可分为表锁,行锁,页锁.
  • 按锁模式分,可分为记录锁,gap锁,next-key锁,意向锁,插入意向锁.

这里主要讨论S锁,X锁,乐观锁与悲观锁.

(1) S锁与X锁

S锁与X锁是InnoDB引擎实现的两种标准行锁机制.查看默认引擎可使用

show variables like ‘%storage_engine%‘;

作者的mysql版本为8.0.17,结果如下:

技术图片

先建好测试库与测试表,很简单,表就两个字段.

create database test;
use test;
create table a
(
id int primary key auto_increment,
money int
);

技术图片

Ⅰ.S锁

S锁也叫共享锁,读锁,数据只能被读取不能被修改.
玩一下,上锁!

lock table a read;

然后.....

技术图片

只能读不能改,删,也不能增.

Ⅱ.X锁

X锁也叫排他锁,写锁,一个事务对表加锁后,其他事务就不能对其进行加锁与增删查改操作.

设置手动提交,开启事务,上X锁.

set autocmmmit=0;
start transaction;
lock table a write;

技术图片

在开启另一个事务,使用select语句.

set autocommit=0;
start transaction;
select * from a;

技术图片

这里是阻塞select操作,因为一直都没释放X锁.

技术图片

同样也不能再加锁,也是阻塞中.

技术图片

回到原来那个加锁的事务,嗯,什么事也没有,正常读写.

技术图片

释放锁后:

unlock table;

技术图片

技术图片

在另一个事务中可以看到中断时间.

(2) 乐观锁与悲观锁

Ⅰ.乐观锁

乐观锁就是总是假设是最好的情况,每次去操作的时候都不会上锁,但在更新时会判断有没有其他操作去更新这个数据,是一种宽松的加锁机制.
mysql本身没有提供乐观锁的支持,需要自己来实现,常用的方法有版本控制和时间戳控制两种.

  • 版本控制
    版本控制就是为表增加一个version字段,读取数据时连同这个version字段一起读出来,之后进行更新操作,版本号加1,再将提交的数据的版本号与数据库中的版本号进行比较,若提交的数据的版本号大于数据库中的版本号才会进行更新.

    举个例子,假设此时version=1,A进行操作,更新数据后version=2,与此同时B也进行操作,更新数据后version=2,A先完成操作,率先将数据库中的version设置为2,此时B提交,B的version与数据库中的version一样,不接受B的提交.

  • 时间戳控制
    时间戳控制与版本控制差不多,把version字段改为timestamp字段
    还有一种实现方法叫CAS算法,这个作者不怎么了解,有兴趣可以自行搜索.

Ⅱ.悲观锁

悲观锁就是总是假设最坏的情况,在整个数据处理状态中数据处于锁定状态,悲观锁的实现往往依靠数据库的锁机制.每次在拿到数据前都会上锁.
mysql在调用一些语句时会上悲观锁,如(先关闭自动提交,开启事务):

set autocommit=0;
start transaction;

技术图片

两个事务都这样操作,然后其中一个事务输入:

select * from a where xxx for update;

技术图片

在另一事务也这样输入:

技术图片

这时语句会被阻塞,直到上锁的那个事务commit(解开悲观锁).

技术图片

技术图片

在另一事务中可以看到这个事务被阻塞了2.81s.

*** lock in share mode.

也会加上悲观锁.

4 脏读,幻读,不可重复读与两类丢失更新

(1) 脏读

脏读是指一个事务读取到了另一事务未提交的数据,造成select前后数据不一致.

比如事务A修改了一些数据,但没有提交,此时事务B却读取了,这时事务B就形成了脏读,一般事务A的后续操作是回滚,事务B读取到了临时数值.

事务A 事务B
开始事务 开始事务
更新X,旧值X=1,新值X=2 ?
? 读取X,X=2(脏读)
回滚X=1 ?
结束事务(X=1) 结束事务

(2) 幻读

幻读是指并不是指同一个事务执行两次相同的select语句得到的结果不同,而是指select时不存在某记录,但准备插入时发现此记录已存在,无法插入,这就产生了幻读.

事务A 事务B
开始事务 开始事务
select某个数据为空,准备插入一个新数据 ?
? 插入一个新数据
? 提交,结束事务
插入数据,发现插入失败,由于事务B已插入相同数据 ?
结束事务 ?

(3) 不可重复读

不可重复读指一个事务读取到了另一事务已提交的数据,造成select前后数据不一致.
比如事务A修改了一些数据并且提交了,此时事务B却读取了,这时事务B就形成了不可重复读.

事务A 事务B
开始事务 开始事务
读取X=1 读取X=1
更新X=2 ?
提交,结束事务 ?
? 读取X=2
? 结束事务

(4) 第一类丢失更新

第一类丢失更新就是两个事务同时更新一个数据,一个事务更新完毕并提交后,另一个事务回滚,造成提交的更新丢失.

事务A 事务B
开始事务 开始事务
读取X=1 读取X=1
修改X=2 修改X=3
? 提交,结束事务
回滚 ?
结束事务(X=1) X=1,X本应为提交的3

(5) 第二类丢失更新

第二类丢失更新就是两个事务同时更新一个数据,先更新的事务提交的数据会被后更新的事务提交的数据覆盖,即先更新的事务提交的数据丢失.

事务A 事务B
开始事务 开始事务
读取X=1 读取X=1
更新X=2 ?
提交事务,X=2,结束 ?
? 更新X=3
? 提交事务,X=3,事务A的更新丢失,结束

5 封锁协议与隔离级别

封锁协议就是在用X锁或S锁时制定的一些规则,比如锁的持续时间,锁的加锁时间等.不同的封锁协议对应不同的隔离级别.事务的隔离级别一共有4种,由低到高分别是Read uncommitted,Read committed,Repeatable read,Serializable,分别对应的相应的封锁协议等级.

(1) 一级封锁协议

一级封锁协议对应的是Read uncommitted隔离级别,Read uncommitted,读未提交,一个事务可以读取另一个事务未提交的数据,这是最低的级别.一级封锁协议本质上是在事务修改数据之前加上X锁,直到事务结束后才释放,事务结束包括正常结束(commit)与非正常结束(rollback).

一级封锁协议不会造成更新丢失,但可能引发脏读,幻读,不可重复读.
设置手动提交与事务隔离等级为read uncommited,并开启事务(注意要先设置事务等级再开启事务).

set autocommit=0;
set session transaction isolation level read uncommitted;
start transaction;

技术图片

(中间有一行打多了一个t可以忽略.....)

a.引发脏读

在一个事务中修改表中的值,不提交,另一个事务可以select到未提交的值.

技术图片

技术图片

出现了脏读.

b.引发幻读

在一个事务中插入一条数据,提交.

技术图片

另一事务中select时没有,准备insert,但是insert时却提示已经存在.引发幻读.

技术图片

c.引发不可重复读

未操作提交前:

技术图片

另一事务修改并提交:

技术图片

再次读:

技术图片

引发不可重复读.

(2) 二级封锁协议

二级封锁协议本质上在一级协议的基础上(在修改数据时加X锁),在读数据时加上S锁,读完后立即释放S锁,可以避免脏读.但有可能出现不可重复读与幻读.二级封锁协议对应的是Read committed与Repeatable Read隔离级别.

先设置隔离等级

set session transaction isolation level read committed;

Ⅰ.Read committed

Read committed,读提交,读提交可以避免脏读,但可能出现幻读与不可重复读.

a.避免脏读

开启一个事务并更新值,在这个事务中money=100(更新后)

技术图片

另一事务中money为未更新前的值,这就避免了脏读.

技术图片

注意,事实上脏读在read committed隔离级别下是不被允许的,但是mysql不会阻塞查询,而是返回未修改之前数据的备份,这种机制叫MVCC机制(多版本并发控制).

b.引发幻读

在一个事务中插入数据并提交.

技术图片

另一事务中不能插入"不存在"的数据,出现幻读.

技术图片

c.引发不可重复读

事务修改并提交前:

技术图片

事务修改并提交:

技术图片

出现不可重复读.

技术图片

Ⅱ.Repeatable read

Repeatable read比Read committed严格一点,是Mysql的默认级别,读取过程更多地受到MVCC影响,可防止不可重复读与脏读,但仍有可能出现幻读.

a.避免脏读

在一个事务中修改数据,不提交.

技术图片

另一事务中两次select的结果都不变,没有出现脏读.

技术图片

b.避免不可重复读

一个事务修改数据并提交.

技术图片

另一事务中select的结果没有发生改变,即没有出现不可重复读.

技术图片

c.引发幻读

同理,一个事务插入一条数据并提交.

技术图片

另一个事务插入时出现幻读.

技术图片

(3) 三级封锁协议

三级封锁协议,在一级封锁协议的基础上(修改时加X锁),读数据时加上S锁(与二级类似),但是直到事务结束后才释放S锁,可以避免幻读,脏读与不可重复读.三级封锁协议对应的隔离级别是Serializable.

先设置Serializable隔离级别

set session transaction isolation level serializable

a.避免脏读

设置事务隔离等级后开启事务并update,发现堵塞.从而避免了脏读.

技术图片

b.避免幻读

插入时直接阻塞,避免了幻读.

技术图片

c.避免不可重复读

在脏读的例子中可以知道,update会被堵塞,都不能提交事务,因此也避免了不可重复读.

6 两段锁协议

事务必须分为两个阶段对数据进行加锁与解锁,两端锁协议叫2PL(不是2PC),所有的加锁都在解锁之前进行.

(1) 加锁

加锁会在更新或者

select *** for update
*** lock in share mode

时进行

(2) 解锁

解锁在事务结束时进行,事务结束包括rollback与commit.

参考链接
1:ACID1

2:ACID2

3:mysql的锁1

4:乐观锁与悲观锁1

5:乐观锁与悲观锁2

6:乐观锁与悲观锁3

7:mysql修改事务隔离等级

8:mysql三级封锁与二段锁

9:数据库封锁协议

10:mysql事务隔离机制1

11:mysql事务隔离机制2

12:mysql幻读

13:mysql脏读,不可重复读与幻读

14:mysql两段锁1

15:mysql两段锁2

以上是关于MySQL事务隔离机制 -- 必须说透的主要内容,如果未能解决你的问题,请参考以下文章

一文带你理解脏读,幻读,不可重复读与mysql的锁,事务隔离机制

mysql事务机制

细品mysql的事务隔离机制

深入了解MySQL的隔离级别和锁机制

深入剖析Mysql事务和Spring事务

MySQL 事务的默认隔离级别是啥?可以解决幻读问题么?