再谈数据库隔离级别
前言
在“数据库事务和事务的隔离级别”一文中,事务的隔离级别有如下4中隔离级别,
1.未授权读取,read uncommitted
2.授权读取,read committed
3.可重复读取,repeatable read
4.串行化,serializable
这次我以mysql为例,通过实际操作演示一下这四种隔离级别。
笔者使用的mysql版本是5.6.41,我们可以在客户端调用version()用来查看数据可的版本。
我们通过客户端通过@@tx_isolation查看一下mysql默认设置的事务隔离级别是REPEATABLE-READ
事务隔离级别设置
在“数据库事务和事务的隔离级别”,笔者已经提出了当前会话事务隔离级别和全局事务隔离级别。
更改当前会话事务隔离级别:set session transaction isolation level serializable
更改全局事务隔离级别:set global transaction isolation level serializable
语法:set [session | global] transaction isolation level [read uncommitted | read committed | repeatable read | serlializable];
毫无疑问,当前会话,往往用于开发或者测试人员在工作过程中,用于单元测试,而全局事务的更改往往用于实际的应用环境中。但是大多数据库一般使用了数据库默认的事务隔离级别。我们来演示一下,现在我们把当前会话的事务隔离级别更改为 未授权读取read uncommitted 。如下图所示,
新开一个会话,查询一下事务隔离级别,如下图所示,
现在把全局事务隔离级别设置为read uncommitted,如下图所示,
把刚才的另一个会话窗口关闭再打开,查询发现也已经是未授权读取的级别,这里要注意的是:在隔离级别修改之前的会话,如果不重新关闭再打开,在这个老的会话范围内依然还是原来的级别。
最佳实践
阿里巴巴中间件负责人毕玄,在早年发表的《分布式Java应用 基础与实践》一书中强调,实践是最好的成长。
为了能够阐述的更加清楚一点,我们创建了一张表tb_user,同时两个字段id和name。下面就这张表作一下演示。演示过程中用到事务,我们就直接称为事务A、事务B、事务C....
①未授权读取。
我们把全局事务隔离级别置为未授权读取read uncommitted。
首先我们新开一个会话客户端A和B,同时,都开启事务,如下图示意,客户端B对原有的记录作更新操作,客户端A读取到了客户端B未提交的数据。
小结:未授权读取,能够读取到其他没有提交的的事务所操作的结果,即未授权读取不能避免脏读。
②授权读取。
我们把全局事务隔离级别置为未授权读取read committed。更改之后,当前会话并没有马上生效,需要重开一个窗口,也再一次证实了上面我提到的论点。
我们新开一个会话客户端A和B,同时,都开启事务,如下图示意,客户端B对tb_user先做更新,再做新增操作,在客户端B事务已经提交的情况下,客户端A读取到了客户端B新增的数据,和更新的数据。
小结:授权读取,只读取到其他事务已经提交的数据,能够避免脏读,但是不能避免可重复读取,上图客户端A中的事务在步骤2和7中的查询结果集不一致。
③可重复读取。
我们把全局事务隔离级别置为未授权读取repeatable read。
我们新开一个会话客户端A和B,同时,都开启事务,如下图示意,客户端B对tb_user先做更新,再做新增操作,在客户端B事务已经提交的情况下,客户端A没有取到了客户端B新增的数据,但是没有读取到更新的数据。
小结:可重复读,可以解决脏读和可重复读,但是对于幻读,可能还是会存在。在上面的实例中,新增,有可能是能读取到的,注意这里的用词是可能,可能各个数据库的版本有所不同。
④串行化。
串行化是最高级别的数据库事务隔离级别。
我们把全局事务隔离级别置为未授权读取serializable。如下图在客户端A开始事务,同时在客户端B也开启事务,在客户端B中执行select *语句,不带where条件,发现客户端A中的update语句进入等待的状态。
客户端A:
客户端B:
当客户端B执行commit;客户端A中的update语句立即执行。
小结:串行化的级别能够解决脏读,可重复读,幻读的问题。只要多个事务存在共享资源的竞争时,就会进入到一个等待的状态,当然两个事务都是对同一个表select查询操作是没有关系的。
注意:
只有在资源竞争时,才会串行化。不同的表,则不受干扰,同一个表的不同记录,也不受干扰。
1.客户端A开启事务执行select from tb_demo;客户端B开启事务执行update tb_user set name='123' where id=1;这两个事务互不干扰。
2.客户端A开启事务执行select * from tb_user where where id=1;客户端B开启事务执行update tb_user t set t.name='老王' where id=2;两个事务互不干扰。