MySQL事务隔离(读现象/锁机制/事务隔离级别/MVCC)

目录:

一、 数据库的读现象

二、 MySQL锁机制

(1)行级锁、表级锁、页级锁
(2)共享锁、排它锁
(3)悲观锁、乐观锁

三、 事务隔离级别

四、 MVCC

(1)MVCC的引出
(2)InnoDB中的MVCC是如何实现的


一、数据库的读现象:

“读现象”是指在多个事务并发执行时,在读取数据方面可能会遇到的问题,其中包括:脏读、不可重复读、幻读。

(注:如果事务通过加锁等方式实现了“ACID”后是不会出现读数据问题的,只有当事务的隔离做的不好或者人为的修改事务隔离级别时才可能会在读取数据时出现这些问题。)

  1. 脏读:

    可以理解为 “read uncommitted”,事务B还没提交,事务A就已经读到了。
    这可能造成事务B后面又 rollback 回退了修改操作,导致事务A前面读到的数据是脏数据。

  2. 不可重复读:

    可以理解为 “read committed”,事务B提交后事务A就读到了,这可能导致事务A执行过程中两次读到的结果不一致。
    (事务A的两次读分别在事务B commit 提交的前后,就会造成不可重复读)

  3. 幻读:

    可以理解为不可重复读的特殊情况,是指在事务A两次读的前后,事务B向A读的范围中插入了新数据并提交,
    导致事务A在第二次读时读到了第一次没有的数据。

PS: select查询范围的SQL语句如何写:(两种写法)

mysql> select * from runoob_tb1 where runoob_id>=1 and runoob_id<=4;
mysql> select * from runoob_tb1 where runoob_id between 1 and 4; //包含1和4

二、MySQL锁机制:

因为存在多任务并发访问数据库的情况,所以会涉及到加锁的问题。

1. 按锁的粒度划分:行级锁、表级锁、页级锁

(1) 行级锁:

行级锁是MySQL中粒度最小的锁,表示只针对当前操作的行进行加锁。

优点:减少数据库操作的冲突,提高并发度;
缺点:加锁的开销最大。

行级锁分为共享锁(select lock in share mode)和排他锁(update,delete,select ... for update)。

(2) 表级锁:

表级锁是MySQL中粒度最大的一种锁,表示对当前操作的整张表加锁。

优点:实现简单,资源开销小,被大部分MySQL引擎支持;
缺点:并发度低。

表级锁分为表共享读锁(共享锁)和 表独占写锁(排它锁)。

注:InnoDB不仅支持行级锁,而且也支持表级锁。

(3) 页级锁:

页级锁是MySQL中粒度介于表级锁和行级锁之间的一种锁。
并发度与资源开销也是介于二者之间。

当一个表(table)较大时,可能会占多个页(操作系统访问内存的单位是页),所以页级锁就是锁住操作行附近的一些数据。

BDB存储引擎支持页级锁。

2. 按锁的级别划分:共享锁、排他锁

(1) 共享锁:

用于select,读时加锁,防止其他事务修改本事务正在读取的数据。

“同一个共享锁有多把钥匙”,一个事务将某个数据加共享锁之后,其他事务仍然可以对其加共享锁,但是不能加排他锁。

注:

数据被加了共享锁之后就无法加排他锁,排他锁操作将进入阻塞。

共享锁使用举例:

mysql> start transaction;
	-> select * from runoob_tb1 where runoob_id=1 lock in share mode;
	//使用时在select后加“lock in share mode”即可
	
//事务B同时:
mysql> start transaction;
	-> update runoob_tb1 set runoob_title="hahaha" where runoob_id=1;
	//update操作进入阻塞,直至事务A commit 或 update超时

//事务C同时:
mysql> start transaction:
	-> select * from runoob_tb1 where runoob_id=1;
	//正常执行,共享锁不影响读

注:

如果没有“lock in share mode” 的关键字,InnoDB对于多事务并发的select读操作默认的处理方式是MVCC,另一个事务的update写操作不会阻塞。

(2) 排他锁:

用于update、delete、insert,写时加锁,防止其他事务在当前事务读写时操作同一份数据。

“排它锁的钥匙只有一把”,update、delete、insert这些操作都是自动加排它锁,不像select那样需要显式加共享锁。

3. 按使用方式划分:悲观锁、乐观锁

(1)悲观锁: “先上锁,再操作”

悲观,即指事务悲观的认为它在操作数据时大概率会出现数据冲突,因此必须要先获得锁,才能继续向下操作。

悲观锁的开销较大,因为每次操作数据前都要先上锁。

注:
共享锁和排它锁都是悲观锁,都要在操作数据(读、写)前上锁。

(2)乐观锁: “先操作,再上锁”

乐观,即指事务乐观的认为它在操作数据时大概率不会出现数据冲突,表中增加一个【Version版本】字段,当事务取出待操作数据时,对该字段加1,再将修改后的数据写入到数据库前,先对Version版本字段进行比对,如果相同,则更新;如果不同,则取消更新。

为什么MyISAM不支持事务,InnoDB支持事务:

因为MyISAM采用的是表级锁,InnoDB采用的行级锁。
如果使用表级锁,那么对于读、写操作都会将整张表锁住,select、update等读写操作只能串行的执行,并发效率不高。


三、事务隔离级别:

1. 四种事务隔离级别会出现哪些读现象:

事务隔离级别 脏读 不可重复读 幻读
读未提交 (read uncommitted)
不可重复读(read committed,RC)
可重复读(repeatable read,RR)
串行化(serializable)

2. 如何设置MySQL的隔离级别:

mysql> set session transaction isolation level read uncommitted; //读未提交
	-> set session transaction isolation level read committed; //不可重复读
	-> set session transaction isolation level repeatable read; //可重复读
	-> set session transaction isolation level serializable; //串行化

四、MVCC:

1. MVCC的引出:从事务,到并发事务

MySQL中有一种应用场景就是需要保证一组SQL语句的原子性操作,比如银行账户余额的操作,要么全部执行,要么全部不执行,即事务。

如果是单个客户机对服务器的访问,那么这种要求很容易实现,只需要一个 undo log,客户机 start transaction 开启事务后,所有操作都保存在 undo log 中,等客户机 commit 提交了,再将操作全部一次性提交到数据库中;如果客户机 rollback 回滚了,则删除 undo log 中的记录即可。

但是,当问题变得复杂,开始有多个客户机同时访问服务器,并同时操作同一张表时,这时还要保证事务的ACID特性就变得较为复杂。

  • 首先,多事务并发执行时,可能会出现如下三种场景:
    (1)读 - 读 : 多个事务同时读同一份数据,不管它们怎么读,都不会出现数据冲突,因此不需要做任何的并发处理;
    (2)写 - 写 : 必须加锁,排它锁,多个事务同时 update/delete/insert 操作同一行数据时会阻塞,等待锁释放(即上锁的事务提交或回滚),一个事务提交后,另一个事务在update时读到的是最新结果,即当前读
    (3)读 - 写 : 读写的情况比较复杂,不同的事务隔离级别会出现不同的读写结果。对于InnoDB,默认的隔离级别是RR,实现方式是 MVCC + gap lock,MVCC解决不可重复读(快照读),gap lock解决幻读。

  • 四种事务隔离级别是如何实现的:

    (1)对于第一种级别 “读未提交”,相当于不需要对读写操作做并发处理;
    (2)对于第四种级别 “串行化”,相当于所有的读、写操作都需要加锁,操作指令串行执行,没有并发可言。

    这两种隔离级别的逻辑都比较简单,复杂的是 RC 和 RR 这两种隔离级别的处理。

    (3)RC:

    RC级别下解决的是脏读,但是解决不了不可重复读和幻读。

    数据读一致性方案有两种:MVCC和LBCC

    RC隔离级别下采用的读一致性方案仍然是MVCC,只是在MVCC的具体操作中与RR级别有些不同。

    关于MVCC与LBCC:

    数据读一致性方案:
    ① LBCC:
    LBCC = Lock Based Concurrency Control,基于锁的事务并发控制。

    我既然要保证前后两次读取的数据一致,那么我读取数据的时候,锁定我要操作的数据,不允许其他事务修改就行了。

    如果仅仅是基于锁实现事务隔离,一个事务读取的时候不允许其他事务修改,那就意味着不支持并发的读写操作,而我们大多数应用都是读多写少的,这样会极大的影响操作数据的效率。

    ② MVCC:
    MVCC = Multi Version Concurrency Control,基于多版本的并发控制。

    MVCC的核心思想是:我可以查到在我这个事务开始之前已经存在的数据,即使它在后面被修改或者删除了。在我这个事务之后新增的数据,我是查不到的。

    (4)RR:

    RR要求解决不可重复读。

    SQL标准只要求RR解决不可重复读,但是由于MySQL采用了 gap lock(next-key lock),所以实际上MySQL的RR隔离级别连幻读也解决了。InnoDB通过MVCC解决不可重复读。

    除了MySQL默认采用RR隔离级别之外,其他几个大数据库都是默认采用RC隔离级别。

