您的位置:首页 > 其它

锁与事务处理

2016-03-10 17:22 197 查看
1.理解ACID特性

1.1A(Atomicity)原子性

事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。

打个比方,张三从他的工行卡里转账10万元到农行卡.执行这个过程,如果不计手续费,只有下列两种情况:

A:转账失败,工行卡和农行卡的金额保持不变

B:转账成功,以下两个语句组为一个单元执行完成。

1)UPDATEGongHangsavings

SETBalance=Balance-100,000

WHEREAccount=‘zhangsan'

2)UPDATENongHangsavings

SETBalance=Balance+100,000

WHEREAccount=‘zhangsan'

1.2C(Consistency)一致性

事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如B树索引或双向链表)都必须是正确的。

一致性要求只有有效的数据才能被写入。一个事务成功执行后,数据库的状态也必须满足原来的规则。这里包含两个层面:

1)约束规则:如果在数据库字段上建立了约束规则,如果事务的执行违反了数据库的约束规则,那么整个事务将会回滚,数据库将恢复到原来的状态,确保符合约束规则.

2)业务逻辑规则:即张三的资金总额要保持不变,张三工行卡的余额或者不变,或者-100,000,于此对应农行卡余额或者不变,或者+100,000。

数据库事务的一致性原则好比一把保护伞,这是一个守规则的世界,任何试图入侵的外来力量都无法破坏已经约定的规则.生活在数据库中的数据在一致性原则的保护下是幸福的.如果现实社会中,有那么一种保护机制,能够确保弱势群体的利益永不受侵犯,任何强大的利益集团都无法入侵就好了.

1.3I(Isolation)隔离性

由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务识别数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是第二个事务修改它之后的状态,事务不会识别中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。

隔离性确保了多个事务的独立性,.NET中的AppDomain和事务的隔离性是一致的,确保了不同的应用程序相互不干扰.

1.4D(Durability)持久性

事务完成之后,它对于系统的影响是永久性的。该修改即使出现系统故障也将一直保持。

对于这个特性,我更倾向于把它理解成数据库的特性,而非作为事务特性来理解。数据库就是用来持久存放关系数据的。

2事务(Transaction)

在谈及事务之前,考虑以下情形:

张三从他的工行卡里转账10万元到农行卡.这个场景中,我们需要执行下列一个过程:

1)UPDATEGongHangsavings

SETBalance=Balance-100,000

WHEREAccount=‘zhangsan'

2)UPDATENongHangsavings

SETBalance=Balance+100,000

WHEREAccount=‘zhangsan'

如果交易过程中,只执行了1),那么结果怎样?张三平白无故地损失了100,000元.显然张三无法接受这个事实.

为解决上述场景,事务登场了.

事务是作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元必须有四个属性,称为原子性、一致性、隔离性和持久性(ACID)属性,只有这样才能成为一个事务。如果某一事务成功,则在该事务中进行的所有数据更改均会提交,成为数据库中的永久组成部分。如果事务遇到错误且必须取消或回滚,则所有数据更改均被清除。

2.1完整的事务

BEGINatransaction:设置事务的起始点

COMMITatransaction:提交事务,使事务提交的数据成为持久,不可更改的部分.

ROLLBACKatransaction:撤消一个事务,使之成为事务开始前的状态.

SAVEatransaction:建立一个标签,做为部分回滚时使用,使之恢复到标签初的状态.

2.1.1语法:

BEGINTRAN[SACTION][<transactionname>|<@transactionvariable>][WITHMARK[’<description>’]][;]

COMMIT[TRAN[SACTION][<transactionname>|<@transactionvariable>]][;]

ROLLBACKTRAN[SACTION][<transactionname>|<savepointname>|<@transactionvariable>|<@savepointvariable>][;]

SAVETRAN[SACTION][<savepointname>|<@savepointvariable>][;]

2.1.2一个完整的例子:



