事务的隔离性

2020-08-05 14:48:19 浏览数 (1)

事务的四大特性为原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),本篇专门说说隔离性。

四大隔离级别

在多个事务同时执行时就会出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决该问题引入了”隔离级别“的概念。

以下四种隔离级别刚好解决三种“读的问题”,隔离级别从轻到重为:

读未提交:一个事务做了修改还未提交就会被其他事务所读到。

读提交:一个事务提交之后其做的改变才能被其他事务读到。

可重复读:一个事务执行过程中看到的数据总是和其刚开始时看到的相同。

串行化:顾名思义,各个事务串行执行。

mysql的隔离级别为可重复读,oracle为读提交

如下以一个经典的例子说明这三种读问题和四种隔离级别

代码语言:javascript复制
事务一                       事务二
启动事务,查询发现为1            启动事务
                            将值由1改为2

查询得到V1
                            提交事务二

查询得到V2
提交事务一
查询得到V3

读未提交的隔离级别下,v1值为2,他读到了事务二还未提交之前的数据,这种现象称之为脏读;

读提交的隔离级别下,v1值为1,v2值为2。事务一执行期间之后读的数据和刚开始的数据不同,这种现象称之为不可重复读;

可重复读的隔离级别下,v1值为1,由于可重复读保证了前后读到的数据相同因此v2值为1,v3值为2。

串行化的隔离级别下,依然v1 = 1, v2 = 1, v3 = 2。

到这好像也没发现串行化比可重复读更高隔离级别的好处,

代码语言:javascript复制
事务一                       事务二
启动事务,
查询c字段发现不存在            
                            启动事务
                            插入c字段
                              提交事务二
查询c字段
插入c字段                            

提交事务一

在上述案例中隔离级别若为可重复读时,发现事务一中第二次查询c字段发现还是不存在(这是又可重复读决定的),但是插入时缺无法插入,对事务一而言明明不存在我想插入缺不能插入,跟见了鬼一样。

使用串行化隔离级别就可以避免该问题,只有事务一执行完成后才能执行事务2,此时事务二发现c字段存在。

事务隔离的实现

实现上,数据库会创建一个视图。“可重复读”隔离级别下,这个视图是在事务启动时创建的;在“读提交”隔离级别下在每条sql语句开始前创建一个视图;“读未提交”直接返回最新值为视图概念;“串行化”隔离级别下直接用加锁的方式避免并行访问。

在MySQL中,每条记录在更新的时候会记录一条回滚操作,由当前值回滚即可得到其上一状态值,如下图所示,若当前值由1改成2,再改成3,最后改成4,其回滚段如下:

上图来自mysql45讲

当系统中没有比回滚日志更早的read-view时,回滚日志才会删除。因此经量避免使用长事务。

事务的启动方式:

1、显式启动事务

代码语言:javascript复制
// autocommit= 1 的情况
begin // 显式启动事务
commit // 提交事务
rollback // 回滚

2、set autocommit=0 // 默认为1

如此只要执行一个select语句,事务就启动了,其并不会自动提交直到主动执行commit或rollback。

推荐使用方式一,对于方式一比方式二对一次交互的问题,使用commit work and chain,提交当前事务并且开始下一个事务(相当于 commit begin)

事务到底是隔离还是不隔离?

该节以一个案例引入,如下情况都是在可重复读的隔离级别下的

假设表t中id = 1时 k字段初始值为1

代码语言:javascript复制
事务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了。

再来一个问题

代码语言:javascript复制
                                        事务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才能拿到行锁继续执行。

可重复读和读提交的主要区别

可重复读的隔离级别下,只需事务开始时创建一个一致性视图,之后事务中其他查询都使用该视图。

读体检隔离级别下,每一条语句执行之前都会重新计算出一个新视图。

0 人点赞