事务的特性
ACID
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
今天主要了解的是隔离性。
事务的隔离性
先了解下脏读,不可重复读取,幻读的概念:
-
脏读:能读到其他事务未提交的数据。
-
不可重复读:两个事务,事务A先读了某个字段值,事务B把这个字段值改了并提交,事务A再次读时,值已经变了。
-
幻读:两个事务,事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。
事务的隔离性从弱到强的顺序:
- 读未提交:一个事务还未提交,它所做的变更就可以被别的事务看到,容易出现脏读,幻读,不可重复读取。
- 读提交:一个事务提交之后,它所做的变更才可以被别的事务看到,会出现幻读,不可重复读取。
- 可重复读:一个事务执行过程中看到的数据是一致的。未提交的更改对其他事务是不可见的。还是会出现“幻读”。
- 串行化:对应一个记录会加读写锁,出现冲突的时候,后访问的事务必须等前一个事务执行完成才能继续执行。
读提交和可重复读比较难理解。我们来看下两个并行事务。
事务A | 事务B |
---|---|
启动事务,查询到字段a的值为1 | 启动事务 |
查询到字段a的值为1,将1改为2 | |
再次查询到字段a的值为v1 | |
提交事务 | |
再次查询到字段a的值为v2 | |
提交事务 | |
再次查询到字段a的值为v3 |
如果是读提交, 那么v1为1,v2为2,v3为2。
如果是可重复读, 那么v1为1,v2为1,v3为2。
查看和修改事务隔离性
查看当前事务隔离性:
show variables like 'transaction_isolation';
修改事务隔离性:
通过transaction-isolation命令。
隔离性的实现
以可重复读为例。
首先通过事务更改数据时,每次操作都会记录一个回滚日志(undo log)
那么之前讲隔离性时,根据不同隔离级别, 可能会展示不同的结果,又是怎么做到的?
每个事务操作,都会产生一个read-view(视图),保存下当时的活跃事务ID等信息。当我们通过事务查询时,也会携带当前事务ID等信息。 通过事务ID依据我们定义的隔离级别进行对比,得到需要展示的值。
事务ID是具有自增性的。
同样,数据库每一行数据也有版本。通过更改操作后,版本信息里也保存了最后一次的事务ID。
具体扩展知识:
自行查找Mysql的多版本并发控制(MVCC)。
read-view和回滚日志何时会删除?当系统里么有比这个回滚日志更早的read-view的时候。
通过例子会更好理解:
例子1:
假设某字段所在行的版本中,最后的事务ID为99。
事务A | 事务B |
---|---|
启动事务(事务ID为100) | 启动事务(事务ID为101,产生read-viewA,里面存了100,101),将字段a的值1改为2(会生成一条从2改为1的回滚日志) |
查询字段a的值(此时会携带当前事务ID为100,与当前行的版本里的事务ID做比较,发现100<101,根据我们定义的可重复读级别,不可见),得到的还是1 | |
提交事务 | |
再次读取,同理,读取到的还是1 | |
提交事务,此时事务B的回滚日志和read-viewA才会删除(所以我们尽量不要使用长事务,会造成大量回滚日志和read-view) |
总结三种情况:
- 版本未提交,不可见;
- 版本已提交,但是是在视图创建后提交的,不可见;
- 版本已提交,而且是在视图创建前提交的,可见。
例子2:
当有单独update时的情况:
假设某字段所在行的版本中,最后的事务ID为99。
事务A | 正常C |
---|---|
启动事务(事务ID为100) | |
直接执行update,将字段a的值1改为2,此时行版本的事务ID已变成101 | |
进行字段值+1操作,(注意:此时会生成一条从3改为2的回滚日志,产生read-viewA,里面存了100) | |
读取字段a,值为3 | |
提交事务(清除所有回滚日志和read-view) |
这里就很奇怪,照理应该还是在1基础上+1,为什么能感知到字段值已经是2了呢?
因为更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。所以读取逻辑和更新时的读取逻辑还是不一样的。