USEAdventureWorks2008;--We’remakingourowntable-whatDBdoesn’tmatter
--Createtabletoworkwith
CREATETABLEMyTranTest
(
OrderIDINTPRIMARYKEYIDENTITY
);
--Startthetransaction
BEGINTRANTranStart;
--Insertourfirstpieceofdatausingdefaultvalues.
--ConsiderthisrecordNo1.Itisalsothe1strecordthatstays
--afteralltherollbacksaredone.
INSERTINTOMyTranTest
DEFAULTVALUES;
--Createa"Bookmark"tocomebacktolaterifneedbe
SAVETRANFirstPoint;
--Insertsomemoredefaultdata(thisonewilldisappear
--aftertherollback).
--ConsiderthisrecordNo2.
INSERTINTOMyTranTest
DEFAULTVALUES;
--Rollbacktothefirstsavepoint.Anythinguptothat
--pointwillstillbepartofthetransaction.Anything
--beyondisnowtoast.
ROLLBACKTRANFirstPoint;
INSERTINTOMyTranTest
DEFAULTVALUES;
--Committhetransaction
COMMITTRANTranStart;
--Seewhatrecordswerefinallycommitted.
SELECTTOP2OrderID
FROMMyTranTest
ORDERBYOrderIDDESC;
--Cleanupafterourselves
DROPTABLEMyTranTest;


2.2隐式事务

2.2.1定义

无须描述事务的开始,只需提交或回滚每个事务。隐性事务模式生成连续的事务链。

为连接将隐性事务模式设置为打开之后,当数据库引擎实例首次执行下列任何语句时,都会自动启动一个事务:

CREATE

ALTERTABLE

GRANT

REVOKE

SELECT

UPDATE

DELETE

INSERT

TRUNCATETABLE

DROP

OPEN

FETCH

在发出COMMIT或ROLLBACK语句之前,该事务将一直保持有效。在第一个事务被提交或回滚之后,下次当连接执行以上任何语句时,数据库引擎实例都将自动启动一个新事务。该实例将不断地生成隐性事务链,直到隐性事务模式关闭为止。

为了更好理解上述内容,举个例子:



1SETQUOTED_IDENTIFIEROFF;
2GO
3SETNOCOUNTOFF;
4GO
5USETestDB;
6GO
7CREATETABLEImplicitTran
8(Colbchar(3)NOTNULL);
9GO
10SETIMPLICIT_TRANSACTIONSON;
11GO
12
13BEGINtransaction
14SAVEtransactionA
15INSERTINTOImplicitTranVALUES('AAA');
16ROLLBACKTRANSACTIONA
17
18INSERTINTOImplicitTranVALUES('BBB');
19COMMITtransaction


在INSERTINTOImplicitTranVALUES('BBB')时,已经隐式创建一个transaction,直到提交或回滚.

默认情况下,隐性事务模式设置为关闭。

开启隐性事务模式语法:

SETIMPLICIT_TRANSACTIONSON;

2.2.2一个隐式事务例子

隐式事务开始后,可以使用COMMITTRANSACTION或ROLLBACKTRANSACTION语句可以结束每个事务。



1SETQUOTED_IDENTIFIEROFF;
2GO
3SETNOCOUNTOFF;
4GO
5USEAdventureWorks2008R2;
6GO
7CREATETABLEImplicitTran
8(ColaintPRIMARYKEY,
9Colbchar(3)NOTNULL);
10GO
11SETIMPLICIT_TRANSACTIONSON;
12GO
13--FirstimplicittransactionstartedbyanINSERTstatement.
14INSERTINTOImplicitTranVALUES(1,'aaa');
15GO
16INSERTINTOImplicitTranVALUES(2,'bbb');
17GO
18--Commitfirsttransaction.
19COMMITTRANSACTION;
20GO
21--SecondimplicittransactionstartedbyaSELECTstatement.
22SELECTCOUNT(*)FROMImplicitTran;
23GO
24INSERTINTOImplicitTranVALUES(3,'ccc');
25GO
26SELECT*FROMImplicitTran;
27GO
28--Commitsecondtransaction.
29COMMITTRANSACTION;
30GO
31SETIMPLICIT_TRANSACTIONSOFF;
32GO
33


注意:使用隐式事务是一个需要注意的领域,一般情况下,建议设置为关闭隐式事务。

