当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性, 所以, 对于不同的事务,采用不同的隔离级别 会 有不同的结果。
如果不考虑事务的隔离性, 那么 会发生 下表所示的 3 种问题:
不可重复读 是由于 事务并发修改 同一条 记录 导致的 ,要避免这种情况,最简单的方法就是对要修改的记录加锁,这 会 导致锁竞争加剧 ,影响性能。另一种方法是通过 MVCC 可以在无锁的情况下,避免不可重复读。
幻读是由于并发事务增加记录导致的,这个不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读。
在 SQL 标准中定义了 4 种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些是在事务内和事务间可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。 SQL 标准定义的四个隔离级别为: Read Uncommitted ( 未提交读 ) 、 Read Committed (提交读)、 Repeatable Read (可重复读)、 Serializable (可串行化) ,下面分别介绍。
不同的隔离级别有不同的现象,并有不同的锁 和 并发机制,隔离级别越高,数据库的并发性 能 就越差, 4 种事 隔离级别与并发性能的关系:
如果没有事务隔离,会出现什么样的情况?
假设我们现在有这样一张表(T),里面记录了很多牛人的名字,我们不进行事务的隔离看看会发生什么呢?
第一天,事务A访问了数据库,它干了一件事情,往数据库里加上了新来的牛人的名字,但是没有提交事务。
insert into T values (4, '牛D');
这时,来了另一个事务B,他要查询所有牛人的名字。
select Name from T;
这时,如果没有事务之间没有有效隔离,那么事务B返回的结果中就会出现“牛D”的名字。这就是“脏读(dirty read)”。
第二天,事务A访问了数据库,他要查看ID是1的牛人的名字,于是执行了
select Name from T where ID = 1;
这时,事务B来了,因为ID是1的牛人改名字了,所以要更新一下,然后提交了事务。
update T set Name = '不牛' where ID = 1;
接着,事务A还想再看看ID是1的牛人的名字,于是又执行了
select Name from T where ID = 1;
结果,两次读出来的ID是1的牛人名字竟然不相同,这就是不可重复读(unrepeatable read)。
第三天,事务A访问了数据库,他想要看看数据库的牛人都有哪些,于是执行了
select * from T;
这时候,事务B来了,往数据库加入了一个新的牛人。
insert into T values(4, '牛D');
这时候,事务A忘了刚才的牛人都有哪些了,于是又执行了。
select * from T;
结果,第一次有三个牛人,第二次有四个牛人。
相信这个时候事务A就蒙了,刚才发生了什么?这种情况就叫“幻读(phantom problem)”。
为了防止出现脏读、不可重复读、幻读等情况,我们就需要根据我们的实际需求来设置数据库的隔离级别。下面介绍下这方面内容。
数据库事务隔离级别
数据库事务隔离级别分为四种(级别递减):
1、Serializable (串行化):最严格的级别,事务串行执行,资源消耗最大;
2、REPEATABLE READ(重复读) :保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但不能避免“幻读”,但是带来了更多的性能损失。
3、READ COMMITTED (提交读):大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,但不能避免“幻读”和“不可重复读取”。该级别适用于大多数系统。
4、Read Uncommitted(未提交读) :事务中的修改,即使没有提交,其他事务也可以看得到,会导致“脏读”、“幻读”和“不可重复读取”。
脏读、不可重复读、幻读:
一个数据库可能拥有多个访问客户端,这些客户端并发访问数据库时,若没有采取必要的隔离措施,存在以下问题,这些问题分为5类,包括3类数据读问题:脏读、不可重复读和幻读。两类数据更新问题:第一类丢失更新、第二类丢失更新。
1、脏读
A事务读取B事务尚未提交的更改数据,并在这个数据的基础上进行操作,这时候如果事务B回滚,那么A事务读到的数据是不被承认的。例如常见的取款事务和转账事务:
2、不可重复读
不可重复读是指A事务读取了B事务已经提交的更改数据。假如A在取款事务的过程中,B往该账户转账100,A两次读取的余额发生不一致。
3、幻读
A事务读取B事务提交的新增数据,会引发幻读问题。幻读一般发生在计算统计数据的事务中,例如银行系统在同一个事务中两次统计存款账户的总金额,在两次统计中,刚好新增了一个存款账户,存入了100,这时候两次统计的总金额不一致。
注意:不可重复读和幻读的区别是:前者是指读到了已经提交的事务的更改数据(修改或删除),后者是指读到了其他已经提交事务的新增数据。对于这两种问题解决采用不同的办法,防止读到更改数据,只需对操作的数据添加行级锁,防止操作中的数据发生变化;二防止读到新增数据,往往需要添加表级锁,将整张表锁定,防止新增数据(oracle采用多版本数据的方式实现)。
4、通俗解释:
脏读:所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。
也就是说,当前事务读到的数据是别的事务想要修改成为的但是没有修改成功的数据。
不可重复读:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。
也就是说,当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配,也就照应了不可重复读的语义。
幻读:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。
也就是说,当前事务读第一次取到的数据比后来读取到数据条目少。
不可重复读和幻读比较:
两者有些相似,但是前者针对的是update或delete,后者针对的insert。
为什么会出现“脏读”?因为没有“select”操作没有规矩。
为什么会出现“不可重复读”?因为“update”操作没有规矩。
为什么会出现“幻读”?因为“insert”和“delete”操作没有规矩。
“读未提(Read Uncommitted)”能预防啥?啥都预防不了。
“读提交(Read Committed)”能预防啥?使用“快照读(Snapshot Read)”,避免“脏读”,但是可能出现“不可重复读”和“幻读”。
“可重复读(Repeated Red)”能预防啥?使用“快照读(Snapshot Read)”,锁住被读取记录,避免出现“脏读”、“不可重复读”,但是可能出现“幻读”。
“串行化(Serializable)”能预防啥?排排坐,吃果果,有效避免“脏读”、“不可重复读”、“幻读”,不过效果谁用谁知道。