一篇文章搞懂MVCC

2023年 8月 21日 45.2k 0

事务

什么是事务?当事务对数据库进行多个更改时,要么在事务提交时所有更改都成功,要么在事务回滚时所有更改都被撤销。 在 MySQL 中,事务支持是在引擎层实现的。MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。

事务有四个特性,ACID,即原子性、一致性、隔离性和持久性。

开启事务用 start transaction 或者 begin ,配套的提交语句是 commit,回滚语句是 rollback。

//开启事务,READ WRITE可写可不写,默认就是READ WRITE,也可以指定只读READ ONLY
START TRANSACTION READ WRITE;

//事务里面有查询语句,有更新语句
SELECT * FROM `ddk_app_config`;
update ddk_app set desc='aaa' where id=1;

//提交该事务
COMMIT; 

//回滚该事务
ROLLBACK;

一条或者多条sql语句都属于事务,那为什么我们平时在写update语句的时候没有手动开启start transaction呢,因为我们有一个属性叫做autocommit自动提交,这个属性平时就是开着的。我们可以看下会话和全局的都是开着的。

image.png

set SESSION autocommit=0,关闭自动提交,关闭以后单条语句也必须要进行commit或者ROLLBACK。 有些客户端连接框架会默认连接成功后先执行一个 set autocommit=0 的命令。这就导致接下来的查询都在事务中,如果是长连接,就导致了意外的长事务。所以我建议总是使用 set autocommit=1 , 通过显式语句的方式来启动事务。

查询事务表,比如下面这个语句,用于查找持续时间超过 60s 的事务。

select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

在这张表里面保存的是还没有提交的事务,会有一些关于事务的信息字段,具体的说明见官网:dev.mysql.com/doc/refman/…
image.png

脏读幻读不可重复读

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题。

脏读:能读取到其他线程还没有提交的数据,但是这些数据可能是会回滚的。

image.png

不可重复读:在开启事务之后,读取到其他事务进行修改或者删除提交的的数据。
image.png

幻读:在开启事务之后,读到了其他事务新添加的新数据。 官网定义:dev.mysql.com/doc/refman/…
image.png

注意:上面三个问题脏读肯定是有危害的,因为你读到的数据如果回滚了,你读到的就是无效数据。但是可重复读跟幻读问题,根据不同的业务场景来,大部分的业务场景来讲,我们是能够接受的。

但是无论如何,是不符合我们数据库设计原则中的隔离性原则;事务跟事务之间进行了相互影响,我们应该让数据在一个事务中进行了读取后,在没有提交事务之前,不能被其他事务进行更改。所以接下来我们看下隔离级别。

隔离级别

为了解决脏读幻读不可重复读这些问题,就有了隔离级别的概念。所谓隔离级别是在多个事务同时进行更改和执行查询时,对性能和结果的性能、一致性之间的平衡进行设置。简单一句话,就是多个事务并发的时候,你是去保证性能还是优先保证数据一致性。隔离级别隔离得越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。

SQL 标准的事务隔离级别包括:读未提交(READ UNCOMMITTED)、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)和串行化(SERIALIZABLE)。官网地址:dev.mysql.com/doc/refman/…

读未提交: 查询语句以非锁定的方式执行,既然非锁定,那么数据还在操作的时候,只要在内存中了,不管有没有提交,所以会导致读取到还没有提交的数据,这些数据也可能是要回滚的,是脏数据。读取到没有提交的数据,就是脏读。

读已提交: 读取已经提交的数据,所以肯定会解决脏读问题。

可重复读: 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。

串行化: 顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

//修改当前会话读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

//修改当前会话读已提交级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

//修改当前会话RR级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

//修改当前会话串行化级别
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; 


//查看全局隔离级别
SHOW GLOBAL VARIABLES LIKE '%isolation%'; 
//查看会话隔离级别
SHOW SESSION VARIABLES LIKE '%isolation%';

下面这张图就是四个隔离级别对三个问题的解决程度,很重要,大家记住。
image.png

MVCC

同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。InnoDB在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的实现。

一致性读取意味着InnoDB使用多版本来向查询提供数据库在某个时间点的快照。该查询查看在该时间点之前提交的事务所做的更改,而不查看后来或未提交的事务所做的更改。所以关键在于快照,就是在某些时间点,我创建一个快照,这个快照创建了之后,后续同一个事务的所有读取都是读取的这个快照内容。

在RC隔离级别下,则每次一致性读都会创建一个新的快照。

在RR隔离级别下,则在第一次一致性读的时候,创建快照。如果不在第一个select创建快照,也可以用 start transaction with consistent snapshot, 这个在事务开启的时候就会创建一个read view。

这里的快照其实不是真正意义的将数据存储了一份,而是一个readView的数据结构保存了某些信息,然后通过对这些信息的判断来达到不会读到最新数据的目的。

image.png

对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。

Read View

下面我们来看下什么是Read View,源码在 storageinnobaseincluderead0types.h

class ReadView {

//省略

..................

/** The read should not see any transaction with trx id >= this value. In other words, this is the "high water mark". */

trx_id_t m_low_limit_id; //如果大于等于这个值的事务不可见,也称高水位线

/** The read should see all trx ids which are strictly smaller (

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论