2.3题外话--几种常用的事务

2.3.1SqlServer数据库级别事务



2.3.2ADO.NET级别事务



SqlConnectionmyConnection=newSqlConnection(conString);
myConnection.Open();
//启动一个事务
SqlTransactionmyTrans=myConnection.BeginTransaction();
//为事务创建一个命令
SqlCommandmyCommand=newSqlCommand();
myCommand.Connection=myConnection;
myCommand.Transaction=myTrans;
try
{
myCommand.CommandText="updateP_ProductsetName='电脑2'whereId=52";
myCommand.ExecuteNonQuery();
myCommand.CommandText="updateP_ProductsetName='电脑3'whereId=53";
myCommand.ExecuteNonQuery();
myTrans.Commit();//提交
Response.Write("两条数据更新成功");
}
catch(Exceptionex)
{
myTrans.Rollback();//遇到错误,回滚
Response.Write(ex.ToString());
}
finally
{
myConnection.Close();
}


2.3.3ASP.NET页面级别事务



1<%@PageTransaction="Required"Language="C#"AutoEventWireup="true"
2CodeBehind="WebForm3.aspx.cs"Inherits="WebApplication4.WebForm3"%>
3
4
5
6protectedvoidButton1_Click(objectsender,EventArgse)
7{
8try
9{
10Work1();
11Work2();
12ContextUtil.SetComplete();//提交事务
13}
14catch(System.Exceptionexcept)
15{
16ContextUtil.SetAbort();//撤销事务
17Response.Write(except.Message);
18}
19}


2.3.4分布式级别事务(COM+)

略,可查看"企业级服务COM+事务"相关技术文档。

2.3.5System.Transactions事务处理

在.NETFramework2.0中增加了System.Transactions,这是一种新的命名空间,完全专注于控制事务性行为。引入了执行事务性工作的更简单方法及一些新的性能优化。System.Transactions提供了一个“轻量级”的、易于使用的Transaction框架。

用法:

1)引用:usingSystem.Transactions;。

2)将事务操作代码放在TransactionScope中执行。

1using(TransactionScopets=newTransactionScope())
2{
3//事务操作代码
4  ts.Complete();
5}


3锁定

3.1定义

锁定是MicrosoftSQLServer数据库引擎用来同步多个用户同时对同一个数据块的访问的一种机制。

在事务获取数据块当前状态的依赖关系(比如通过读取或修改数据)之前,它必须保护自己不受其他事务对同一数据进行修改的影响。事务通过请求锁定数据块来达到此目的。锁有多种模式,如共享或独占。锁模式定义了事务对数据所拥有的依赖关系级别。如果某个事务已获得特定数据的锁,则其他事务不能获得会与该锁模式发生冲突的锁。如果事务请求的锁模式与已授予同一数据的锁发生冲突,则数据库引擎实例将暂停事务请求直到第一个锁释放。

3.2四大冲突问题

脏读

不重复读

幻读

更新丢失

3.2.1脏读

某个事务读取的数据是另一个事务正在处理的数据。而另一个事务可能会回滚,造成第一个事务读取的数据是错误的。

3.2.2不可重复读

在一个事务里两次读入数据,但另一个事务已经更改了第一个事务涉及到的数据,造成第一个事务读入旧数据。

3.2.3幻读

幻读是指当事务不是独立执行时发生的一种现象。例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

3.2.4更新丢失

多个事务同时读取某一数据,一个事务成功处理好了数据,被另一个事务写回原值,造成第一个事务更新丢失。

3.3锁粒度和层次结构

MicrosoftSQLServer数据库引擎具有多粒度锁定,允许一个事务锁定不同类型的资源。为了尽量减少锁定的开销,数据库引擎自动将资源锁定在适合任务的级别。锁定在较小的粒度(例如行)可以提高并发度,但开销较高,因为如果锁定了许多行,则需要持有更多的锁。锁定在较大的粒度(例如表)会降低了并发度,因为锁定整个表限制了其他事务对表中任意部分的访问。但其开销较低,因为需要维护的锁较少。

