隔离性(Isolation):与数据库中的事务隔离级别以及锁相关,多个用户可以对同一数据并发访问而又不破坏数据的正确性和完整性。但是,并行事务的修改必须与其它并行事务的修改相互独立,隔离。 但是在不同的隔离级别下,事务的读取操作可能得到的结果是不同的。

 隔离级别用于决定如何控制并发用户读写数据的操作。前面说到,读操作默认使用共享锁,写操作需要使用排它锁。对于操作获得的锁,以及锁的持续时间来说,虽然不能控制写操作的处理方式,但可以控制读操作的处理方式。作为对读操作的行为进行控制的一种结果,也会隐含地影响写操作的行为方式。

  为此,可以在会话级别上用会话选项来设置隔离级别,也可以在查询级别上用表提示(Table Hint)来设置隔离级别。

  在SQL Server中,可以设置的隔离级别有6个:READ UNCOMMITED(未提交读)、READ COMMITED(已提交读)、REPEATABLE READ(可重复读)、SERIALIZEABLE(可序列化)、SNAPSHOT(快照)和READ COMMITED SNAPSHOT(已经提交读隔离)。最后两个SNAPSHOT和READ COMMITED SNAPSHOT是在SQL 

 

 

1、READ UNCOMMITTED 造成数据脏的问题。如下进行数据脏的模拟

首先打开连接查询窗口A,执行如下代码.

sqlite3脏读 sql server 脏读_sqlite3脏读

打开查询窗口B执行代码查询的结果是20

 

sqlite3脏读 sql server 脏读_排它锁_02

在查询窗口A 未提交的事务进行回滚

sqlite3脏读 sql server 脏读_数据_03

再次执行窗口B的代码结果

sqlite3脏读 sql server 脏读_排它锁_04

总结:在读取到未提交的事务数据,由于各种原因造成事务回滚,此时读取到的数据为无效的数据即脏数据。

 

2 、READ COMMITTED 解决数据脏的问题 ,但避免不了 不可重复读、更新值丢失的问题,原因是对数据的修改需要排它锁,对数据的查询需要共享锁,而二者相互排斥,读操作(select )在读取有排他锁的数据时,会一直阻塞,直到排它锁被释放(修改数据的事务提交或者回滚)后,获取到共享锁后才能查询到数据。

  •  a、 解决数据脏的问题

执行查询窗口A  修改数据事务,该事务未提交

sqlite3脏读 sql server 脏读_共享锁_05

 

执行窗口B ,查询被窗口A 修改的数据,并一直处于阻塞中(等待排他锁释放后获取共享锁)

sqlite3脏读 sql server 脏读_排它锁_06

在执行窗口A 提交事务(释放掉排它锁)


sqlite3脏读 sql server 脏读_排它锁_07

窗口B的查询结果如下(获取共享锁后,可以获取查询的数据)

sqlite3脏读 sql server 脏读_排它锁_08

 

  • b、不可重复读问题

    打开连接窗口A ,把断点放到第二个查询语句上

sqlite3脏读 sql server 脏读_排它锁_09

打开连接窗口B ,对upload 值进行修改为200

sqlite3脏读 sql server 脏读_共享锁_10

再次打开连接窗口A 继续执行断点后打代码,结果如下图(在同一个事务内多次查询,竟让出现两种查询结果)

sqlite3脏读 sql server 脏读_数据_11

问题原理分析:执行第一个select操作后就会把共享锁释放掉,断点停在第二个select 语句上等待执行(在执行时才能获取共享锁),而此时的修改操作就可以获取排他锁,对数据进行修改,然后提交事务释放排它锁,最后停在第二行上的select语句获取互斥锁后继续执行,因而就会查询多个结果的情况,即不可重复读到同一个结果。 如果把共享锁的生命周期给当前事务保持一致,那么修改操作就不会获得排它锁(共享锁和排它锁是互斥的),因而数据就可以重复读(多次查询的结果一致)了。

 

  • c.更新值丢失的问题

设置upload 的值是0

sqlite3脏读 sql server 脏读_数据_12

 

打开连接窗口A ,执行如下把断点定位到更新(update)语句上,本例采用断点的方式去模拟,正常运行的情况下 和并发执行的次数多少相关,并不是每一次都造成 更新值丢失的问题。

sqlite3脏读 sql server 脏读_sqlite3脏读_13

