数据库事务隔离级别
标准隔离级别
读未提交、读已提交、可重复读、串行化
串行化
对事务中所有读写的数据加上读锁、写锁、范围锁。所以冲突的事务必须同步执行。
//console1 start transaction ; select * from transaction_test where `key`=1; update transaction_test set name='newTest' where `key`=1; //console2 start transaction ; select * from transaction_test where `key`=1;(由于事务1没有释放写锁,所以这里的查询会阻塞 如果等待时间过长,会报如下的错误;如果事务1只是查询,那么事务2也可以查询) [40001][1205] Lock wait timeout exceeded; try restarting transaction //console1 commit ;(提交完之后如果事务2没有等待超时,那么会立即执行) //console2; commit ;
可重复读
核心是只对事务中所有读写的数据加上读锁、写锁,不加范围锁。
相比于读已提交,由于对整个事务都加上了读锁,避免其他事务可以进行更新,进而保证同一个事务多次读到的数据都是没有被修改过的数据。
----避免可重复读---- name初始值为init //console1 start transaction ; select * from transaction_test where `key`=1;(查询结果是init) //console2 start transaction ; update transaction_test set name='test' where `key`=1; (理论上,由于事务1已经获取了读锁,事务2这里添加写锁应该是添加不上的,应该是阻塞中才对; 但是,实操发现,执行成功了,且在事务2中通过下面这个语句查询是test,这应该也是mvcc导致的 select * from transaction_test where `key`=1; //console1 select * from transaction_test where `key`=1; console1的第2次查询,查询结果和第一次一样,还是init 另外,事务2都获得写锁了,怎么能允许你事务1再去获得读锁 commit ; //console2 commit ;
相比于串行化,由于没有加范围锁,会引发一种叫幻读的情况
所谓幻读是指在同一个事务中,第一次查询id<10的假定有1条,第二次查询可能会有2条,原因是在两次查询的中间,存在别的事务插入或者删除了数据,由于事务A只加了读锁或者写锁,只能防止其他事务对已经加锁的这几条数据进行修改,但避免不了插入和删除,所以才会出现这个问题。
----幻读---- 初始是1,name //console1 start transaction ; select * from transaction_test where `key`<10; //console2 start transaction ; insert into transaction_test ( `key`,`name`) value (3,'newddd'); select * from transaction_test where `key`<10; commit; //console1 select * from transaction_test where `key`<10; 理论上来讲,这个地方应该会查到三条,但是实操发现,在事务2添加并提交之后,事务1查到了依然是原来的样子 commit ; select * from transaction_test where `key`<10;(提交之后再次查询就有新结果了)
读已提交
核心是对事务中需要更新的操作行加写锁,直到事务结束,但对查询的操作行加读锁,但在查询完之后立即释放,即不是在整个事务范围锁定。
读已提交通过对查询操作加锁来避免读未提交,在事务B修改数据时因为其在事务结束之前一直持有写锁,事务A无法对数据加读锁,只能等待事务B提交事务才可以读取,这也是读已提交的名称的由来。
虽然解决了读未提交的问题,但是由于只在查询的时候短暂加了写锁,引发了另一个不可重复读的问题;
所谓不可重复读是指在同一个事务中,对于同样一条数据的两次查询结果不一样,那么这个和幻读有什么区别呢?幻读整个事务中都存在读锁或者写锁,其他事务无法修改,只能增删;但是不可重复读,则是指当前已经查到的结果被更新了。
原因是假如同一个事务两次查询中间,别的事务进行了修改,由于事务A没有加整个事务范围的读锁,所以事务B是可以成功获取写锁的,进而修改数据,最终导致了不可重复读。
---避免读未提交---- name初始值是init //console1 start transaction ; select * from transaction_test where `key`=1; update transaction_test set name='test' where `key`=1; //console2 start transaction ; select * from transaction_test where `key`=1;(由于读不到未提交的,所以肯定获取不到修改后的test值,理论上只能等待事务1结束) 这个地方由于事务1已经添加了写锁,原则上事务2根本查询不了,应该阻塞,就像串行化那里一样 但是实际结果却是可以查到以前的值,即init;所以这里应该是mvcc的作用 在读已提交的级别下,mvcc机制总是取最新的版本即可,即最近被 Commit 的那个版本的数据记录。 这样保证了读到的都是已提交的事务,且保留了幻读问题 最新版本的快照读,不是当前读 //console1 commit;//提交之后,事务2再次查询,发现已经可以获取到改动后的值了,即test ---不可重复读---- name初始值是init //console1 start transaction ; select * from transaction_test where `key`=1;(第一次查询是init) //console2 start transaction ; update transaction_test set name='test' where `key`=1;(在事务2中更新并提交) commit ; //console1 select * from transaction_test where `key`=1;(第二次查询是test) commit ;
读未提交
核心是对事务中需要更新的操作行加写锁,直到事务结束,但对查询的操作行不加锁。
引发的问题是脏读,其实就是读到了其他事务还没有提交的数据;那么为什么事务A可以读到事务B还没有提交的数据?
分为两步理解:
1.为什么存在可以读的新的数据?
核心原因应该是write-ahead logging的设计。即上一章提到的允许在事务提交之前提前写入数据,理论上肯定是写到了内存中,并且记录到undolog里面,虽然还不太情况事务的提交真正干了什么操作,但目前来,在内存是可以读到已经修改好的数据。
2.为什么可以读到已经加了写锁的数据
原因是读未提交读取数据是不加读锁的,而写锁只能防止其他事物不能加读锁和写锁,而不能防止没有锁 也可以看一下这篇博客的解释
show variables like 'transaction%'; set global transaction isolation level read uncommitted ;//设置完之后要重新登录 CREATE TABLE `transaction_test` ( `key` int(11), `name` varchar(10) DEFAULT NULL ) ENGINE=InnoDB; ---read uncommitted--- 读未提交 //console1 start transaction ; insert into transaction_test value (1,'test'); //console2 start transaction ; select * from transaction_test where `key`=1; (查询结果为1,test) //console1 commit ; //console2 commit; 两个事务都是写事务,晚开启的事务更新会阻塞 //console1 start transaction ; update transaction_test set name='newTest' where `key`=1; //console2 start transaction ; update transaction_test set name='Test' where `key`=1;(会阻塞,一直在执行中) //console1 commit ;(在事务1提交成功后,事务2的更新立马就成功了) //console2 commit;
参考资料:
本文来自博客园,作者:起司啊,转载请注明原文链接:https://www.cnblogs.com/qisi/p/transaction_isolation_level.html