数据库--事务

本文详细阐述了事务的ACID特性,探讨了并发处理中可能出现的问题,如脏读、不可重复读和幻读。特别介绍了InnoDB引擎如何通过MVCC和锁机制解决这些问题,以及MySQL的隔离级别和使用策略。
摘要由CSDN通过智能技术生成

事务的四种特性
ACID: 原子性,一致性,隔离性,持久性。

原子性(Atomicity)指事物在逻辑上是不可分割的操作单元,所有语句要么都执行,要么都撤销执行
一致性(Consistent)一个事务本质是将数据从一种一致性状态转换到另一种一致性状态,具体取决于现实生活的逻辑
隔离性(Isolation)隔离性是针对并发事务而言的,同时处理多个事务的时候,数据库的事务提供了不同的隔离级别来保证正确
持久性(Durable)事务一旦提交,对于数据的修改是持久性的,数据更新的结果已经从内存转存到外部存储器,即使系统故障,已提交的数据更新也不会丢失

这四个特性在没有并发的时候显然很容易满足,但是在并发处理事务的情况下,可能会带来一些问题

丢失更新多个事务操作同一行,后面的事务修改的值会 覆盖 前面的事务修改的值
脏读当一个事务正在多次修改一个数据,而这一系列修改还没有最后提交,另一个并发事务来读取,就会导致错误。也就是另一个事务读到了脏数据(A事务修改完 没提交,被B事务读到)
不可重复读一个事务操作的过程里,先读取一个数据,后来又读取,而两次读出的数据值不一致。就是因为中间被别的事务修改了(A事务修改完 并提交了,被B事务读到)
幻读一个事务按照相同的查询条件查两次,第一次查出了A 集合,第二次却不是,因为其他事务插入了数据,正好满足这个事务的查询条件。(AB事务读到的 集合、数据集合、数据条目不一致)

注意事项:

脏读和不可重复读的区别:脏读读到的脏数据是另一个事务没有提交的数据,但是不可重复读读到错误数据是因为另一个事务把数据修改并提交了;

幻读和不可重复读的区别:幻读和不可重复读都是读到了另一个事务提交的数据,但是不可重复读是两个事务针对同一个数据项,而幻读针对的是一个数据整体(数据条目)

为了解决上述提到的事务并发问题,数据库提供一定的事务隔离机制来解决这个问题。

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使用事务在一定程度上“串行化” 进行,这显然与“并发” 是矛盾的。

事实上 Mysql 的 InnoDB 通过 MVCC (Multi-Version Concurrent Control,多版本并发控制)机制解决了不可重复读的基础上,又解决了幻读的的问题。

MySQL的锁(结合 InnoDB引擎)
背景
对于数据库事务的并发控制技术有很多,基于锁、基于时间戳、基于MVCC的并发控制、基于MVCC的可串行化快照隔离等。

InnoDB 是支持 ACID 的,而MySQL用 InnoDB 作为自己的默认存储引擎,事务管理是 MySQL Server 实现框架和接口定义,而 InnoDB 提供具体的事务操作和并发控制,所以 MySQL 的事务模型,主要是指 MySQL 的InnoDB 的事务管理部分。

InnoDB 使用锁和 MVCC 技术来实现并发事务的访问控制技术。
其中,锁是并发控制的基础,在此基础之上,实现了 MVCC 机制,用以提高基于锁的方式带来的低效率问题。

有了这个概念,我们下面分别讨论 锁 和 MVCC 两个内容。

MyISAM 默认使用的是表锁;
InnoDB 支持行级锁,加上 MySQL Server 支持表级锁,所以结合事务的时候,使用 InnoDB 引擎,系统就默认使用的是行级锁。

锁的分类
从对数据操作的粒度分 :

表锁:操作时,会锁定整个表。(MySQL Server)
行锁:操作时,会锁定当前操作行。(也叫 Record Lock,记录锁),实际上他是在索引上的记录之锁,因为 InnoDB 的表的组织结构是通过 B+ 树索引。
从对数据操作的类型分:

读锁(共享锁、S锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
写锁(排它锁、X锁):当前操作没有完成之前,它会阻断其他写锁和读锁。
意向共享锁(IS):事务想要获取一张表中某几行的共享锁
意向排它锁(IX):事务想要获取一张表中的某几行的排它锁
前两个很容易理解,他们的兼容情况是:只有 S 锁和 S 锁是兼容的,其他的组合都是互斥的。

其他锁
InnoDB里还有几个锁:

间隙锁(Gap Locks):两个索引项之间的间隔、称为间隙,把这个间隙看作一个对象,在此对象上加锁,就是间隙锁。这个锁是从加锁的对象角度定义的锁,所以和表、行是同一个角度的锁。
Next-Key锁(Next-Key Locks):行级锁+间隙锁共同组成。
Insert Intention Locks:基于间隙锁,专门用于 Insert 操作。
间隙锁会在 RC 隔离级别的某些情况下使用,在 RR 隔离级别下,间隙锁会和行级锁合并成 Next-key 锁使用。

InnoDB 的MVCC原理
在基于锁的并发控制的基础之上,实现了 MVCC 技术。

首先还是强调一点,MVCC 技术本身思想从名字就可以看的出来,就是通过多个版本进行并发的控制。那么并发控制技术,是需要配合其他的并发控制技术来具体实现,这里我们讲的 InnoDB 的 MVCC 原理,就是基于锁的。

日志
日志是保证事务的原子性、持久性的重要技术之一,在 MVCC 的实现中也是要用到的,对于每一个 SQL 操作,都不是一下子执行完成,因此数据的状态都要变化,那么把这个过程记录下来,出现问题进行”回放“,就能应对事物的原子性和持久性。

需要记录的数据通常包括:

事务标识:比如事务 id;
数据项的标识;
旧值:数据项被修改之前的值;
新值:数据项被修改之后的值。
一系列的 SQL 操作过程变成一个序列,这就是日志,数据库引擎在具体实现的时候会把日志放到日志缓存区,然后刷出到外存,存放到日志文件。

日志文件一般分为 REDO 日志和 UNDO 日志:

REDO 日志记录事务的标识、数据项的标识和 新值;
UNDO 日志记录事务的标识、数据项的标识和 旧值。(InnoDB 的 MVCC 用到的是 UNDO log)
InnoDB 的MVCC
因为 InnoDB 的多版本,指的是 行(元组) 级别的版本,在每行(或者每个元组、每条记录)上,都有一些和并发、回滚相关的隐含字段,分别为:

DB_TRX_ID:很好理解,就是 id,表示上一个执行(insert | update)操作的事务。
DB_ROLL_PTR:就是 pointer,回滚指针,指向的就是一个旧版本。那么其实指向的是当前记录行的 undo log 信息,是旧版本的数据位于回滚段中的位置,通过这个指针能够找到旧版本;
DB_ROW_ID:随着新行插入而单调递增的行 ID,和 MVCC 关系不大。
在回滚段里的 UNDO 日志分为两种:

INSERT UNDO logs:插入到回滚段中的日志,仅用于事务提交时使用,当事务提交,则插入 UNDO 日志里的内容被清除;
UPDATE UNDO logs:被用于一致性无锁读,为一致性读提供快照隔离下的可被读取的老版本数据。当没有需要满足一致性读的快照时,一些老版本数据才能被清理。
以上,实现的原理基本告一段落,但是 InnoDB 的实现层面,还有另一个数据结构,就是 Read View 快照。

Read View(读视图),跟快照、snapshot 是一个概念,可以看作事务的生命周期里面的一段,而不同的快照就是不同的段。在源码层面,他是一个类,名叫 ReadView,这里面的内容,重点有两个:一个就是保存了快照的左右边界另一个是提供了如何判断当前行(元组)的可见性的标志。

和 MVCC 有关的额外两个概念
快照读(snapshot read):普通的 select 语句(不包括 select … lock in share mode, select … for update)。也就是不加锁的非阻塞读,所以在串行级别下的快照读会退化成当前读。他是基于多版本的,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
当前读(current read) :select … lock in share mode,select … for update,insert,update,delete 语句。(为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。)

MVCC 原理总结
首先,事务的概念有了,事务的特性随着概念出来:ACID。
那么,并发事务如果不加控制,就会存在问题,按处理的难易程度从低到高:丢失更新-脏读-不可重复读-幻读。
于是,数据库如果实现事务,就要保证特性,解决对应的问题,应运而生四个隔离级别:RU-RC-RR-S。
因为事务是在存储引擎层实现的,所以接下来讨论了基于 InnoDB 引擎的 MySQL 事务的实现:相关概念:锁的分类:表锁、行锁。读锁、写锁、意向锁。间隙锁(Gap Locks)、Next-Key锁;相关概念:锁的实施细则;相关概念:基于锁的并发版本控制,结合 MVCC。如果没有 MVCC ,四个并发问题,除了读读不用加锁,读写、写读、写写都不能并发执行,否则就会产生问题,效率低下,有了MVCC,读写和写读可以并发执行。那 MVCC 都用到了什么呢?额外的几个字段;基于 Undo log;结合 Read View (快照)。
现在我们回过头看一下MVCC机制工作的两个隔离级别:RC 和 RR :

RC,提交读,要解决脏读的问题,保证一个事务读取到的一定是另一个事务的已经提交的结果,而不能是未提交的结果。那么,对于 RC级别来说,务中,每次快照读都会新生成一个快照和Read View,保证每个事务可以看到别的事务提交的更新。
RR,可重复读,要解决不可重复读的问题,保证快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。

MVCC 解决幻读问题了吗?
在 RR 级别下,没有完全解决幻读 的问题。

我们回忆幻读的概念:一个事务按照相同的查询条件查两次,第一次查出了A集合,第二次却不是了,因为其他事务插入了数据,正好满足这个事务的查询条件。

那么对于上面的 MVCC 原理,基于快照读的情况:

事务 A 开始后,执行普通 select 语句,创建了快照;事务 B 执行 insert 语句;事务 A 再执行普通 select 语句,得到的还是之前 B 没有 insert 过的数据,因为这时候 A 读的数据是符合快照可见性条件的数据。这是解决了幻读问题的。
但是考虑这种情况:

事务A执行的不是普通 select 语句,而是 select … for update 等语句,根据上面的定义,事务 A 是当前读,每次语句执行的时候都是获取的最新数据。那么 B 执行 insert语句;A 再次查询的时候,就可能会查到多一条数据,产生幻读。
这个时候就要出场我们在前面的锁分类部分的一行红字,另外两个锁:间隙锁和 Next-key Locks。

间隙锁在 RR 级别发挥作用,结合行级锁称为 Next-key locks,解决幻读问题。具体的算法可能很复杂,原理就是锁定范围的设置加上了间隙,这样插入操作肯定是没办法进行的,因此就不会存在其他事务的插入操作导致幻读了。

到这里我们可以得出结论,MySQL 里完全解决幻读的方法有两个:

直接使用 S 隔离等级完全串行化;
RR 的隔离级别结合 MVCC 机制,还要结合 间隙锁。

  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值