数据库事务的隔离级别有4种,由低到高分别为Read uncommitted(读未提交) 、Read committed(读提交) 、Repeatable read(可重复读) 、Serializable(序列化/串行化) 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。
脏读、不可重复读、幻象读概念说明:
脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
幻象读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)
一、其他数据库四种隔离级别:
1、读未提交 (Read uncommitted)
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。
事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。
分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。如无特殊情况,基本是不会使用这种隔离级别的。
那怎么解决脏读呢?Read committed!读提交,能解决脏读问题。
2、读提交(Read Committed)
读提交,顾名思义,就是只能读到已经提交了的内容
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…
分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。
这是各种系统中最常用的一种隔离级别,也是SQL Server和Oracle的默认隔离级别。这种隔离级别能够有效的避免脏读,但除非在查询中显示的加锁,如:
select * from T where ID=2 lock in share mode;
select * from T where ID=2 for update;
不然,普通的查询是不会加锁的。
那为什么“读提交”同“读未提交”一样,都没有查询加锁,但是却能够避免脏读呢?
这就要说道另一个机制“快照(snapshot)”,而这种既能保证一致性又不加锁的读也被称为“快照读(Snapshot Read)”
假设没有“快照读”,那么当一个更新的事务没有提交时,另一个对更新数据进行查询的事务会因为无法查询而被阻塞(因为上了X锁,即写锁,所以不能得到S锁,即读锁),这种情况下,并发能力就相当的差。而“快照读”就可以完成高并发的查询,不过,“读提交”只能避免“脏读”,并不能避免“不可重复读”和“幻读”。
那怎么解决可能的不可重复读问题?Repeatable read !
3、可重复读(Repeated Read)
可重复读,顾名思义,就是专门针对“不可重复读”这种情况而制定的隔离级别,自然,它就可以有效的避免“不可重复读”。而它也是MySql的默认隔离级别。
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。
分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。
什么时候会出现幻读?
事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。
在这个级别下,普通的查询同样是使用的“快照读”,但是,和“读提交”不同的是,当事务启动时,就不允许进行“修改操作(Update)”了,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改,因此,“可重复读”能够有效的避免“不可重复读”,但却避免不了“幻读”,因为幻读是由于“插入或者删除操作(Insert or Delete)”而产生的。
那怎么解决幻读问题?Serializable!
4、序列化 Serializable
这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。这种级别下,“脏读”、“不可重复读”、“幻读”都可以被避免,但是执行效率奇差,性能开销也最大,所以基本没人会用。
值得一提的是:大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。Mysql的默认隔离级别是Repeatable read。
二、达梦数据库事务隔离级别:
达梦数据库支持读未提交、读提交和序列化(串行化)三种隔离级。
1、读未提交:
设定事务为读未提交隔离级:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;[1]
此外,DM 还允许用户在 SELECT 语句的末尾加上 WITH UR 或 ?[2] 以指定当前查询语句的隔离级为读未提交,即允许脏读,并在该语句结束时自动恢复为原来的隔离级。
旧版本可能此方法不能使用
例 :
会话 1 创建表 T1,插入一行数据且不提交,会话 2 查询表 T1,因为缺省的事务隔离级为读提交,此时查询不到数据,但是当会话 2 在 SELECT 语句末尾加上 WITH UR 时,可以查询到会话 1 插入的还未提交的数据。
--会话 1 执行
create table T1 (c1 int);
select * from T1;
insert into T1 values (1);
--会话 2执行
select * from T1;
select * from T1 WITH UR;
--设置数据库隔离级为读未提交后:
--对会话1插入数据先进行提交,会话3可以查询到数据,会话1再次插入数据:
insert into T1 values (2);
--会话3设置完数据库隔离级后可查询到插入但未提交数据:
2、读提交:
DM 数据库的读提交隔离可以确保只访问到已提交事务修改的数据,保证数据处于一致
性状态,能够满足大多数应用的要求,并最大限度的保证系统并发性能,但可能会出现不可
重复读取和幻像读。
用户可以在事务开始时使用以下语句设定事务为读提交隔离级:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
3、串行化隔离级别:
在要求消除不可重复读取或幻像读的情况下,我们可以设置事务隔离级为串行化。跟读
提交隔离级相比,串行化事务的查询本身不会增加任何代价,但修改数据可能引发“串行化
事务被打断”错误。
具体来说,当一个串行化事务试图更新或删除数据时,而这些数据在此事务开始后被其
他事务修改并提交时,DM 数据库将报“串行化事务被打断”错误。应用开发者应该充分考
虑串行化事务带来的回滚及重做事务的开销,从应用逻辑上避免对相同数据行的激烈竞争导
致产生大量事务回滚。并结合应用逻辑,捕获“串行化事务被打断”错误,进行事务重做等
相应处理。如果系统中存在长时间运行的写事务,并且该长事务所操作的数据还会被其他短
事务频繁更新的话,最好避免使用串行化事务。
用户可以在事务开始时使用以下语句设定事务为串行化隔离级:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;