数据库事务隔离级别

标准隔离级别

读未提交、读已提交、可重复读、串行化

串行化

对事务中所有读写的数据加上读锁、写锁、范围锁。所以冲突的事务必须同步执行。
//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;

参考资料:

 

热门相关:洪荒二郎传   最强反套路系统      学霸女神超给力   法医王妃不好当!