数据库引擎通常必须获取多粒度级别上的锁才能完整地保护资源。这组多粒度级别上的锁称为锁层次结构。例如,为了完整地保护对索引的读取,数据库引擎实例可能必须获取行上的共享锁以及页和表上的意向共享锁。

下表列出了数据库引擎可以锁定的资源。

资源说明
RID

用于锁定堆中的单个行的行标识符。

KEY

索引中用于保护可序列化事务中的键范围的行锁。

PAGE

数据库中的8KB页,例如数据页或索引页。

EXTENT

一组连续的八页,例如数据页或索引页。

HoBT

堆或B树。用于保护没有聚集索引的表中的B树(索引)或堆数据页的锁。

TABLE

包括所有数据和索引的整个表。

FILE

数据库文件。

APPLICATION

应用程序专用的资源。

METADATA

元数据锁。

ALLOCATION_UNIT

分配单元。

DATABASE

整个数据库。

3.4锁模式

MicrosoftSQLServer数据库引擎使用不同的锁模式锁定资源,这些锁模式确定了并发事务访问资源的方式。

下表显示了数据库引擎使用的资源锁模式。

锁模式说明
共享(S)

用于不更改或不更新数据的读取操作,如SELECT语句。

更新(U)

用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。

排他(X)

用于数据修改操作,例如INSERT、UPDATE或DELETE。确保不会同时对同一资源进行多重更新。

意向

用于建立锁的层次结构。意向锁包含三种类型:意向共享(IS)、意向排他(IX)和意向排他共享(SIX)。

架构

在执行依赖于表架构的操作时使用。架构锁包含两种类型:架构修改(Sch-M)和架构稳定性(Sch-S)。

大容量更新(BU)

在向表进行大容量数据复制且指定了TABLOCK提示时使用。

键范围

当使用可序列化事务隔离级别时保护查询读取的行的范围。确保再次运行查询时其他事务无法插入符合可序列化事务的查询的行。

3.4.1共享锁
共享锁(S锁)允许并发事务在封闭式并发控制下读取(SELECT)资源。有关详细信息,请参阅并发控制的类型。资源上存在共享锁(S锁)时,任何其他事务都不能修改数据。读取操作一完成,就立即释放资源上的共享锁(S锁),除非将事务隔离级别设置为可重复读或更高级别,或者在事务持续时间内用锁定提示保留共享锁(S
锁)。

3.4.2更新锁(U锁)
更新锁在共享锁和排他锁的杂交。更新锁意味着在做一个更新时,一个共享锁在扫描完成符合条件的数据后可能会转化成排他锁。

这里面有两个步骤:
1)扫描获取Where条件时。这部分是一个更新查询,此时是一个更新锁。
2)如果将执行写入更新。此时该锁升级到排他锁。否则,该锁转变成共享锁。

更新锁可以防止常见的死锁。在可重复读或可序列化事务中,此事务读取数据[获取资源(页或行)的共享锁(S锁)],然后修改数据[此操作要求锁转换为排他锁(X锁)]。如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排他锁(X锁)。共享模式到排他锁的转换必须等待一段时间,因为一个事务的排他锁与其他事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排他锁(X锁)以进行更新。由于两个事务都要转换为排他锁(X锁),并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。

若要避免这种潜在的死锁问题,请使用更新锁(U锁)。一次只有一个事务可以获得资源的更新锁(U锁)。如果事务修改资源,则更新锁(U锁)转换为排他锁(X锁)。

更新锁只与共享锁和意向共享锁兼容。

3.4.3排他锁
排他锁(X锁)可以防止并发事务对资源进行访问。排他锁不与其他任何锁兼容。使用排他锁(X锁)时,任何其他事务都无法修改数据;仅在使用NOLOCK提示或未提交读隔离级别时才会进行读取操作。

3.4.4意向锁
数据库引擎使用意向锁来保护共享锁(S锁)或排他锁(X锁)放置在锁层次结构的底层资源上。意向锁之所以命名为意向锁,是因为在较低级别锁前可获取它们,因此会通知意向将锁放置在较低级别上。
意向锁有两种用途:

