首页 > 编程知识 正文

数据库隔离级别怎么实现的,innodb事务隔离级别

时间:2023-05-06 09:24:43 阅读:47180 作者:2662

前言

说到数据库事务,您的脑海中一定会浮现出事务的相关知识,包括事务的ACID特性、隔离级别和已解决的问题(脏读、不可重复读、幻像读)

今天首先谈谈MySQL中事务隔离性的实现原理。 之后继续发文分析其他特性的实现原理。

当然,MySQL博大精深。 文章的疏漏是不可避免的。 欢迎批评指正。

说明

MySQL的事务实现逻辑位于引擎层,并非所有引擎都支持事务。 以下说明均基于InnoDB引擎。

定义

“隔离”(isolation )是指在不断提交和执行不同事务后最终出现的效果。 也就是说,对于事务来说,在执行过程中感知到的数据变化是由自己的操作引起的,而其他事务中的数据变化应该不存在。

隔离解决的是并发事务的问题。

标准SQL隔离级别

隔离的最简单实现方法是,如果每个事务都串行执行,而上一个事务尚未执行,则所有后续事务都等待。 但是,这种实现方式明显同时性不高,不适合在实际环境中使用。

为了解决上述问题,实现不同级别的并发控制,SQL标准制定者提出了不同的隔离级别:未提交领先、提交领先、可重复领先) 其中,最高级的隔离级别是序列化读取,但其他隔离级别同时执行事务,因此允许出现一些问题。 请参考以下矩阵表。

隔离级别(:允许出现,-:不允许出现) )。

潦草的读法

不要重复

幻读

未读

提交阅读

_

可重复读取

_

_

序列化读取

_

_

_

请注意,MySQL innodb引擎在提交读取级别通过MVCC解决了不可重复的读取问题,在可重复读取级别通过间隙锁解决了幻像读取问题。 具体请参照以下分析。

实现原理

标准SQL事务隔离级别的实现原理

我们面临的问题实际上是并发事务中的控制问题,解决并发事务的最常见方法是悲观并发控制。 这意味着数据库中的锁定。 标准SQL事务隔离级别的实现依赖于锁定。 让我们来看看具体是如何实现的。

事务隔离级别

实现方式

未读(RU ) ) )。

事务不锁定当前读取的数据;

事务必须在某个数据更新的瞬间(即更新发生的瞬间)进行行级共享锁定,直到事务结束。

提交先导(RC )

事务将对当前读取的数据进行行级共享锁定,并在读取时进行锁定。 行读完后,立即解除行级共享锁定。

事务必须在某个数据更新的瞬间(即更新发生的瞬间)进行行级独占锁定,直到事务结束。

可重复(RR ) )。

事务必须在开始读取某个数据的瞬间(即开始读取数据的瞬间)进行行级共享锁定,直到事务结束。

事务必须在某个数据更新的瞬间(即更新发生的瞬间)进行行级独占锁定,直到事务结束。

序列化读取(s ) ) )。

在读取数据时,事务必须在表级共享锁定后才能释放,直到事务结束。

更新数据时,事务必须在事务结束之前进行表级独占锁定。

可见,如果仅使用锁定实现隔离级别控制,则需要频繁解除锁定,并且容易发生读/写冲突。 例如,在RC级别,事务处理a更新数据行1,事务处理b在事务处理a提交之前读取数据行1,然后等待事务处理a提交并解锁。

为了在不锁定的情况下解决读写冲突问题,MySQL引入了MVCC机制。 有关详细信息,请查看我以前的分析文章:阅读数据库中的乐观锁、悲观锁和MVCC。

InnoDB事务隔离级别的实现原理

在进行分析之前,需要了解一些概念。

1、锁定引线和一致性非锁定引线

锁定读取:在单个事务中主动锁定读取,如SELECT . LOCK IN SHARE MODE或SELECT . FOR UPDATE。 分别添加了行共享锁定和行排他锁定。 锁的分类可以看到我以前的分析文章:你应该知道的MySQL锁的分类)。

一致的未锁定读取: InnoDB使用MVCC在事务查询中提供时间点的数据库快照。 查询显示在当前时间之前提交的地点所做的更改,但不显示在以后或未提交的地点所做的更改。 此事务除外。 也就是说,事务启动后,事务看到的所有数据都是事务启动时的数据,其他事务的后续更改在此事务中不可见。

一致读取是InnoDB在RC和RR隔离级别处理选择语句的缺省模式。 一致的非锁定读取不会对要访问的表设置锁定,因此在对表执行一致的非锁定读取时,其他事务可以同时读取和修改。

2、当前领先优势和快照领先优势

