并发异常,事务隔离级别,锁

事务准备接受不一致数据的级别称为隔离级别。隔离级别是一个事务必须与其它事务进行隔离的程度。较低的隔离级别可以增加并发,但代价是降低数据的正确性。相反,较高的隔离级别可以确保数据的正确性,但可能对并发产生负面影响。应用程序要求的隔离级别确定了所使用的锁定行为:

数据库在被广大客户所共享访问的操作过程中很可能出现以下几种不确定情况 :

1. 脏读取(Dirty Reads):一个事务开始读取了某行数据但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的。

假设事务T2中事务T1提交前读取了T1写过的数据项Balance(存款),在事务T1提交前就释放了Balance上的写锁时就可能发生这种问题:由于事务T2读取的Balance是一个没有提交的事务修改后的值,而这个值可能不会最终出现在数据库中。

2. 不可重复读取(Non-repeatable Reads):一个事务对同一行数据重复读取两次但是却得到了不同结果。例如在两次读取中途有另外一个事务对该行数据进行了修改并提交。

例如:假设List是一个乘客列表,Count是这个列表的乘客数量。

T1读取List-> T2读取List –> T2预订了一个座位 –> T2读取了Count –> T2将Count值加1 –> T2提交 –> T1读取Count。

事务T1看到的是一个新乘客加入前的List和新乘客加入后的Count,出现了不一致问题。

3. 更新丢失(lost updates problem):无法重复读取特例,有两个并发事务同时读取同一行数据然后其中一个对它进行修改提交而另一个也进行了修改提交这就会造成第一次写操作失效。

例如:T1读取Balance –》 T2读取Balance –》 T2存款后计算了更新了Balance –》 T2提交 –》 T1存款后根据T1读取的Balance为基准更新了Balance –》 T1提交

在最终的Balance中,事务T2的存款没有体现出来,事务T2的更新丢失了。

 