防止其他事务以会使较低级别的锁无效的方式修改较高级别资源。
提高数据库引擎在较高的粒度级别检测锁冲突的效率。

例如,在该表的页或行上请求共享锁(S锁)之前,在表级请求共享意向锁。在表级设置意向锁可防止另一个事务随后在包含那一页的表上获取排他锁(X锁)。意向锁可以提高性能,因为数据库引擎仅在表级检查意向锁来确定事务是否可以安全地获取该表上的锁。而不需要检查表中的每行或每页上的锁以确定事务是否可以锁定整个表。

意向锁包括意向共享(IS)、意向排他(IX)以及意向排他共享(SIX)。

意向锁锁模式说明
意向共享(IS)

保护针对层次结构中某些(而并非所有)低层资源请求或获取的共享锁。

意向排他(IX)

保护针对层次结构中某些(而并非所有)低层资源请求或获取的排他锁。IX是IS的超集,它也保护针对低层级别资源请求的共享锁。

意向排他共享(SIX)

保护针对层次结构中某些(而并非所有)低层资源请求或获取的共享锁以及针对某些(而并非所有)低层资源请求或获取的意向排他锁。顶级资源允许使用并发IS锁。例如,获取表上的SIX锁也将获取正在修改的页上的意向排他锁以及修改的行上的排他锁。虽然每个资源在一段时间内只能有一个SIX锁,以防止其他事务对资源进行更新,但是其他事务可以通过获取表级的IS锁来读取层次结构中的低层资源。

意向更新(IU)

保护针对层次结构中所有低层资源请求或获取的更新锁。仅在页资源上使用IU锁。如果进行了更新操作,IU锁将转换为IX锁。

共享意向更新(SIU)

S锁和IU锁的组合,作为分别获取这些锁并且同时持有两种锁的结果。例如,事务执行带有PAGLOCK提示的查询,然后执行更新操作。带有PAGLOCK提示的查询将获取S锁,更新操作将获取IU锁。

更新意向排他(UIX)

U锁和IX锁的组合,作为分别获取这些锁并且同时持有两种锁的结果。

3.4.5架构锁
数据库引擎在表数据定义语言(DDL)操作(例如添加列或删除表)的过程中使用架构修改(Sch-M)锁。保持该锁期间,Sch-M锁将阻止对表进行并发访问。这意味着Sch-M锁在释放前将阻止所有外围操作。

某些数据操作语言(DML)操作(例如表截断)使用Sch-M锁阻止并发操作访问受影响的表。

数据库引擎在编译和执行查询时使用架构稳定性(Sch-S)锁。Sch-S锁不会阻止某些事务锁,其中包括排他(X)锁。因此,在编译查询的过程中,其他事务(包括那些针对表使用X锁的事务)将继续运行。但是,无法针对表执行获取Sch-M锁的并发DDL操作和并发DML操作。

3.4.5大容量更新锁
数据库引擎在将数据大容量复制到表中时使用了大容量更新(BU)锁,并指定了TABLOCK提示或使用
sp_tableoption
设置了tablelockonbulkload表选项。大容量更新锁(BU锁)允许多个线程将数据并发地大容量加载到同一表,同时防止其他不进行大容量加载数据的进程访问该表。

3.4.6键范围锁
在使用可序列化事务隔离级别时,对于Transact-SQL语句读取的记录集,键范围锁可以隐式保护该记录集中包含的行范围。键范围锁可防止幻读。通过保护行之间键的范围,它还防止对事务访问的记录集进行幻像插入或删除。

3.5锁之间的兼容性



更多内容,参看锁兼容性http://msdn.microsoft.com/zh-cn/library/ms186396.aspx
3.6事务隔离级别

SQLServer通过SETTRANSACTIONISOLATIONLEVEL语句设置事务隔离级别:

SETTRANSACTIONISOLATIONLEVEL

{READUNCOMMITTED

|READCOMMITTED

|REPEATABLEREAD

|SNAPSHOT

|SERIALIZABLE

}

[;]