当前装入

读取的是最新版本,像UPDATE、DELETE、INSERT、SELECT ...  LOCK IN SHARE MODE、SELECT ... FOR UPDATE这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

快照读

读取的是快照版本,也就是历史版本,像不加锁的SELECT操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是未提交读和序列化读级别,因为未提交读总是读取最新的数据行,而不是符合当前事务版本的数据行,而序列化读则会对表加锁。

3、隐式锁定和显式锁定

隐式锁定

InnoDB在事务执行过程中,使用两阶段锁协议(不主动进行显示锁定的情况):

随时都可以执行锁定,InnoDB会根据隔离级别在需要的时候自动加锁;

锁只有在执行commit或者rollback的时候才会释放,并且所有的锁都是在同一时刻被释放。

显式锁定

InnoDB也支持通过特定的语句进行显示锁定(存储引擎层)

select ... lock in share mode //共享锁

select ... for update //排他锁

MySQL Server层的显示锁定:

lock table

unlock table

了解完上面的概念后,我们来看下InnoDB的事务具体是怎么实现的(下面的读都指的是非主动加锁的select)

事务隔离级别

实现方式

未提交读(RU)

事务对当前被读取的数据不加锁,都是当前读;

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级共享锁,直到事务结束才释放。

提交读(RC)

事务对当前被读取的数据不加锁,且是快照读;

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record),直到事务结束才释放。

通过快照,在这个级别MySQL就解决了不可重复读的问题

可重复读(RR)

事务对当前被读取的数据不加锁,且是快照读;

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record,GAP,Next-Key),直到事务结束才释放。

通过间隙锁,在这个级别MySQL就解决了幻读的问题

序列化读(S)

事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放,都是当前读;

事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。

可以看到,InnoDB通过MVCC很好的解决了读写冲突的问题,而且提前一个级别就解决了标准级别下会出现的幻读和不可重复读问题,大大提升了数据库的并发能力。

一些常见误区

幻读到底包不包括了delete的情况?

不可重复读:前后多次读取一行,数据内容不一致,针对其他事务的update和delete操作。为了解决这个问题,使用行共享锁,锁定到事务结束(也就是RR级别,当然MySQL使用MVCC在RC级别就解决了这个问题)

幻读:当同一个查询在不同时间生成不同的行集合时就是出现了幻读,针对的是其他事务的insert操作,为了解决这个问题,锁定整个表到事务结束(也就是S级别,当然MySQL使用间隙锁在RR级别就解决了这个问题)

网上很多文章提到幻读和提交读的时候,有的说幻读包括了delete的情况,有的说delete应该属于提交读的问题,那到底真相如何呢?我们实际来看下MySQL的官方文档(如下)

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT) is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html

可以看到,幻读针对的是结果集前后发生变化,所以看起来delete的情况应该归为幻读,但是我们实际分析下上面列出的标准SQL在RR级别的实现原理就知道,标准SQL的RR级别是会对查到的数据行加行共享锁,所以这时候其他事务想删除这些数据行其实是做不到的,所以在RR下,不会出现因delete而出现幻读现象,也就是幻读不包含delete的情况。

MVCC能解决了幻读问题?

网上很多文章会说MVCC或者MVCC+间隙锁解决了幻读问题,实际上MVCC并不能解决幻读问题。如以下的例子:

begin;

#假设users表为空,下面查出来的数据为空

select * from users; #没有加锁

#此时另一个事务提交了,且插入了一条id=1的数据

select * from users; #读快照,查出来的数据为空

update users set name='mysql' where id=1;#update是当前读,所以更新成功,并生成一个更新的快照

select * from users; #读快照,查出来id为1的一条记录,因为MVCC可以查到当前事务生成的快照

commit;

可以看到前后查出来的数据行不一致,发生了幻读。所以说只有MVCC是不能解决幻读问题的,解决幻读问题靠的是间隙锁。如下:

begin;

#假设users表为空,下面查出来的数据为空

select * from users lock in share mode; #加上共享锁

#此时另一个事务B想提交且插入了一条id=1的数据,由于有间隙锁,所以要等待

select * from users; #读快照,查出来的数据为空

update users set name='mysql' where id=1;#update是当前读,由于不存在数据,不进行更新

select * from users; #读快照,查出来的数据为空

commit;

#事务B提交成功并插入数据

注意,RR级别下想解决幻读问题,需要我们显式加锁,不然查询的时候还是不会加锁的

以上就是详解MySQL中事务隔离级别的实现原理的详细内容,更多关于MySQL 事务隔离级别的资料请关注脚本之家其它相关文章!

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。