文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>漫谈数据库之事务和锁

漫谈数据库之事务和锁

时间:2010-09-18  来源:Ring

1. 理解ACID 特性

 

1.1 A (Atomicity)   原子性

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

 

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

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

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

1) UPDATE GongHangsavings
SET Balance = Balance - 100,000
WHERE Account = ‘zhangsan'


2) UPDATE NongHangsavings
SET Balance = Balance + 100,000
WHERE Account = ‘zhangsan'

 

1.2 C (Consistency) 一致性

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

 

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

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

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

 

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

 

1.3 I (Isolation)   隔离性

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

 

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

 



1.4 D (Durability)  持久性

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

 

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

 

2 事务(Transaction)

 

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

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

 

1) UPDATE GongHangsavings
SET Balance = Balance - 100,000
WHERE Account = ‘zhangsan'


2) UPDATE NongHangsavings
SET Balance = Balance + 100,000
WHERE Account = ‘zhangsan'

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

 

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

 

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

 

 2.1 完整的事务

 

BEGIN a transaction: 设置事务的起始点

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

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

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

 

2.1.1 语法:

 

BEGIN TRAN[SACTION] [<transaction name>|<@transaction variable>][WITH MARK [’<description>’]][;] 

COMMIT [TRAN[SACTION] [<transaction name>|<@transaction variable>]][;]

ROLLBACK TRAN[SACTION] [<transaction name>|<save point name>|<@transaction variable>|<@savepoint variable>][;]

SAVE TRAN[SACTION] [<save point name>| <@savepoint variable>][;]

 

2.1.2 一个完整的例子:

 

代码
USE AdventureWorks2008; -- We’re making our own table - what DB doesn’t matter
--
Create table to work with
CREATE TABLE MyTranTest
(
OrderID
INT PRIMARY KEY IDENTITY
);
-- Start the transaction
BEGIN TRAN TranStart;
-- Insert our first piece of data using default values.
--
Consider this record No1. It is also the 1st record that stays
--
after all the rollbacks are done.
INSERT INTO MyTranTest
DEFAULT VALUES;
-- Create a "Bookmark" to come back to later if need be
SAVE TRAN FirstPoint;
-- Insert some more default data (this one will disappear
--
after the rollback).
--
Consider this record No2.
INSERT INTO MyTranTest
DEFAULT VALUES;
-- Roll back to the first savepoint. Anything up to that
--
point will still be part of the transaction. Anything
--
beyond is now toast.
ROLLBACK TRAN FirstPoint;
INSERT INTO MyTranTest
DEFAULT VALUES;
-- Commit the transaction
COMMIT TRAN TranStart;
-- See what records were finally committed.
SELECT TOP 2 OrderID
FROM MyTranTest
ORDER BY OrderID DESC;
-- Clean up after ourselves
DROP TABLE MyTranTest;

 

2.2 隐式事务

 

2.2.1 定义

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

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

CREATE
ALTER TABLE
GRANT
REVOKE
SELECT
UPDATE
DELETE
INSERT

TRUNCATE TABLE
DROP
OPEN
FETCH

 

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

 

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

 

代码
 1 SET QUOTED_IDENTIFIER OFF;
2 GO
3 SET NOCOUNT OFF;
4 GO
5 USE TestDB;
6 GO
7 CREATE TABLE ImplicitTran
8 ( Colb char(3) NOT NULL);
9 GO
10 SET IMPLICIT_TRANSACTIONS ON;
11 GO
12
13 BEGIN transaction
14 SAVE transaction A
15 INSERT INTO ImplicitTran VALUES ('AAA');
16 ROLLBACK TRANSACTION A
17
18 INSERT INTO ImplicitTran VALUES ('BBB');
19 COMMIT transaction

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

 

 

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

开启隐性事务模式语法:

SET IMPLICIT_TRANSACTIONS ON;

 

2.2.2 一个隐式事务例子

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

 

代码
 1 SET QUOTED_IDENTIFIER OFF;