ReadCommitted是SQLServer的预设隔离等级。

3.6.1READCOMMITTED

一旦创建共享锁的语句执行完成,该锁顶便释放。

ReadCommitted是SQLServer的预设隔离等级。

ReadCommitted只可以防止脏读。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
--先创建表:

CREATE
TABLE
tb(id
int
,val
int
)

INSERT
tb
VALUES
(1,10)

INSERT
tb
VALUES
(2,20)


然后在连接1中,执行:

SET
TRANSACTION
ISOLATION
LEVEL

READ
COMMITTED

BEGIN
TRANSACTION

SELECT
*
FROM
tb;
--这个SELECT结束后,就会释放掉共享锁


WAITFORDELAY
'00:00:05'
--模拟事务处理,等待5秒


SELECT
*
FROM
tb;
--再次SELECTtb表

ROLLBACK
--回滚事务


在连接2中,执行

UPDATE
tb
SET

val=val+10

WHERE
id=2;


--------

回到连接1中.可以看到.两次
SELECT
的结果是不同的.

因为在默认的
READ
COMMITTED
隔离级别下,
SELECT
完了.就会马上释放掉共享锁.


3.6.2READUNCOMMITTED

ReadUnCommitted事务可以读取事务已修改,但未提交的的记录。

ReadUnCommitted事务会产生脏读(DirtyRead)。

ReadUnCommitted事务与select语句加nolock的效果一样,它是所有隔离级别中限制最少的。

3.6.3REPEATABLEREAD

REPEATABLEREAD事务不会产生脏读,并且在事务完成之前,任何其它事务都不能修改目前事务已读取的记录。

其它事务仍可以插入新记录,但必须符合当前事务的搜索条件——这意味着当前事务重新查询记录时,会产生幻读(PhantomRead)。

3.6.4SERIALIZABLE

SERIALIZABLE可以防止除更新丢失外所有的一致性问题,即:

1.语句无法读取其它事务已修改但未提交的记录。

2.在当前事务完成之前,其它事务不能修改目前事务已读取的记录。

3.在当前事务完成之前,其它事务所插入的新记录,其索引键值不能在当前事务的任何语句所读取的索引键范围中。

Serializable事务与select语句加holdlock的效果一样。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
--示例2.REPEATABLEREAD


连接1:


SET
TRANSACTION
ISOLATION
LEVEL

REPEATABLE
READ

BEGIN
TRANSACTION

SELECT
*
FROM
tb;
--这个SELECT结束后,就会释放掉共享锁


WAITFORDELAY
'00:00:05'
--模拟事务处理,等待5秒


SELECT
*
FROM
tb;
--再次SELECTtb表

ROLLBACK
--回滚事务


连接2:

UPDATE
tb
SET

val=val+10

WHERE
id=2;


---

可以看到连接2被阻塞了.而且连接1中.两次
SELECT
的结果是一样的.

因为
REPEATABLE
READ
隔离级别下的共享锁保留到事务结束.

所以别的事务的X锁与S锁不兼容.所以连接2等待.


3.6.5SNAPSHOT

Snapshot事务中任何语句所读取的记录,都是事务启动时的数据。

这相当于事务启动时,数据库为事务生成了一份专用“快照”。在当前事务中看到不其它事务在当前事务启动之后所进行的数据修改。

Snapshot事务不会读取记录时要求锁定,读取记录的Snapshot事务不会锁住其它事务写入记录,写入记录的事务也不会锁住Snapshot事务读取数据。

3.7悲观锁和悲观锁



3.7.1悲观锁


悲观锁是指假设并发更新冲突会发生,所以不管冲突是否真的发生,都会使用锁机制。

悲观锁会完成以下功能:锁住读取的记录,防止其它事务读取和更新这些记录。其它事务会一直阻塞,直到这个事务结束.

悲观锁是在使用了数据库的事务隔离功能的基础上,独享占用的资源,以此保证读取数据一致性,避免修改丢失。

悲观锁可以使用RepeatableRead事务,它完全满足悲观锁的要求。

[b]

3.7.2乐观锁
[/b]

