事务的隔离性
事务的四大特性为原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),本篇专门说说隔离性。
四大隔离级别
在多个事务同时执行时就会出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决该问题引入了”隔离级别“的概念。
以下四种隔离级别刚好解决三种“读的问题”,隔离级别从轻到重为:
读未提交:一个事务做了修改还未提交就会被其他事务所读到。
读提交:一个事务提交之后其做的改变才能被其他事务读到。
可重复读:一个事务执行过程中看到的数据总是和其刚开始时看到的相同。
串行化:顾名思义,各个事务串行执行。
mysql的隔离级别为可重复读,oracle为读提交
如下以一个经典的例子说明这三种读问题和四种隔离级别
事务一 事务二
启动事务,查询发现为1 启动事务
将值由1改为2
查询得到V1
提交事务二
查询得到V2
提交事务一
查询得到V3
读未提交的隔离级别下,v1值为2,他读到了事务二还未提交之前的数据,这种现象称之为脏读;
读提交的隔离级别下,v1值为1,v2值为2。事务一执行期间之后读的数据和刚开始的数据不同,这种现象称之为不可重复读;
可重复读的隔离级别下,v1值为1,由于可重复读保证了前后读到的数据相同因此v2值为1,v3值为2。
串行化的隔离级别下,依然v1 = 1, v2 = 1, v3 = 2。
到这好像也没发现串行化比可重复读更高隔离级别的好处,
事务一 事务二
启动事务,
查询c字段发现不存在
启动事务
插入c字段
提交事务二
查询c字段
插入c字段
提交事务一
在上述案例中隔离级别若为可重复读时,发现事务一中第二次查询c字段发现还是不存在(这是又可重复读决定的),但是插入时缺无法插入,对事务一而言明明不存在我想插入缺不能插入,跟见了鬼一样。
使用串行化隔离级别就可以避免该问题,只有事务一执行完成后才能执行事务2,此时事务二发现c字段存在。
事务隔离的实现
实现上,数据库会创建一个视图。“可重复读”隔离级别下,这个视图是在事务启动时创建的;在“读提交”隔离级别下在每条sql语句开始前创建一个视图;“读未提交”直接返回最新值为视图概念;“串行化”隔离级别下直接用加锁的方式避免并行访问。
在MySQL中,每条记录在更新的时候会记录一条回滚操作,由当前值回滚即可得到其上一状态值,如下图所示,若当前值由1改成2,再改成3,最后改成4,其回滚段如下:
上图来自mysql45讲
当系统中没有比回滚日志更早的read-view时,回滚日志才会删除。因此经量避免使用长事务。
事务的启动方式:
1、显式启动事务
// autocommit= 1 的情况
begin // 显式启动事务
commit // 提交事务
rollback // 回滚
2、set autocommit=0 // 默认为1
如此只要执行一个select语句,事务就启动了,其并不会自动提交直到主动执行commit或rollback。
推荐使用方式一,对于方式一比方式二对一次交互的问题,使用commit work and chain,提交当前事务并且开始下一个事务(相当于 commit + begin)
事务到底是隔离还是不隔离?
该节以一个案例引入,如下情况都是在可重复读的隔离级别下的
假设表t中id = 1时 k字段初始值为1
事务A开启
事务B事务开启
select k from id = 1
事务C事务开启
update t set k = k + 1 where id = 1
提交事务
update t set k = k + 1 where id = 1
select k from t where id = 1
提交事务
select k from t where id = 1
提交事务
事务A查询到k的值仍然为1,事务B查询到第一次查询到的值为1, 第二次查询到的k值竟然为3。不是说可重复读嘛,咋不重复了,就算要变也是变为2啊?
InnoDB中每条事务都有自己的id号,其在事务开始时向InnoDB事务系统申请的,按照申请顺序严格递增。
此外每行数据也有多个版本,每次事务更新数据后都会生成一个新的数据版本,并把当前的事务id赋给这个数据版本的事务id,因此访问数据时可以通过判断当前事务id和当前数据版本事务id的大小,若当前数据版本的事务id小于当前行的事务id,则说明该数据在本事务中是可见的,否则任务当前数据版本是本事务开启后别的事务修改后的版本,往前回退直到得到满足条件的数据版本。
数据的更新操作都是先读后写的,这个读指的就是当前读(即读当前最新版本的数据而不是读小于等于当前事务id 的数据)。
因此事务B修改操作读到的是事务C提交的k值2,加1就变为3了。
再来一个问题
事务B事务开启
select k from id = 1
事务C事务开启
update t set k = k + 1 where id = 1
update t set k = k + 1 where id = 1
select k from t where id = 1
提交事务
提交事务
其结果与之前相同。
事务B执行更新语句之前,事务C先拿到行锁,并更新完,此时到事务B后,由于其拿不到锁一直阻塞,直到事务C提交事务了(用到两阶段锁协议),然后事务B才能拿到行锁继续执行。
可重复读和读提交的主要区别
可重复读的隔离级别下,只需事务开始时创建一个一致性视图,之后事务中其他查询都使用该视图。
读体检隔离级别下,每一条语句执行之前都会重新计算出一个新视图。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Elasticsearch 升级 7.x 版本后,我感觉掉坑里了!
- 【一起学系列】之适配器模式:还有外观模式呢
- 【翻译】200行代码讲透RUST FUTURES (5)
- Unable to preventDefault inside passive event listener
- js 停止事件冒泡 阻止浏览器的默认行为(阻止a标签跳转 )
- EmitMapper的使用小结
- js .map方法
- 【一起学系列】之模板方法:写SSO我只要5分钟
- ConcurrentDictionary线程不安全么,你难道没疑惑,你难道弄懂了么?
- 【一起学系列】之迭代器&组合:虽然有点用不上啦
- 移动端touch事件影响click事件以及在touchmove添加preventDefault导致页面无法滚动的解决方法
- 使用ActionFilterAttribute 记录 WebApi Action 请求和返回结果记录
- scipy.stats连续分布的基本操作
- InvocationHandler中invoke方法中的第一个参数proxy的用途
- height、offsetheight、clientheight、scrollheight、innerheight、outerheight