欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据库事务的四大特性与隔离级别及测试

程序员文章站 2022-03-09 10:37:06
...

四大特性

⑴ 原子性(Atomicity)

  原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

⑵ 一致性(Consistency)

  一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

  拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

⑶ 隔离性(Isolation)

  隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

  即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

  关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。

⑷ 持久性(Durability)

  持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

  例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。


隔离级别

以上介绍完事务的四大特性(简称ACID),现在重点来说明下事务的隔离性,当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:

1,脏读

  脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

例:
创建一个表

INSERT INTO `kedb`.`test_table`
(`id`,
`age`)
VALUES
(<{id: }>,
<{age: }>);

插入一个数据:

INSERT INTO `kedb`.`test_table`
(
`age`)
VALUES
(
5);

数据库事务的四大特性与隔离级别及测试

数据库隔离级别操作:

-- 获取全局隔离级别  
SELECT @@global.tx_isolation;  
--获取会话隔离级别  
SELECT @@tx_isolation; 

--全局隔离级别设置  
SET GLOBAL TRANSACTION ISOLATION LEVEL [  
           READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE];  
--会话隔离级别设置  
SET SESSION TRANSACTION ISOLATION LEVEL [  
            READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE];  

查看数据库的隔离级别

select @@global.tx_isolation;

数据库事务的四大特性与隔离级别及测试

修改级别为read uncommitted;

set gloabal transaction isolation level read uncommitted;

再查看:

select @@global.tx_isolation;

数据库事务的四大特性与隔离级别及测试

数据库事务的四大特性与隔离级别及测试

在终端A开启一个修改事务:
数据库事务的四大特性与隔离级别及测试
但是还未提交

这时开启另一个终端B,查询:
数据库事务的四大特性与隔离级别及测试

终端A进行回滚事务:
数据库事务的四大特性与隔离级别及测试

现在查询:
数据库事务的四大特性与隔离级别及测试

(B可以认为是客户端,A可认为是服务端)
可以发现A的事务还未提交时,B就去获取了,B获取的是个当前状态的,但B并不知道A的事务是否已经提交。这样如果A事务进行回滚数据就不会发生改变,而B却得到了一个改变后的数据,这就是脏读。

那么怎么解决这一问题?
只要将隔离级别设为read uncommitted更高的级别就可以了
数据库事务的四大特性与隔离级别及测试
现在再次模拟:
数据库事务的四大特性与隔离级别及测试
可以发现B这时就不会再因为A事务没提交而出现脏读了。

2,不可重复读

  不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

例如:
事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发生了不可重复读。

再举个例子:
事务1:查询有双人床房间。99号房间,有双人床。
事务2:将99号房间,改成单人床房间。
事务1:再次执行查询,请求所有双人床房间列表,99号房间不再列表中了。也就是说,事务1,可以看到其他事务所做的修改。
在不可重复读,里面,可以看到其他事务所做的修改,而导致2次的查询结果不再一样了。这里的修改,是提交过的。也可以是没有提交的,这种情况同时也是脏读。
如果,数据库系统的隔离级别。允许,不可重复读。那么你启动一个事务,并做一个select查询操作。查询到的数据,就有可能,和你第2次,3次…n次,查询到的数据不一样。一般情况下,你只会做一次,select查询,并以这一次的查询数据,作为后续计算的基础。因为允许出现,不可重复读。那么任何时候,查询到的数据,都有可能被其他事务更新,查询的结果将是不确定的。

  不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

模拟:
再次之前已经将级别设为read committed;
数据库事务的四大特性与隔离级别及测试
从以上可以发现事务A在修改时,这时B开始读取,读取的是A修改之前的数据。没有发生脏读。

现在A提交事务,B再次查询:
数据库事务的四大特性与隔离级别及测试
这时B查询到的结果就不是第一次查询的结果了,所以出现了不可重复读。

可能你会觉得这样也很符合逻辑呀,注意这是异步执行的,而且A,B同时执行,可B却读取了不同的数据,主要原因就是异步。如果改成同步,A事务再修改的时候,B只能进行等待,当A事务结束后B再执行,这样B就能得到正确的数据了。

那么怎么解决这问题?
可以想象就是同步了,在A执行这个事务的时候,B事务就必须进入等待,在A事务执行完后B再进行执行。
以上是分析,在InnoDB中可以设置隔离级别为repeatable read解决不可重复读的问题。

repeatable read
读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
避免了不可重复读取和脏读,但是有时可能出现幻读。这可以通过“共享读锁”和“排他写锁”实现。

3,虚读(幻读)

  幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

这是最高的隔离级别,它通过强制事务排序使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。只要操作产生了锁,就不允许其他事务读取和修改!

在MySQL中,实现了这四种隔离级别,分别有可能产生问题如下所示:
数据库事务的四大特性与隔离级别及测试

REPEATABLE READ 是MySQL的默认事务隔离级别

REPEATABLE READ 这是MySQL的默认事务隔离级别,能确保事务在并发读取数据时会看到同样的数据行,解决了READ-COMMITTED隔离级别下的不可重复读问题。mysql的InnoDB存储引擎通过多版本并发控制(Multi_Version Concurrency Control, MVCC)机制来解决该问题。在该机制下,事务每开启一个实例,都会分配一个版本号给它,如果读取的数据行正在被其它事务执行DELETE或UPDATE操作(即该行上有排他锁),这时该事物的读取操作不会等待行上的锁释放,而是根据版本号去读取行的快照数据(记录在undo log中),这样,事务中的查询操作返回的都是同一版本下的数据,解决了不可重复读问题。其原理如下图所示:

数据库事务的四大特性与隔离级别及测试

 缺陷:虽然该隔离级别下解决了不可重复读问题,但理论上会导致另一个问题:幻读(Phantom Read)。正如上面所讲,一个事务在执行过程中,另一个事物对已有数据行的更改,MVCC机制可保障该事物读取到的原有数据行的内容相同,但并不能阻止另一个事务插入新的数据行,这就会导致该事物中凭空多出数据行,像出现了幻读一样,这便是幻读问题。

而解决幻读问题,就得使用serializable级别了。
SERIALIZABLE 这是事务的最高隔离级别,通过强制事务排序,使之不可能相互冲突,就是在每个读的数据行加上共享锁来实现。在该隔离级别下,可以解决前面出现的脏读、不可重复读和幻读问题,但也会导致大量的超时和锁竞争现象,一般不推荐使用。


参考:http://www.cnblogs.com/JohnABC/p/3521061.html