4. 幻读(Phantom Reads):也称为幻像(幻影)。事务在操作过程中进行两次查询,第二次查询结果包含了第一次查询中未出现的数据(这里并不要求两次查询SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

如:有两个表,Accounts表,其中每个元组对应银行中的一个账户;Depositors表,其中每个元组对应一个存款人。Depositors表中有一个TotalBalance属性,指的是一个特定存款人所有账户的余额总和。

有两个事务访问这张表。一个对应Mary的审计事务T1,先从Account表中查询出Mary的所有账户的余额总和。

select sum(balance) from accounts a where a.name = ‘Mary’

然后,把这个Select语句的返回值与下面的Select语句返回值进行比较:

select d.TotalBalance from depositors d where d.name = ‘Mary’

同时还有一个并发执行的事务T2,为Mary创建一个新账户。它为Mary创建了一个初始余额为100的新账户,用如下语句向Accounts表中增加一个元组:

insert into accounts values(‘10021’, ‘Mary’, 100)

然后用如下语句更新Depositors表中恰当的元组,将Mary的TotalBalance值加100

update depositors set TotalBalance = TotalBalance + 100 where name = ‘Mary’

假设事务T1在执行第一个Select语句时获取了Accounts表中Mary账户对应的元组上的锁,但是这不能防止事务T2向这个表中插入一个全新的元组。所以,对元组枷锁不能保证生成可串行化的调度。然后,对表枷锁可以方式生成不满足可串行化的调度,因为它完全控制了对表的访问。

这个问题的根源是:事务T1认为它已经锁柱了Name=‘Mary’的所有元组,但事务T2修改了Name=‘Mary’对应的元组集。这个新的元组就称为一个幻像。幻像可能导致生成非可串行化调度,因此也就可能生成错误结果。

 

为了避免上面出现几种情况在标准SQL规范中定义了4个事务隔离级别,不同隔离级别对事务处理不同 。

1.读未提交(Read Uncommitted):允许脏读取,如果一个事务已经开始写数据则另外一个数据则不允许同时进行写操作但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现 。事务隔离的最低级别,仅可保证不读取物理损坏的数据。与READ COMMITTED 隔离级相反,它允许读取已经被其它用户修改但尚未提交确定的数据。

2. 读已提交(Read Committed):允许不可重复读取但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现,读取数据的事务允许其他事务继续访问该行数据,但是未提交写事务将会禁止其他事务访问该行 。SQL Server 默认的级别。在此隔离级下,SELECT 命令不会返回尚未提交(Committed) 的数据,也不能返回脏数据。

3. 可重复读(Repeatable Read):禁止不可重复读取和脏读取。但是有时可能出现幻影数据,这可以通过“共享读锁”和“排他写锁”实现,读取数据事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务 。在此隔离级下,用SELECT 命令读取的数据在整个命令执行过程中不会被更改。此选项会影响系统的效能,非必要情况最好不用此隔离级。

4. 序列化/可串行化(Serializable):提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作事务访问到 。事务隔离的最高级别,事务之间完全隔离。如果事务在可串行读隔离级别上运行,则可以保证任何并发重叠事务均是串行的。

隔离级需要使用SET 命令来设定其语法如下:
SET TRANSACTION ISOLATION LEVEL
{READ COMMITTED
| READ UNCOMMITTED
| REPEATABLE READ
| SERIALIZABLE }
下面是四种隔离级别允许不同类型的行为。
隔离级别    脏读    不可重复读取    幻像(幻影)
读未提交    是       是                 是
读已提交    否       是                 是
可重复读    否       否                 是
可串行化    否       否                 否

隔离级别越高越能保证数据完整性和一致性,但是对并发性能影响也越大。对于多数应用程序,可以优先考虑把数据库系统隔离级别设为Read Committed ,它能够避免脏读取而且具有较好并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制 。

锁(Lock) 是在多用户环境下对资源访问的一种限制。机制当对一个数据源加锁后,此数据源就有了一定的访问限制。我们就称对此数据源进行了“锁定”。在SQL Server中,可以对以下的对象进行锁定:
数据行(Row):数据页中的单行数据;
索引行(Key):索引页中的单行数据,即索引的键值;
页(Page):页是SQL Server 存取数据的基本单位,其大小为8KB;
盘区(Extent):一个盘区由8 个连续的页组成;
表(Table);
数据库(Database)。

在SQL Server 中,锁有两种分类方法。
(1) 从数据库系统的角度来看
锁分为以下三种类型:
1.独占锁(Exclusive Lock)
独占锁锁定的资源只允许进行锁定操作的程序使用,其它任何对它的操作均不会被接受。执行数据更新命令,即INSERT、 UPDATE 或DELETE 命令时,SQL Server 会自动使用独占锁。但当对象上有其它锁存在时,无法对其加独占锁。独占锁一直到事务结束才能被释放。
2.共享锁(Shared Lock)
共享锁锁定的资源可以被其它用户读取,但其它用户不能修改它。在SELECT 命令执行时,SQL Server 通常会对对象进行共享锁锁定。通常加共享锁的数据页被读取完毕后,共享锁就会立即被释放。
3.更新锁(Update Lock)
更新锁是为了防止死锁而设立的。当SQL Server 准备更新数据时,它首先对数据对象作更新锁锁定,这样数据将不能被修改,但可以读取。等到SQL Server 确定要进行更新数据操作时,它会自动将更新锁换为独占锁。但当对象上有其它锁存在时,无法对其作更新锁锁定。
2)从程序员的角度看
锁分为以下两种类型:
1.乐观锁(Optimistic Lock)
乐观锁假定在处理数据时,不需要在应用程序的代码中做任何事情就可以直接在记录上加锁、即完全依靠数据库来管理锁的工作。一般情况下,当执行事务处理时SQL Server会自动对事务处理范围内更新到的表做锁定。
2.悲观锁(Pessimistic Lock)
悲观锁对数据库系统的自动管理不感冒,需要程序员直接管理数据或对象上的加锁处理,并负责获取、共享和放弃正在使用的数据上的任何锁。

锁定优化程序提示及其描述
优化程序提示 优化程序提示描述
holdlock 保持锁定直到事务结束
nolock 检索数据时不使用锁
paglock 使用页面锁
tablock 使用表锁
tablockx 使用独占表锁
updlock 使用更新锁  

如: SELECT *    FROM authors (paglock holdlock index=aunmind) 
死锁及其防止:

死锁(Deadlocking) 是在多用户或多进程状况下,为使用同一资源而产生的无法解决的争用状态,通俗地讲,就是两个用户各占用一个资源,两人都想使用对方的资源,但同时又不愿放弃自己的资源,就一直等待对方放弃资源,如果不进行外部干涉,就将一直耗下去。
死锁会造成资源的大量浪费,甚至会使系统崩溃。在SQL Server 中解决死锁的原则是“牺牲一个比两个都死强”,即挑出一个进程作为牺牲者,将其事务回滚,并向执行此进程的程序发送编号为1205 的错误信息。而防止死锁的途径就是不能让满足死锁条件的情况发生,为此,用户需要遵循以下原则:
尽量避免并发地执行涉及到修改数据的语句;
要求每个事务一次就将所有要使用的数据全部加锁,否则就不予执行;
预先规定一个封锁顺序所有的事务,都必须按这个顺序对数据执行封锁,例如,不同的过程在事务内部对对象的更新执行顺序应尽量保持一致;
每个事务的执行时间不可太长,对程序段长的事务可考虑将其分割为几个事务。

原文地址:https://www.cnblogs.com/henryhappier/p/1800965.html