参考原文:https://my.oschina.net/bigdataer/blog/1976010html
上一篇文章讲述了:数据库主从复制,那么新的问题数据库读写分离对事物是否有影响?数据库
1. 名词session
-
读未提交read-uncommited并发
-
读已提交read-commitedoracle
-
重复读repeatable--》可能产生主从数据不一致问题性能
-
串行化serializable--》特殊场景-秒杀使用,通常不用spa
数据库事务的隔离级别有4个,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可能产生和解决以下问题:脏读、不可重复读、幻读。.net
事务的隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted | √ | √ | √ |
Read committed--Sql Server , Oracle | × | √ | √ |
Repeatable read--MySQL | × | × | √ |
Serializable | × | × | × |
2.注意:咱们讨论隔离级别的场景,主要是在多个事务并发的状况下,所以,接下来的讲解都围绕事务并发。 code
3.Read uncommitted 读未提交htm
- 公司发工资了,领导把5000元打到singo的帐号上,可是该事务并未提交,而singo正好去查看帐户,发现工资已经到帐,是5000元整,很是高兴。但是不幸的是,领导发现发给singo的工资金额不对,是2000元,因而迅速回滚了事务,修改金额后,将事务提交,最后singo实际的工资只有2000元,singo空欢喜一场。
- 出现上述状况,即咱们所说的脏读,两个并发的事务,“事务A:领导给singo发工资”、“事务B:singo查询工资帐户”,事务B读取了事务A还没有提交的数据。
- 当隔离级别设置为Read uncommitted时,就可能出现脏读,如何避免脏读,请看下一个隔离级别。
4.Read committed 读提交
- singo拿着工资卡去消费,系统读取到卡里确实有2000元,而此时她的老婆也正好在网上转帐,把singo工资卡的2000元转到另外一帐户,并在singo以前提交了事务,当singo扣款时,系统检查到singo的工资卡已经没有钱,扣款失败,singo十分纳闷,明明卡里有钱,为什么......
- 出现上述状况,即咱们所说的不可重复读,两个并发的事务,“事务A:singo消费”、“事务B:singo的老婆网上转帐”,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
- 当隔离级别设置为Read committed时,避免了脏读,可是可能会形成不可重复读。
- 大多数数据库的默认级别就是Read committed,好比Sql Server , Oracle。如何解决不可重复读这一问题,请看下一个隔离级别。
5.Repeatable read 重复读
- 当隔离级别设置为Repeatable read时,能够避免不可重复读。当singo拿着工资卡去消费时,一旦系统开始读取工资卡信息(即事务开始),singo的老婆就不可能对该记录进行修改,也就是singo的老婆不能在此时转帐。
- 虽然Repeatable read避免了不可重复读,但还有可能出现幻读。
- singo的老婆工做在银行部门,她时常经过银行内部系统查看singo的信用卡消费记录。有一天,她正在查询到singo当月信用卡的总消费金额(select sum(amount) from transaction where month = 本月)为80元,而singo此时正好在外面胡吃海塞后在收银台买单,消费1000元,即新增了一条1000元的消费记录(insert transaction ... ),并提交了事务,随后singo的老婆将singo当月信用卡消费的明细打印到A4纸上,却发现消费总额为1080元,singo的老婆很诧异,觉得出现了幻觉,幻读就这样产生了。
- 注:MySQL的默认隔离级别就是Repeatable read。
6.Serializable 序列化
Serializable是最高的事务隔离级别,同时代价也花费最高,性能很低,通常不多使用,在该级别下,事务顺序执行,不只能够避免脏读、不可重复读,还避免了幻像读。
.........
7.名词解释
7.1 READ UNCOMMITTED
READ UNCOMMITTED是限制性最弱的隔离级别,由于该级别忽略其余事务放置的锁。使用READ UNCOMMITTED级别执行的事务,能够读取还没有由其余事务提交的修改后的数据值,这些行为称为“脏”读。这是由于在Read Uncommitted级别下,读取数据不须要加S锁,这样就不会跟被修改的数据上的X锁冲突。好比,事务1修改一行,事务2在事务1提交以前读取了这一行。若是事务1回滚,事务2就读取了一行没有提交的数据,这样的数据咱们认为是不存在的。
实验
(1)打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:
(2)在客户端A的事务提交以前,打开另外一个客户端B,更新表account:
(3)这时,虽然客户端B的事务还没提交,可是客户端A就能够查询到B已经更新的数据:
(4)一旦客户端B的事务由于某种缘由回滚,全部的操做都将会被撤销,那客户端A查询到的数据其实就是脏数据:
(5)在客户端A执行更新语句update account set balance = balance - 50 where id =1,lilei的balance没有变成350,竟然是400,是否是很奇怪,数据不一致啊,若是你这么想就太天真 了,在应用程序中,咱们会用400-50=350,并不知道其余会话回滚了,要想解决这个问题能够采用读已提交的隔离级别(不可重复读(read-committed))
7.2 READ COMMITTED
READ COMMITTED(Nonrepeatable reads)是SQL Server默认的隔离级别。该级别经过指定语句不能读取其余事务已修改可是还没有提交的数据值,禁止执行脏读。在当前事务中的各个语句执行之间,其余事务仍能够修改、插入或删除数据,从而产生没法重复的读操做,或“影子”数据。好比,事务1读取了一行,事务2修改或者删除这一行而且提交。若是事务1想再一次读取这一行,它将得到修改后的数据或者发现这同样已经被删除,所以事务的第二次读取结果与第一次读取结果不一样,所以也叫不可重复读。
实验1
query1:事务1
--step1:建立实验数据
select * into Employee from AdventureWorks.HumanResources.Employee
alter table Employee add constraint pk_Employee_EmployeeID primary key(EmployeeID)
--step2:设置隔离级别,这是数据库的默认隔离界别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
--step3:开启第一个事务
BEGIN TRAN tran1
--step4:执行select操做,查看VacationHours,对查找的记录加S锁,在语句执行完之后自动释放S锁
SELECT EmployeeID, VacationHours
FROM Employee
WHERE EmployeeID = 4;
--step5:查看当前加锁状况,没有发如今Employee表上面有锁,这是由于当前的隔离界别是READ COMMITTED
--在执行完step2之后立刻释放了S锁.
SELECT request_session_id, resource_type, resource_associated_entity_id,
request_status, request_mode, resource_description
FROM sys.dm_tran_locks
查看锁的状况以下图所示,咱们发如今只有在数据库级别的S锁,而没有在表级别或者更低级别的锁,这是由于在Read Committed级别下,S锁在语句执行完之后就被释放。
query2:事务2
--step6:开启第二个事务
BEGIN TRAN tran2;
--step7:修改VacationHours,须要得到排它锁X,在VacationHours上没有有S锁
UPDATE Employee
SET VacationHours = VacationHours - 8
WHERE EmployeeID = 4;
--step8:查看当前加锁状况
SELECT request_session_id, resource_type, resource_associated_entity_id,
request_status, request_mode, resource_description
FROM sys.dm_tran_locks
在开启另一个update事务之后,咱们再去查看当前的锁情况,以下图所示,咱们发如今表(Object)级别上加了IX锁,在这张表所在的Page上也加了IX锁,由于表加了汇集索引,因此在叶子结点上加了X锁,这个锁的类型是KEY。
而后咱们回到事务1当中再次执行查询语句,咱们会发现查询被阻塞,咱们新建一个查询query3来查看这个时候的锁情况,其查询结果以下,咱们能够发现查询操做须要在KEY级别上申请S锁,在Page和表(Object)上面申请IS锁,可是由于Key上面原先有了X锁,与当前读操做申请的S锁冲突,因此这一步处于WAIT状态。
若是此时提交事务2的update操做,那么事务1的select操做再也不被阻塞,获得查询结果,可是咱们发现此时获得的查询结果与第一次获得的查询结果不一样,这也是为何将read committed称为不可重复读,由于同一个事物内的两次相同的查询操做的结果可能不一样。
7.3 REPEATABLE READ
REPEATABLE READ是比READ COMMITTED限制性更强的隔离级别。该级别包括READ COMMITTED,而且另外指定了在当前事务提交以前,其余任何事务均不能够修改或删除当前事务已读取的数据。并发性低于 READ COMMITTED,由于已读数据的共享锁在整个事务期间持有,而不是在每一个语句结束时释放。好比,事务1读取了一行,事务2想修改或者删除这一行而且提交,可是由于事务1还没有提交,数据行中有事务1的锁,事务2没法进行更新操做,所以事务2阻塞。若是这时候事务1想再一次读取这一行,它读取结果与第一次读取结果相同,所以叫可重复读。
实验2
query1:事务1
--step1:建立实验数据
select * into Employee from AdventureWorks.HumanResources.Employee
alter table Employee add constraint pk_Employee_EmployeeID primary key(EmployeeID)
--step2:设置隔离级别
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
--step3:开启第一个事务
BEGIN TRAN tran1
--step4:执行select操做,查看VacationHours
SELECT EmployeeID, VacationHours
FROM Employee
WHERE EmployeeID = 4;
--step5:查看当前加锁状况,发如今Employee表上面有S锁,这是由于当前的隔离界别是REPEATABLE READ
--S锁只有在事务执行完之后才会被释放.
SELECT request_session_id, resource_type, resource_associated_entity_id,
request_status, request_mode, resource_description
FROM sys.dm_tran_locks
查询锁状态的结果以下图所示,咱们发如今KEY上面加了S锁,在Page和Object上面加了IS锁,这是由于在Repeatable Read级别下S锁要在事务执行完之后才会被释放。
query2:事务2
--step6:开启第二个事务
BEGIN TRAN tran2;
--step7:修改VacationHours,须要得到排他锁X,在VacationHours上有S锁,出现冲突,因此update操做被阻塞
UPDATE Employee
SET VacationHours = VacationHours - 8
WHERE EmployeeID = 4;
执行上述update操做的时候发现该操做被阻塞,这是由于update操做要加排它锁X,而由于原先的查询操做的S锁没有释放,因此二者冲突。咱们新建一个查询3执行查询锁状态操做,发现结果以下图所示,咱们能够发现是WAIT发生在对KEY加X锁的操做上面。
此时再次执行查询1中的select操做,咱们发现查询结果跟第一次相同,因此这个叫作可重复读操做。可是可重复读操做并非特定指两次读取的数据如出一辙,Repeatable Read存在的一个问题是幻读,就是第二次读取的数据返回的条目数比第一次返回的条目数更多。
好比在Repeatable Read隔离级别下,事务1第一次执行查询select id from users where id>1 and id <10,返回的结果是2,4,6,8。这个时候事务1没有提交,那么对2,4,6,8上面依然保持有S锁。此时事务2执行一次插入操做insert into user(id) valuse(3),插入成功。此时再次执行事务1中的查询,那么返回结果就是2,3,4,6,8。这里的3就是由于幻读而出现的。所以能够得出结论:REPEATABLE READ隔离级别保证了在相同的查询条件下,同一个事务中的两个查询,第二次读取的内容确定包换第一次读到的内容。
7.4 SERIALIZABLE
SERIALIZABLE 是限制性最强的隔离级别,由于该级别锁定整个范围的键,并一直持有锁,直到事务完成。该级别包括REPEATABLE READ,并增长了在事务完成以前,其余事务不能向事务已读取的范围插入新行的限制。好比,事务1读取了一系列知足搜索条件的行。事务2在执行SQL statement产生一行或者多行知足事务1搜索条件的行时会冲突,则事务2回滚。这时事务1再次读取了一系列知足相同搜索条件的行,第二次读取的结果和第一次读取的结果相同。
重复读与幻读
重复读是为了保证在一个事务中,相同查询条件下读取的数据值不发生改变,可是不能保证下次一样条件查询,结果记录数不会增长。
幻读就是为了解决这个问题而存在的,他将这个查询范围都加锁了,因此就不能再往这个范围内插入数据,这就是SERIALIZABLE 隔离级别作的事情。
隔离级别与锁的关系
- 在Read Uncommitted级别下,读操做不加S锁;
- 在Read Committed级别下,读操做须要加S锁,可是在语句执行完之后释放S锁;
- 在Repeatable Read级别下,读操做须要加S锁,可是在事务提交以前并不释放S锁,也就是必须等待事务执行完毕之后才释放S锁。
- 在Serialize级别下,会在Repeatable Read级别的基础上,添加一个范围锁。保证一个事务内的两次查询结果彻底同样,而不会出现第一次查询结果是第二次查询结果的子集。