乐观锁不会锁住任何东西,也就是说,它不依赖数据库的事务机制,乐观锁完全是应用系统层面的东西。

如果使用乐观锁,那么数据库就必须加版本字段,否则就只能比较所有字段,但因为浮点类型不能比较,所以实际上没有版本字段是不可行的。

3.8死锁

当二或多个工作各自具有某个资源的锁定,但其它工作尝试要锁定此资源,而造成工作永久封锁彼此时,会发生死锁。例如:

1.事务A取得数据列1的共享锁定。

2.事务B取得数据列2的共享锁定。

3.事务A现在要求数据列2的独占锁定,但会被封锁直到事务B完成并释出对数据列2的共享锁定为止。

4.事务B现在要求数据列1的独占锁定,但会被封锁直到事务A完成并释出对数据列1的共享锁定为止。

等到事务B完成后,事务A才能完成,但事务B被事务A封锁了。这个状况也称为「循环相依性」(CyclicDependency)。事务A相依于事务B,并且事务B也因为相依于事务A而封闭了这个循环。

SQLServer遇到死锁时会自动杀死其中一个事务,而另一个事务会正常结束(提交或回滚)。

SQLServer对杀死的连接返回错误代码是1205,异常提示是:

Yourtransaction(processID#52)wasdeadlockedon{lock|communicationbuffer|thRead}resourceswithanotherprocessandhasbeenchosenasthedeadlockvictim.Rerunyourtransaction.

例如以下操作就会产生死锁,两个连接互相阻塞对方的update。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
连接1:

begin
tran


select
*
from
customers


update
customers
set
CompanyName=CompanyName



waitfordelay
'00:00:05'



select
*
from
Employees

–因为Employees被连接2锁住了,所以这里会阻塞。

update
Employees
set
LastName=LastName

commit
tran


连接2:

begin
tran


select
*
from
Employees


update
Employees
set
LastName=LastName



waitfordelay
'00:00:05'



select
*
from
customers


--因为customers被连接1锁住了,所以这里会阻塞。

update
customers
set
CompanyName=CompanyName

commit
tran


3.9如何避免死锁

(1).按同一顺序访问对象。(注:避免出现循环)

(2).避免事务中的用户交互。(注:减少持有资源的时间,较少锁竞争)

(3).保持事务简短并处于一个批处理中。(注:同(2),减少持有资源的时间)

(4).使用较低的隔离级别。(注:使用较低的隔离级别(例如已提交读)比使用较高的隔离级别(例如可序列化)持有共享锁的时间更短,减少锁竞争)

(5).使用基于行版本控制的隔离级别:2005中支持快照事务隔离和指定READ_COMMITTED隔离级别的事务使用行版本控制,可以将读与写操作之间发生的死锁几率降至最低:

SETALLOW_SNAPSHOT_ISOLATIONON--事务可以指定SNAPSHOT事务隔离级别;

SETREAD_COMMITTED_SNAPSHOTON--指定READ_COMMITTED隔离级别的事务将使用行版本控制而不是锁定。默认情况下(没有开启此选项,没有加withnolock提示),SELECT语句会对请求的资源加S锁(共享锁);而开启了此选项后,SELECT不会对请求的资源加S锁。

注意:设置READ_COMMITTED_SNAPSHOT选项时,数据库中只允许存在执行ALTERDATABASE命令的连接。在ALTERDATABASE完成之前,数据库中决不能有其他打开的连接。数据库不必一定要处于单用户模式中。

(6).使用绑定连接。(注:绑定会话有利于在同一台服务器上的多个会话之间协调操作。绑定会话允许一个或多个会话共享相同的事务和锁(但每个回话保留其自己的事务隔离级别),并可以使用同一数据,而不会有锁冲突。可以从同一个应用程序内的多个会话中创建绑定会话,也可以从包含不同会话的多个应用程序中创建绑定会话。在一个会话中开启事务(begintran)后,调用execsp_getbindtoken@Tokenout;来取得Token,然后传入另一个会话并执行EXECsp_bindsession@Token来进行绑定(最后的示例中演示了绑定连接)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: