事务特性ACID中,隔离性最为复杂,它指的是事务与事务之间不会互相影响,一个事务的中间状态不会被其他事务感知。事务的隔离性由低到高分为:Read uncommitted 、Read committed 、Repeatable read 、Serializable。不同的隔离性在并发事务下会引起不同的读现象:脏读、不可重复读和幻读。
一、读现象及其区别
1、脏读(读取了未提交的数据)
脏读又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,但是这种修改还没有提交(commit),然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的,这个数据就是是脏数据,依据脏数据所做的操作可能是不正确的。
例子:程序员小王妻子给小王转零花钱,但是不小心按错数字了,将1K按成了7K,但是还没最后提交,这时候小王正好查看自己的零花钱,发现这个月零花钱有7K,以为老婆开恩,非常高兴。但是小王妻子发现问题,马上回滚差点就提交了的事务,但是小王看到是7K。他看到的是他老婆还没提交事务时的数据。这就是脏读。小王,瞧把你美的。
2、不可重复读(一个事务范围内两个相同的查询却返回了不同数据)
不可重复读,是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。这是由于查询时系统中其他事务修改的提交而引起的。比如事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。一种更易理解的说法是:在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复,读取数据不一样。
例子:程序员小王拿着工资卡去Happy(卡里当然只有可怜1K),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有1K,就在这个时候!小王的妻子发现小王去Happy,就把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待小王妻子转出金额事务提交完)。小王就会很郁闷,明明卡里是有钱的。小王,你还是太天真了。
3、幻读(一个事务范围内操作不完整的现象,它对应的是插入Insert操作,而不是Update操作)
幻读是事务非独立执行时发生的一种现象,它是指B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B事务的这两次读取出来的集合不一样(集合数量或者集合中的元素)。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
例子:程序员小王某一天去Happy,花了1K元,然后他的妻子去查看他今天的消费记录(全表扫描,妻子事务开启),看到确实是花了1K元,就在这个时候,小王又花了1万元,即Insert了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现小王花了1.1万元,似乎出现了幻觉,这就是幻读。小王,等着跪键盘吧。
脏读 | 某一事务读取了另一事务未提交的脏数据 |
不可重复读 | 某一事务分别读取了另一事务提交前和提交后的数据 |
幻读 | 幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是幻读强调的集合的增减(Insert),不可重复读强调的是读取数据的修改(Update) |
二、隔离级别锁实现机制
排他锁 |
被加锁的对象只能被持有锁的事务读取和修改,其他事务无法在该对象上加其他锁,也不能读取和修改该对象 |
共享锁 |
被加锁的对象可以被持锁事务读取,但是不能被修改,其他事务也可以在上面再加共享锁 特别的,对共享锁:如果两个事务对同一个资源上了共享锁,事务B想更新该数据,那么它必须等待事务A释放其共享锁 |
三、隔离级别
1、未提交读(Read uncommitted)
定义 | 未提交读是最低的隔离级别,在这种事务隔离级别下,一个事务可以读到另外一个事务未提交的数据 |
锁机制 | 采用的是一级封锁协议 a、事务在读数据的时候并未对数据加锁; b、事务在修改数据的时候只对数据增加行级共享锁,直到事务结束才释放。 |
操作逻辑 | a、事务1读取某行记录时,事务2也能对这行记录进行读取、更新(因为事务1并未对数据增加任何锁) b、当事务2对该记录进行更新时,事务1再次读取该记录,能读到事务2对该记录的修改版本(因为事务2只增加了共享读锁,事务1可以再增加共享读锁读取数据),即使该修改尚未被提交,导致“脏读” c、事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(因为事务一对数据增加了共享读锁,事务二不能增加排他锁进行数据的修改) |
缺点 | 不能避免脏读,不可重复读,幻读 |
2、提交读(Read committed)
定义 | 提交读也可以翻译成读已提交,在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据 |
锁机制 | 采用的是二级封锁协议 a、事务对当前被读取的数据加行级共享锁,一旦读完该行,立即释放该行级共享锁(非事务结束); b、事务在更新某数据的瞬间,必须先对其加行级排他锁,直到事务结束才释放。 |
操作逻辑 | a、事务1在读取某行记录的整个过程中,事务2都可以对该行记录进行读取(因为事务1对该行记录增加行级共享锁的情况下,事务2同样可以对该数据增加共享锁来读数据。)。 b、事务1读取某行的一瞬间,事务2不能修改该行数据,但是,只要事务1读取完该行数据,事务2就可以对该行数据进行修改。当事务1再次读取该行数据,并结束事务,与第一次读取的不一致,导致了不可重复读。(事务1在读取的一瞬间会对数据增加共享锁,任何其他事务都不能对该行数据增加排他锁。但是事务1只要读完该行数据,就会释放行级共享锁,一旦锁释放,事务2就可以对数据增加排他锁并修改数据) c、事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(事务1在更新数据的时候,会对该行数据增加排他锁,知道事务结束才会释放锁,所以,在事务1没有提交之前,事务2都能不对数据增加共享锁进行数据的读取。所以,提交读可以解决脏读的现象) |
缺点 | 不能避免不可重复读,幻读 |
3、可重复读(Repeatable reads)
定义 | 由于提交读隔离级别会产生不可重复读的读现象。所以,比提交读更高一个级别的隔离级别就可以解决不可重复读的问题 |
锁机制 | a、事务在读取某数据的瞬间,必须先对其加行级共享锁,直到事务结束才释放; b、事务在更新某数据的瞬间,必须先对其加行级排他锁,直到事务结束才释放。 |
操作逻辑 | a、事务1在读取某行记录的整个过程中,事务2都可以对该行记录进行读取(因为事务1对该行记录增加行级共享锁的情况下,事务2同样可以对该数据增加共享锁来读数据。)。 b、事务1在读取某行记录的整个过程中,事务2都不能修改该行数据(事务1在读取的整个过程会对数据增加共享锁,直到事务提交才会释放锁,所以整个过程中,任何其他事务都不能对该行数据增加排他锁。所以,可重复读能够解决不可重复读的读现象) c、事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(事务1在更新数据的时候,会对该行数据增加排他锁,知道事务结束才会释放锁,所以,在事务2没有提交之前,事务1都能不对数据增加共享锁进行数据的读取。所以,提交读可以解决脏读的现象) |
缺点 | 不能避免幻读 |
4、可序列化(Serializable)
定义 | 是最高的隔离级别,前面提到的所有的隔离级别都无法解决的幻读,在可序列化的隔离级别中可以解决 |
锁机制 | a、事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放; b、事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。 |
操作逻辑 | a、事务1正在读取A表中的记录时,则事务2也能读取A表,但不能对A表做更新、新增、删除,直到事务1结束。(因为事务1对表增加了表级共享锁,其他事务只能增加共享锁读取数据,不能进行其他任何操作) b、事务1正在更新A表中的记录时,则事务2不能读取A表的任意记录,更不可能对A表做更新、新增、删除,直到事务1结束。(事务1对表增加了表级排他锁,其他事务不能对表增加共享锁或排他锁,也就无法进行任何操作) |
备注 | a、无法读取其它事务已修改但未提交的记录。 b、在当前事务完成之前,其它事务不能修改目前事务已读取的记录。 c、在当前事务完成之前,其它事务所插入的新记录,其索引键值不能在当前事务的任何语句所读取的索引键范围中。 |
5、综述
隔离级别 | 脏读 | 不可重复读 | 幻读 |
Read uncommitted | Yes | Yes | Yes |
Read committed | No | Yes | Yes |
Repeatable reads | No | No | Yes |
Serializable | No | No | No |
特别说明:大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。Mysql的默认隔离级别是Repeatable read。