2. InnoDB中的MVCC是如何实现的:

MVCC只是一种概念,就如同通信协议一样,约定了要实现的效果,至于如何去实现MVCC,不同的存储引擎有各自不同的实现方式。

要实现MVCC,也就是要实现无锁达到RR的事务隔离级别,InnoDB中主要依靠以下三点实现MVCC:

隐藏字段 + undo log + read view ;

(1)表中的4个隐藏字段:

DB_TRX_ID:

6 Byte,最近修改(修改/插入)事务ID:记录创建这行数据/最后一次修改这行数据的事务ID;
(TRX可能是Transaction的意思)

DB_ROLL_POINTER:

7 Byte,回滚指针,指向这条记录的上一个版本;

DB_ROW_ID:

6 Byte,隐藏主键(隐含的自增ID),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引;

flag:

表中的一样数据被更新或删除并不是真正的删除,而是删除flag标志位的更改。

(2)undo log:

undo log有两种:

① insert undo log:

代表事务在insert新纪录时产生的undo log,只在事务回滚时需要,并在在事务提交后可以被立即丢弃;

② update undo log:

事务在进行update或者delete时产生的undo log,不仅在回滚时需要,在快照读时也需要(即为MVCC所需);
不能立即删除,由purge线程统一删除。

在 update undo log 中,记录着同一行数据被修改的每一个历史版本,由 DB_ROLL_PTR 连接成为一个链表。每个节点中的 DB_TRX_ID 记录每次对其操作的事务ID。

(3)read view:

read view 就是事务进行快照读时产生的读视图。

read view 中有三个重要的变量:

trx_list:       当前活跃事务ID的列表;
up_limit_id:    活跃事务列表中最小的事务ID;
low_limit_id:   当前系统已分配的事务ID值+1,即下一个待分配的事务ID。

注意:

一个事务 start transaction 后并不是立刻产生一个读视图 read view,而是当这个事务调用 select 真正的进行读操作时,才会产生一个read view,相应的 read view 视图中的属性变量也是根据调用select时数据库的情况生成()

一个事务的read view读视图只生成一次,即在第一次调用select时生成,后面如果再次调用select进行读操作,read view读视图不会更新,即依然读第一次生成的快照。

一张表示undo log的示图:

在这里插入图片描述


五、 关于 select…lock in share mode 和 select…for update:

mysql> select * from runoob_tb1 where runoob_id=123 lock in share mode;
mysql> select * from runoob_tb1 where runoob_id=123 for update;

这两个操作都是【读操作】进行加锁,相同点在于它们都会对所读的数据行加锁,禁止其他事务对其此行数据更新、改写;不同点在于:

select … lock in share mode 是共享锁 (IS,意向共享锁)
select … for update 是排它锁 (IX,意向排它锁)

另外需要注意的是:
select...for update 仅适用于InnoDB,并且必须开启事务!必须开始事务!
在事务之外使用select...for update 和 select...lock in share mode没有任何效果。

举例:

事务A 事务B
start transaciton; strart transaction;
select * from runoob_tb lock in share mode;
select * from runoob_tb lock in share mode;
(正常执行) (正常执行)
事务A 事务B
start transaciton; strart transaction;
select * from runoob_tb for update;
select * from runoob_tb for update;
(正常执行) (阻塞)

最后来张结构图:

在这里插入图片描述


参考链接:

MVCC多版本并发控制(MVCC原理讲解很透彻)
MySQL中的行级锁,表级锁,页级锁
数据库的读现象浅析
Select for update使用详解
浅析MySQL二段锁
MySQL的事务与锁(这篇非常全面)

猜你喜欢

转载自blog.csdn.net/ArtAndLife/article/details/109871262