打开连接窗口B,执行如下

sqlite3脏读 sql server 脏读_共享锁_14

在打开连接窗口A 顺着断点继续执行,得到结果和窗口B的结果一样,明明对upload值两次加1,正确的结果应该是2

sqlite3脏读 sql server 脏读_数据_15

原理分析:由于事务对锁的控制没有和事务生命周期一致,导致查询结束后释放对共享锁的控制,而此时另一个事务的修改操作,获取排它锁,并对数据进行了修改 ,导致第一个事务查询出的值是更新前的值 。窗口A 在执行Update 语句前已经查询到upload的值(1) 并释放掉了共享锁,正当执行update 操作时,这时另外一个事务(连接窗口B)抢先获取了排它锁,这时窗口A的update 操作就要阻塞等待(等待另一个事务释放排它锁),窗口B提交事务并释放排它锁,窗口A 的update 操作继续执行,并对获取的值进行加1,这就造成了,两个事务对同一个值进行加1 操作,只成功一个的问题。REPEATABLE READ 可重复读 可以解决此问题,但容易造成死锁。

 

3. REPEATABLE READ 可重复读 ,可以解决不可重复读的问题。也可以解决数据更新值丢失的问题(但负面影响会造成死锁);缺点是无法避免幻读的问题。可重复读 获取共享锁的生命周期是同事务的生命周期一致。只要事务不提交,将一直保持着该共享所,随意其它事务将无法获取排它锁,进而无法对数据进行修改。

打开连接窗口A 执行可重复读隔离级别的查询操作

sqlite3脏读 sql server 脏读_排它锁_16

打开连接窗口B、执行修改操作,由于窗口A的事务没有提交或回滚事务,因而造成窗口B事务无法获取排它锁,并一直处于阻塞等待中

sqlite3脏读 sql server 脏读_sqlite3脏读_17

打开窗口A 执行事务提交,释放共享锁

sqlite3脏读 sql server 脏读_数据_18

窗口B 获取排它锁,修改操作执行成功

 

 

sqlite3脏读 sql server 脏读_数据_19

 

  • a、幻读:在可重复读级别下运行的事务,读操作获得的共享锁将一直保持到事务结束。因此可以保证在事务中第一次读取某些行后,还可以重复读取这些行。但是,事务只锁定查询第一次运行时找到的那些行,而不会锁定查询结果范围外的其他行。因此,在同一事务进行第二次读取之前,如果其他事务插入了新行,而且新行也能满足读操作额查询过滤条件,那么这些新行也会出现在第二次读操作返回的结果中。这些新行称之为幻影,这种读操作也被称为幻读

打开连接窗口A,把断点定位到 第二个select 语句上

sqlite3脏读 sql server 脏读_共享锁_20

 

打开窗口B,插入一条 数据过滤条件和第一个事务的查询条件一致(customerid ='CM0000055')

sqlite3脏读 sql server 脏读_数据_21

 

打开窗口A继续执行剩下的sql 语句,第二次查询结果比第一次查询结果要多一条数据这就是幻读

sqlite3脏读 sql server 脏读_sqlite3脏读_22

原理总结:可重复读会在满足过滤条件的数据上都加上过滤锁,但是无法在新添加的数据上添加共享锁,因而隔离级别可重复读无法避免幻读的问题,SERIALIZEABLE 可序列化 的隔离模式可以解决该问题。

4、SERIALIZEABLE 可序列化:  为了避免刚刚提到的幻读,需要将隔离级别设置为可序列化。可序列化级别的处理方式与可重复读类似:读操作需要获得共享锁才能读取数据并一直保留到事务结束,不同之处在于在可序列化级别下,读操作不仅锁定了满足查询条件的那些行,还锁定了可能满足查询条件的行。换句话说,如果其他事务试图增加能够满足操作的查询条件的新行,当前事务就会阻塞这样的操作。

打开窗口A,断点定位到第二条查询语句上

sqlite3脏读 sql server 脏读_sqlite3脏读_23

打开连接窗口B 添加一条满足过滤条件的数据,由于SERIALIZABLE 对满足过滤条件的数据进行加锁,因而无法获取排锁,因而无法只能阻塞等待!

sqlite3脏读 sql server 脏读_数据_24

原理分析:新添加的数据如果满足过滤条件,则被视为对该添加的数据为加锁数据。