- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
事务,由一个有限的数据库操作序列构成,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位.
假如A转账给B 100 元,先从A的账户里扣除 100 元,再在 B 的账户上加上 100 元。如果扣完A的100元后,还没来得及给B加上,银行系统异常了,最后导致A的余额减少了,B的余额却没有增加。所以就需要事务,将A的钱回滚回去,就是这么简单.
为什么要有事务呢? 就是为了保证数据的最终一致性.
事务四个典型特性,即ACID,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability).
事务并发会引起 脏读、不可重复读、幻读 问题.
如果一个事务读取到了另一个未提交事务修改过的数据,我们就称发生了脏读现象.
假设现在有两个事务A、B:
因为事务A读取到事务B 未提交的数据 ,这就是脏读.
同一个事务内,前后多次读取,读取到的数据内容不一致 。
假设现在有两个事务A和B:
事务A被事务B干扰到了!在事务A范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是 不可重复读 .
如果一个事务先根据某些搜索条件查询出一些记录,在该事务未提交时,另一个事务写入了一些符合那些搜索条件的记录(如insert、delete、update),就意味着发生了 幻读 .
假设现在有两个事务A、B:
事务A查询一个范围的结果集,另一个并发事务B往这个范围中插入新的数据,并提交事务,然后事务A再次查询相同的范围,两次读取到的结果集却不一样了,这就是幻读.
为了解决并发事务存在的 脏读、不可重复读、幻读 等问题,数据库大叔设计了四种隔离级别。分别是 读未提交,读已提交,可重复读,串行化(Serializable) .
读未提交隔离级别,只限制了两个数据 不能同时修改 ,但是修改数据的时候,即使事务 未提交 ,都是可以被别的事务读取到的,这级别的事务隔离有 脏读、重复读、幻读 的问题; 。
读已提交隔离级别,当前事务只能读取到其他事务 提交 的数据,所以这种事务的隔离级别 解决了脏读 问题,但还是会存在 重复读、幻读 问题; 。
可重复读隔离级别,限制了读取数据的时候,不可以进行修改,所以 解决了重复读 的问题,但是读取范围数据的时候,是可以插入数据,所以还会存在 幻读 问题; 。
事务最高的隔离级别,在该级别下,所有事务都是进行 串行化顺序 执行的。可以避免脏读、不可重复读与幻读所有并发问题。但是这种事务隔离级别下,事务执行很耗性能.
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | √ | √ | √ |
读已提交 | × | √ | √ |
可重复读 | × | × | √ |
串行化 | × | × | × |
数据库是通过 加锁 ,来实现事务的隔离性的。这就好像,如果你想一个人静静,不被别人打扰,你就可以在房门上加上一把锁.
加锁确实好使,可以保证隔离性。比如 串行化隔离级别就是加锁实现的 。但是频繁的加锁,导致读数据时,没办法修改,修改数据时,没办法读取,大大 降低了数据库性能 .
那么,如何解决加锁后的性能问题的?
答案就是, MVCC多版本并发控制 !它实现读取数据不用加锁,可以让读取数据同时修改。修改数据时同时可读取.
MVCC,即 Multi-Version Concurrency Control (多版本并发控制) 。它是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存.
通俗的讲,数据库中同时存在多个版本的数据,并不是整个数据库的多个版本,而是某一条记录的多个版本同时存在,在某个事务对其进行操作的时候,需要查看这一条记录的隐藏列事务版本id,比对事务id并根据事物隔离级别去判断读取哪个版本的数据.
数据库隔离级别读 已提交、可重复读 都是基于MVCC实现的,相对于加锁简单粗暴的方式,它用更好的方式去处理读写冲突,能有效提高数据库并发性能.
事务每次开启前,都会从数据库获得一个 自增 长的事务ID,可以从事务ID判断事务的执行先后顺序。这就是事务版本号.
对于InnoDB存储引擎,每一行记录都有两个隐藏列 trx_id 、 roll_pointer ,如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列 row_id .
列名 | 是否必须 | 描述 |
---|---|---|
row_id | 否 | 单调递增的行ID,不是必需的,占用6个字节。 |
trx_id | 是 | 记录操作该数据事务的事务ID |
roll_pointer | 是 | 这个隐藏列就相当于一个指针,指向回滚段的undo日志 |
undo log, 回滚日志 ,用于记录数据被修改前的信息。在表记录修改之前,会先把数据拷贝到undo log里,如果事务回滚,即可以通过undo log来还原数据.
可以这样认为,当delete一条记录时,undo log 中会记录一条对应的insert记录,当update一条记录时,它记录一条对应相反的update记录.
undo log有什么 用途 呢?
多个事务并行操作某一行数据时,不同事务对该行数据的修改会产生多个版本,然后通过回滚指针(roll_pointer),连成一个链表,这个链表就称为 版本链 。如下:
其实,通过版本链,我们就可以看出 事务版本号、表格隐藏的列和undo log 它们之间的关系。我们再来小分析一下.
update core_user set name ="曹操" where id=1
,会进行如下流程操作
快照读: 读取的是记录数据的可见版本(有旧的版本)。不加锁,普通的select语句都是快照读,如:
select * from core_user where id > 2;
当前读 :读取的是记录数据的最新版本,显式加锁的都是当前读 。
select * from core_user where id > 2 for update; select * from account where id> 2 lock in share mode;
Read View是如何保证可见性判断的呢?我们先看看Read view 的几个重要属性 。
Read view 匹配条件规则 如下:
trx_id < min_limit_id
,表明生成该版本的事务在生成Read View前,已经提交(因为事务ID是递增的),所以该版本可以被当前事务访问。 trx_id>= max_limit_id
,表明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。 min_limit_id =<trx_id< max_limit_id
,需腰分3种情况讨论
- (1).如果
m_ids
包含trx_id
,则代表Read View生成时刻,这个事务还未提交,但是如果数据的trx_id
等于creator_trx_id
的话,表明数据是自己生成的,因此是 可见 的。- (2)如果
m_ids
包含trx_id
,并且trx_id
不等于creator_trx_id
,则Read View生成时,事务未提交,并且不是自己生产的,所以当前事务也是 看不见 的;- (3).如果
m_ids
不包含trx_id
,则说明你这个事务在Read View生成之前就已经提交了,修改的结果,当前事务是能看见的。
InnoDB 实现MVCC,是通过 Read View+ Undo Log 实现的,Undo Log 保存了历史快照,Read View可见性规则帮助判断当前版本的数据是否可见.
事务A: select * fom core_user where id= 1 事务B: update core_user set name =”曹操”
执行流程如下:
最后事务A查询到的结果是, name=曹操 的记录,我们 基于MVCC ,来分析一下执行流程:
(1). A开启事务,首先得到一个事务ID为100 。
(2).B开启事务,得到事务ID为101 。
(3).事务A生成一个Read View,read view对应的值如下 。
变量 | 值 |
---|---|
m_ids | 100,101 |
max_limit_id | 102 |
min_limit_id | 100 |
creator_trx_id | 100 |
然后回到版本链:开始从版本链中挑选可见的记录:
由图可以看出,最新版本的列name的内容是 孙权 ,该版本的 trx_id 值为100。开始执行read view可见性规则校验:
min_limit_id(100)=<trx_id(100)<102 ; creator_trx_id = trx_id = 100 ;
由此可得,trx_id=100的这个记录,当前事务是可见的。所以查到是name为 孙权 的记录.
(4). 事务B进行修改操作,把名字改为曹操。把原数据拷贝到undo log,然后对数据进行修改,标记事务ID和上一个数据版本在undo log的地址.
(5) 提交事务 。
(6) 事务A再次执行查询操作, 新生成一个Read View ,Read View对应的值如下 。
变量 | 值 |
---|---|
m_ids | 100 |
max_limit_id | 102 |
min_limit_id | 100 |
creator_trx_id | 100 |
然后再次回到版本链:从版本链中挑选可见的记录:
从图可得,最新版本的列name的内容是 曹操 ,该版本的 trx_id 值为101。开始执行Read View可见性规则校验:
min_limit_id(100)=<trx_id(101)<max_limit_id(102) ; 但是, trx_id= 101,不属于m_ids集合
因此, trx_id=101 这个记录,对于当前事务是可见的。所以SQL查询到的是name为 曹操 的记录.
综上所述,在 读已提交(RC)隔离级别 下,同一个事务里,两个相同的查询,读取同一条记录(id=1),却返回了不同的数据( 第一次查出来是孙权,第二次查出来是曹操那条记录 ),因此RC隔离级别,存在 不可重复读 并发问题.
在RR隔离级别下,是如何解决不可重复读问题的呢?我们一起再来看下, 。
还是4.2小节那个流程,还是这个事务A和事务B,如下:
实际上,各种事务隔离级别下的Read view工作方式,是不一样的,RR可以解决不可重复读问题,就是跟 Read view工作方式有关 .
begin | |
---|---|
select * from core_user where id =1 | 生成一个Read View |
/ | / |
/ | / |
select * from core_user where id =1 | 生成一个Read View |
begin | |
---|---|
select * from core_user where id =1 | 生成一个Read View |
/ | |
/ | |
select * from core_user where id =1 | 共用一个Read View副本 |
我们穿越下,回到 刚4.2的例子 ,然后执行第2个查询的时候:
事务A再次执行查询操作,复用老的Read View副本,Read View对应的值如下 。
变量 | 值 |
---|---|
m_ids | 100,101 |
max_limit_id | 102 |
min_limit_id | 100 |
creator_trx_id | 100 |
然后再次回到版本链:从版本链中挑选可见的记录:
从图可得,最新版本的列name的内容是 曹操 ,该版本的 trx_id 值为101。开始执行read view可见性规则校验:
min_limit_id( 100)=<trx_id( 101)<max_limit_id( 102); 因为m_ids{ 100, 101}包含trx_id( 101), 并且creator_trx_id ( 100) 不等于trx_id( 101)
所以, trx_id=101 这个记录,对于当前事务是 不可见 的。这时候呢,版本链 roll_pointer 跳到下一个版本, trx_id=100 这个记录,再次校验是否可见:
min_limit_id( 100)=<trx_id( 100)< max_limit_id( 102); 因为m_ids{ 100, 101}包含trx_id( 100), 并且creator_trx_id ( 100) 等于trx_id( 100)
所以, trx_id=100 这个记录,对于当前事务是 可见 的。即在可重复读(RR)隔离级别下,复用老的Read View副本,解决了 不可重复读 的问题.
最后此篇关于MVCC-数据库的文章就讲到这里了,如果你想了解更多关于MVCC-数据库的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
参考地址: 看一遍就理解:MVCC原理详解 - 掘金 (juejin.cn) 1. 相关数据库知识点回顾 1.1 什么是数据库事务,为什么要有事务
版本链 在InnoDB引擎表中,他们的聚簇索引记录中有两个隐藏列: trx_id:用来存储对数据进行修改时的事务id roll_pointer:每次对哪条聚簇索引记录有修改的时候,就
是否MVCC数据库隔离模式是否允许进行中的事务查看其他事务插入(和提交)的行? 例如,给定: 表 names[id BIGINT NOT NULL, name VARCHAR(30), PRIMARY
我试图理解 MVCC 但无法理解。例如。Transaction1 (T1) 尝试读取一些数据行。在同一时间,T2 更新同一行。 交易流程是 T1 begin -> T2 begin -> T2 com
问题是关于 MySQL InnoDB 表中同时 SELECT 和 UPDATE 的行为: 我们有一个相对较大的表,我们会定期扫描它,读取多个字段,其中包括一个名为 LastUpdate 的字段。在扫描
我对交易的理论和实现感到困惑。 根据教科书,数据项有共享锁和排他锁,这些锁是冲突的。因此,如果一个事务具有排他锁(用于插入/更新),那么即使是读取(选择/共享锁),其他事务也无法访问该数据项。 到目前
我一直在关注一些无锁代码的正确性,我真的很感激我能得到的任何输入。我的问题是关于如何在 C++11 的内存模型中使用获取和释放语义来实现一些所需的线程间同步。在我的问题之前,一些背景... 在 MVC
问候溢出者, 根据我的理解(我希望我是不对的)不能对索引进行 MVCC 更改。 我想知道这是否也适用于大记录,因为副本可能很昂贵。 由于记录是通过索引访问的(通常),MVCC 如何才能有效? 例如,索
我在网络上找到了很多资源,这些资源提供了 MVCC(多版本并发控制)概念的一般概述,但没有关于它应该如何工作或实现的详细技术引用。是否有任何在线文档或离线书籍包含足够的理论(最好是一些实际帮助)以作为
1、前言 作为一个数据库爱好者,自己动手写过简单的sql解析器以及存储引擎,但感觉还是不够过瘾。<<事务处理-概念与技术>>诚然讲的非常透彻,但只能提纲挈领,不能让你
在 PostgreSQL 中,元组的每次更新都会创建新的元组版本。所以在一段时间内,同一个元组可能有很多版本,不同的交易可以看到不同版本的元组(使用可见性规则) 索引在交易完成前更新。这如何与 SI
来自 MySQL manual关于 InnoDB 多版本控制: Internally, InnoDB adds three fields to each row stored in the datab
假设我想在读取提交模式(在 postgres 中)执行以下事务。 T1: r(A) -> w(A) T2: r(A) -> w(A) 如果按以下顺序调用操作: r1(A)->r2(A)->w1(A)-
关闭。这个问题是off-topic .它目前不接受答案。 想改进这个问题吗? Update the question所以它是on-topic用于堆栈溢出。 关闭 10 年前。 Improve thi
据我了解,postgres使用了两个额外的字段Xmin和Xmax来实现mvcc,假设我们有包含 id 和 name 列的 Employee 表。 下面是一些 crud 操作以及它们如何并发工作(考虑隔
我目前正在阅读 dbms 书籍,据我所知,Mvcc(多版本并发控制)用于高并发读写事务。 但是“搜索结构的并发控制”一章提到了 B 树的不同锁定概念(锁耦合、链接技术等)。 Mvcc 不是应用于 db
今天我想在一件可能不熟悉的事情上使用 Infinispan。我想保存一个变量,让我们在缓存中多次调用它 x - 同时能够将其寻址为 X。 普通、旧的 MVCC。然而,infinispan 似乎在后端使
问题总结 这是一个关于 SQL 事务中查询的可串行化的问题。 具体来说,我正在使用 PostgreSQL。可能假设我使用的是最新版本的 PostgreSQL。根据我的阅读,我相信用于支持我正在尝试做的
MVCC Non-Blocking Reads是InnoDB行锁的正式名称吗?我在 comparison table 中遇到过这个词汇表对于 InnoDB 和 NDB;我不确定它们是同一种东西还是完全
MongoDB 对我来说是一个很棒的数据库。但是,在某些情况下,我确实需要原子多文档事务。例如,在帐户之间转移东西(如金钱或声誉),这需要完全成功或完全失败。 我想知道是否可以通过实现多版本并发控制模
我是一名优秀的程序员,十分优秀!