2 GO
3 SET NOCOUNT OFF;
4 GO
5 USE AdventureWorks2008R2;
6 GO
7 CREATE TABLE ImplicitTran
8 (Cola int PRIMARY KEY,
9 Colb char(3) NOT NULL);
10 GO
11 SET IMPLICIT_TRANSACTIONS ON;
12 GO
13 -- First implicit transaction started by an INSERT statement.
14 INSERT INTO ImplicitTran VALUES (1, 'aaa');
15 GO
16 INSERT INTO ImplicitTran VALUES (2, 'bbb');
17 GO
18 -- Commit first transaction.
19 COMMIT TRANSACTION;
20 GO
21 -- Second implicit transaction started by a SELECT statement.
22 SELECT COUNT(*) FROM ImplicitTran;
23 GO
24 INSERT INTO ImplicitTran VALUES (3, 'ccc');
25 GO
26 SELECT * FROM ImplicitTran;
27 GO
28 -- Commit second transaction.
29 COMMIT TRANSACTION;
30 GO
31 SET IMPLICIT_TRANSACTIONS OFF;
32 GO
33

 

 

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

 

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

 

2.3.1 Sql Server 数据库级别事务

  略 

2.3.2 ADO.NET 级别事务

 

代码
SqlConnection myConnection = new SqlConnection(conString);
myConnection.Open();
//启动一个事务
SqlTransaction myTrans = myConnection.BeginTransaction();
//为事务创建一个命令
SqlCommand myCommand = new SqlCommand();
myCommand.Connection
= myConnection;
myCommand.Transaction
= myTrans;
try
{
myCommand.CommandText
= "update P_Product set Name='电脑2' where Id=52";
myCommand.ExecuteNonQuery();
myCommand.CommandText
= "update P_Product set Name='电脑3' where Id=53";
myCommand.ExecuteNonQuery();
myTrans.Commit();
//提交
Response.Write("两条数据更新成功");
}
catch (Exception ex)
{
myTrans.Rollback();
//遇到错误,回滚
Response.Write(ex.ToString());
}
finally
{
myConnection.Close();
}

 

 

2.3.3 ASP.NET页面级别事务

 

代码
 1 <%@ Page Transaction="Required" Language="C#" AutoEventWireup="true"
2 CodeBehind="WebForm3.aspx.cs" Inherits="WebApplication4.WebForm3" %>
3
4
5
6 protected void Button1_Click(object sender, EventArgs e)
7 {
8 try
9 {
10 Work1();
11 Work2();
12 ContextUtil.SetComplete(); //提交事务
13 }
14 catch (System.Exception except)
15 {
16 ContextUtil.SetAbort(); //撤销事务
17 Response.Write(except.Message);
18 }
19 }

 

 

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

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

 

2.3.5 System.Transactions 事务处理

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

用法:

1) 引用:using System.Transactions;。
2) 将事务操作代码放在 TransactionScope 中执行。

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

 

 

3 锁定

 

3.1 定义

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

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

 

3.2 四大冲突问题

 

脏读

不重复读

幻读

更新丢失

 

3.2.1 脏读

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

 

3.2.2 不可重复读

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

 

3.2.3 幻读

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

 

3.2.4 更新丢失

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

 

 3.3 锁粒度和层次结构

 

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

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

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

资源 说明

RID

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

KEY

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

PAGE

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

EXTENT

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

HoBT

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

TABLE

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

FILE

数据库文件。

APPLICATION

应用程序专用的资源。

METADATA

元数据锁。

ALLOCATION_UNIT

分配单元。

DATABASE

整个数据库。

 

 

 3.4 锁模式

 

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

 

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

锁模式 说明

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

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

 

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

数据修改语句(如 INSERT、UPDATE 和 DELETE)合并了修改和读取操作。 语句在执行所需的修改操作之前首先执行读取操作以获取数据。 因此,数据修改语句通常请求共享锁和排他锁。 例如,UPDATE 语句可能根据与一个表的联接修改另一个表中的行。 在此情况下,除了请求更新行上的排他锁之外,UPDATE 语句还将请求在联接表中读取的行上的共享锁。

 

  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 设置了 table lock on bulk load 表选项。 大容量更新锁(BU 锁)允许多个线程将数据并发地大容量加载到同一表,同时防止其他不进行大容量加载数据的进程访问该表。      3.4.6 键范围锁

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

 

 

本文内容来源: 

1. MSDN官方文档 http://msdn.microsoft.com/zh-cn/library/ms187875.aspx

2. Sql Server 2008 Programming 作者:Rob Vieira

3. 亮剑.NET 第5章 数据库开发 作者:李天平

排行榜 更多 +
辰域智控app

辰域智控app

系统工具 下载
网医联盟app

网医联盟app

运动健身 下载
汇丰汇选App

汇丰汇选App

金融理财 下载