Mysql事务隔离级别
事物的个隔离级别是非常重要的概念,Mysql的隔离级别有以下几种
- 读未提交读
在所有事物中可以看到事物没有提交的结果,实际应用中是很少的,他的性能也不比其他隔离级别好很多,读到未提交的结果导致脏读
- 读已提交度
大多数据库的默认隔离级别,但是不是mysql的默认级别,一个事物只能看到已经提交的结果,他也支持不可重复读,在同一个事物的其他实例在该实例中修改的数据,导致两次select的结果可能不一样
- 可重复读
mysql的默认隔离级别,在事务开始的时候,直到事务结束看到的行的数据都是一样,这种隔离级别是会产生幻读,幻读就是在用户读取某一范围的数据时候,其他事物新增了一条数据,用户再次读取的时候,返现多了一行数据(幻读是指读到了其他事务的inset,不可重复读是指读到了其他事物的delete/update)
- 串行化 这种隔离级别就是使事物严格按照顺序执行,就是在每一行数据上加上锁,保证了事物不可冲突,避免了幻读,脏读,不可重复读,但是增加了锁超时和锁竞争
之前我们说过在可重复读级别下,事物T开始的时候会创建一个read-view,之后再事物T期间的其他事物修改了数据,对于事务T也是不可见的,但是在我上一篇说过,一个事物在修改一行数据的时候,发现这行数据已经被行锁锁住了,这个时候只能等待行锁被释放,但是在释放之后,他读取的值有事什么呢
首先,我们看一下例子如下建表语句
mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL,
`k` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);
看看上面的例子,我们看看事物A 和事物B查询到的结果是什么,此时你老想一下,(没有特别说明,默认都是autocommit=1)
事物A的结果是1,而事物B的结果是3,不知道是否和你的答案一致呢,
为什么会是这样呢,首先我们得看看数据库的一个概念视图,数据库中有定义了两种视图
- 一个视图view是在一个查询语句生成的一个虚拟表,在调用查询语句并生成结果,创建的语句可用 create view ,查询方式和表的方式一样
- 另一个视图则是innodb中的一致性视图,consistent read view ,用于隔离级别读已提交和可重复读的实现
快照在MVCC中是如何使用的呢
在开启一个事物的时候,就会拍个照,这个快照是对于整个库的,但是我们想象,对于一个100G的数据库,我们不可能把所有的数据全部复制一次,但是为什么开始一个事物还是很快呢,
实际上每一个事物的开启,都会创建一个事物id ,transaction id,他是向数据库申请,严格按照顺序递增的,而每一行数据都是有多个版本的,每一个事物更新数据后都会把此时的事物id,赋值给每一个版本的事物id,即row tx_id,并且保留旧的数据,并且在新的数据版本都可以直接拿到旧版本的数据,正如下图,一行数据多次更新的版本状态
如上图显示,目前最新的版本是k=22,他是的事物id=15,因此他的row trx_id=15,之前我们也提到过,语句的更新会产生undo log,其实上面的三个实线箭头就是undo log,而实际上v1,v2,v3不是真实存在的,而是有当前版本和undo log计算出来的。
在可重复读隔离级别,我们知道在事物启动的时候,只能看到事物启动前提交的数据,之后生成的版本我们是不认的,当然自己修改的数据还是要认的,
在实际应用中,每一个事物都会有一个数组,数组保存的是当前系统活跃的事物id,活跃的是指,启动但未提交的事物id。
- 低水位,是指数组中最小的事物id
- 高水位,是指数组中最大的事物id+1
一致性视图是有视图数组和高水位组成如下图
数据的可见性是根据数据的row trx_id和一致性视图判断的
这样,当一个事物启动的瞬间,row trx_id可能有以下几种情况
- 如果落在绿色部分,表示事物已经提交,对当前事物可见
- 如果落在红色部分,表示事物未启动,对当前事物不可见
- 如果在黄色部分有两种情况
- 如果在视图数组中,表示事务没有提交,可见
- 如果不在视图数组中,表示事务已经提交,不可见
到这里,我们回过来看看开头我们的问题,为什么事务A的k=1,而事物B的k=3
此时假设一下如下
- 事物A开启前,只有一个活跃事务row trx_id=99
- 事物A,B,C的版本分别是100,101,102
- 三个事物开启前此时(1,1)row trx_id=90
因此根据上面的可见性规则判断如下
上图中看到事物c先把(1,1)更新成了(1,2)此时row trx_id=102,然后事物B更新(1,2)为(1,3),此时的row trx_id=101,
此时我们来看看事物A查询的数据如何获取
- 此时事务A的视图数组是[99,100]
- 找到当前版本(1,3)此时的row trx_id=101,落在了红色部分,不可见
- 向上寻找上一个版本(1,2)此时的row trx_id=102,落在红色部分,不可见
- 向上寻找上一个版本(1,1),此时row trx_id=90,落在了绿色部分,可见
- 因此此时的k=1
上面的判断是从代码逻辑进行判断,其实我们可以按照下面规则进行判断
- 版本未提交,不可见
- 版本提交,是视图创建后,不可见
- 版本提交,是视图创建前,可见
我们也验证一下上面的规则如下
- (1,3)版本未提交,不可见
- (1,2)版本提交,但是视图创建后提交,不可见
- (1,1)版本提交,且是视图创建前提交,可见
但是有人会发现事物B 的update语句感觉是不有问题呢,为什么会是在(1,2)基础上进行增加的,事物C的视图不是后面才创建的吗
如果不是按照历史版本更新的话,事物c的更新不是就丢失了吗,导致读到的数据是脏读,那究竟是为什么的,这里我们要加一条规则,uodate的时候,是先读在写的,而这个读必须读取当前值,这种叫做当前读,
除了update语句外,我们的select 如果加锁,也是使用当前读,如果使用下面语句读取到的k=3
select k from t where id =1 lock in share mode
select k from t where id =1 for update
我们再进一步分析事务C如果不是自动提交,而是在下面的事务C1
此时(1,2)已经生产,但是事物没有进行提交,那么事物B的更新语句如何执行,这就要提到了上一节说的行锁,此时id=1被行锁锁住,事物B的当前读,必须等待id=1的行锁释放后,才能使用当前读。
可重复读的核心就是一致性视图,更新的时候只能用当前读,如果当前记录被行锁锁定,必须等待释放,再去更新,
读已提交和不可重复读的逻辑类似,主要有以下区别
- 在可重复读下,事物创建的建立的一致性视图,之后的其他语句通用这一个一致性视图
- 读已提交,是事物中每一个语句都会新建一个一致性视图
我们再来看看读已提交的情况,如下图
事物A在获取查询语句的时候创建视图,(1,3)(1,2)此时生成的时刻是在创建视图之前,因此
- (1,3)此时还没有提交,不可见
- (1,2)此时已经提交,可见
- 因此事物A,k=2
- 显而易见事务B,k=3
- 【Go 语言社区】在 Go 语言中,如何正确的使用并发
- Data Guard高级玩法:通过闪回恢复failover备库 (r10笔记第7天)
- ajax跨域问题-web开发必会
- 在线重定义的补充测试(r10笔记第26天)
- 聊聊Data Guard中的DG Broker(r10笔记第24天)
- stuts2返回json数据简单实现
- Linux命令ssh-copy-id (r10笔记第21天)
- 【Go 语言社区】HTML5 canvas验证码识别
- 迁移式升级的测试(二)(r10笔记第35天)
- Golang实现图片缩放服务器
- mongodb数据结构与基本操作增删改查整理(二)
- 使用在线重定义重构亿级分区表(r10笔记第34天)
- 【Go 语言社区】一个WebSocket的简单Echo例子
- Java基础-day10-代码题-继承&抽象类
- MySQL 教程
- MySQL 安装
- MySQL 管理与配置
- MySQL PHP 语法
- MySQL 连接
- MySQL 创建数据库
- MySQL 删除数据库
- MySQL 选择数据库
- MySQL 数据类型
- MySQL 创建数据表
- MySQL 删除数据表
- MySQL 插入数据
- MySQL 查询数据
- MySQL where 子句
- MySQL UPDATE 查询
- MySQL DELETE 语句
- MySQL LIKE 子句
- mysql order by
- Mysql Join的使用
- MySQL NULL 值处理
- MySQL 正则表达式
- MySQL 事务
- MySQL ALTER命令
- MySQL 索引
- MySQL 临时表
- MySQL 复制表
- 查看MySQL 元数据
- MySQL 序列 AUTO_INCREMENT
- MySQL 处理重复数据
- MySQL 及 SQL 注入
- MySQL 导出数据
- MySQL 导入数据
- MYSQL 函数大全
- MySQL Group By 实例讲解
- MySQL Max()函数实例讲解
- mysql count函数实例
- MYSQL UNION和UNION ALL实例
- MySQL IN 用法
- MySQL between and 实例讲解
- Nginx | Nginx增加模块
- springboot之整合druid并配置数据源监控
- c++之程序流程控制
- linux使用 source etc/profile
- c++之数组
- c++之指针
- c++之函数
- kafka的生产者分区机制原理(二)
- (17)Bash别名与快捷键
- (16)Bash历史命令与补全
- 【python-leetcode325-滑动窗口法】最大子数组之和为k
- 爬取51job出现can only concatenate str (not “NoneType“) to str
- springboot之基于注解整合mybatis
- springboot之基于配置文件整合mybatis
- springboot之整合JPA