19. 了解交易与交易锁定
什么是交易?
ACID 属性
交易模式
交易复原
交易锁定
封锁与死结
锁定提示
本章总结
本章将阐明交易和交易锁定的基本法则,学习什么是交易、Microsoft SQL Sever 2000对有效交易所要求的属性、交易的起始与结束可指定使用哪些交易模式,以及如何认可及复原交易;还会看到执行交易时SQL Server所使用的锁定类型和模式,以及「封锁」(blocking)和「死结」(deadlocking)的概念;最后介绍您在交易中可能会使用到的一些数据表锁定提示。
什么是交易?
「交易(Transaction)」是以工作的单一逻辑单元(Logic Unit)来执行的一系列相关作业。交易使SQL Server确保一定水平的数据完整性和数据可回复性。每个数据库都必须拥有所谓的「交易记录文件(transaction log)」,用于保存对数据库进行修改(插入、更新或删除)的所有交易记录。一旦发生了错误或系统故障,SQL Server便使用该交易记录文件来回复数据。
交易的完整性,部分要靠程序设计人员来达成。程序设计人员必须知道何时开始交易、何时终止交易,以及以什么顺序来进行数据修改,以确保数据的逻辑一致性和有意义。现在您知道什么是交易了,让我们来看看有效交易要求的属性。
ACID属性
一个交易必须符合四个特定要求才有资格作为一个有效的交易,这些要求被称为「ACID属性」,ACID是「不可部分完成性(Atomicity)」、「一致性(Consistency)」、「隔离性(Isolation)」和「耐久性(Durability)」的英文缩写。SQL Server提供机制来帮助确认一个交易是否符合每一个要求。
不可部分完成性
如果交易成功,那么SQL Server保证交易中的所有数据修改都是集体性的完成;而当交易不成功时,就不作任何修改。换句话说,SQL Server确保了交易的不可部分完成性。交易必须作为一个不可分割的基本单元来执行,所以称为不可部分完成性。要交易获得成功,交易的每一个步骤(或陈述式)都必须成功。如果一个步骤失败,整个交易就失败了,而且从该交易的开始之后所作的修改都将会取消。SQL Server提供了交易管理机制,它自动执行确定交易成功或失败的任务,而且如果失败,它必定会取消所有的数据修改。
一致性
SQL Server还会确保交易的一致性。一致性意味着在交易完成后,所有的数据保持一致的状态,这保证了数据的完整性,无论交易是成功还是失败。在交易之前,数据库必须处于一个一致的状态-亦即数据维持其完整性,而且内在的结构,例如B-tree索引和双连结清单(double linked lists),是正确无误的。在交易发生后,数据库也必须保持一致的状态,如果交易成功就变为新的状态,或是如果交易失败,会保持与交易开始前的状态一致。
一致性也是SQL Server提供的交易管理特性。如果您的数据一致,交易将保持逻辑一致和数据完整,SQL Server将确保交易后的数据一致性。当您在分布式环境中使用数据复写时,能够达到不同等级的一致性,其范围从最后的交易收敛(或潜在的一致性)到实时交易一致性均包含在内。一致性的等级取决于您使用的复写类型。如需获得更多信息,请参阅 第26章 到 第28章 。
隔离性
「隔离性」意指每一个交易产生的作用,会如同此交易是系统中唯一的一个交易;也就是说,一个交易所作的修改与任何其它同时发生的交易所作的修改是隔离的。在一个交易的变更得到认可之前,另一个交易并不会受到此交易变更的值所影响。如果交易失败,它的修改不会有任何影响,因为变更都将被退回。SQL Server允许您决定交易的隔离等级。交易的隔离性动作取决于您指定的隔离等级。
________________________________________
说明
当交易完成后,它的所有修改成为数据库的永久部分。当交易被退回,变更将被取消,数据库看上去就像交易从未发生一样。
________________________________________
隔离等级
SQL Server支持四种等级的隔离性。隔离等级是一种设定,用来决定允许交易接受不一致数据的等级-即一个交易与另一个交易隔离的程度。一个较高的隔离等级提高了数据的正确性,但是它减少了可并行的交易数目。另一方面,较低的隔离等级将允许更多交易并行,但却降低了资料的准确性。您对SQL Server工作阶段所指定的隔离等级,决定了工作阶段期间所有SELECT陈述式的锁定行为(除非您设定到另一隔离等级)。锁定行为会在本章稍后的 〈交易锁定〉 一节中讨论。
• 读取未认可 :最低级别的隔离性。在这个等级,交易隔离仅仅足以确保被实体毁坏的数据不可读取。
• 读取认可 :SQL Server的预设等级。在这个等级,只允许读取已认可的数据(已认可数据指的是已经成为数据库永久部分的数据)。
• 可重复读取 :在这个等级,一个交易对同一数据列或数数据列的重复读取将得到同样的结果。(直到该交易完成,否则没有其它交易能修改数据。)
• 序列化(Serializable) :最高级别的隔离性,交易彼此之间完全隔离。在这个等级,一个数据库中同时执行多个交易获得的结果与交易序列执行(即以一定的顺序一次一个)是一样的。
并行交易行为
为了更容易理解每一个隔离等级,首先看看当您执行并行交易时,可能会发生的三种行为。
• Dirty读取 :即对尚未认可的数据进行读取。Dirty read发生在一个交易修改数据,而第二个交易在第一个交易认可变更前读取修改的数据时。如果第一个交易复原变更,第二个交易将检索到数据库中没有的数据。
• 非可重复读取(Nonrepeatable read) :重复的读取会获得不一致的数据。非可重复读取发生在同一交易中对单一数据列进行多次读取期间,另一个独立的交易对该数据列数据进行了更新。因为第一个交易的重复读取会检索到不同的数据,在这个交易中结果是不可重复的。
• 幻像读取(Phantom read) :发生在交易试图检索一个不存在的数据列,但是在交易完成前另一个交易又插入了那一资料列。如果第一个交易再次寻找该数据列,它将会发现该资料列已经突然出现。这个新的资料列称为「幻像资料列(phantom row)」。
表19-1列出了每个隔离等级允许的行为类型。如您所见,「读取未认可」是限制最少的隔离等级,而「序列化」是限制最严格的隔离等级。如前所述,SQL Server的预设隔离等级是「读取认可」。随着隔离等级的提高,SQL Server将会用较长的时间周期,以掌控更加严格的限制锁定。隔离等级影响了对SELECT陈述式的锁定行为,它表示隔离性影响着读取的数据所使用的锁定模式。锁定模式将在本章稍后的 〈交易锁定〉 一节中讨论。
表19-1隔离等级行为
隔离等级 行为
Dirty读取 非可重复读取 幻像读取
读取未认可 Yes Yes Yes
读取认可 No Yes Yes
可重复读取 No No Yes
序列化 No No No
设定隔离等级
透过使用T-SQL陈述式或应用程序中的函数,您可以设定整个SQL Server使用者工作阶段中使用的隔离等级,还能在查询中指定锁定提示以覆盖该特定交易已设定的隔离等级。锁定提示将在本章稍后的 〈锁定提示〉 一节中讨论。要使用T-SQL或在DB-LIB应用程序中设定隔离等级,请使用SET TRANSACTION ISOLATION LEVEL陈述式,并指定四个隔离等级中的一个。语法如下:
SET TRANSACTION ISOLATION LEVEL
READ UNCOMMITTED
| READ COMMITTED
| REPEATABLE READ
| SERIALIZABLE
GO
________________________________________
相关信息
对于其它类型的应用程序,如使用ODBC、ADO或是OLE-DB,请查阅在线丛书索引中的「ACID属性」,并选择「找到的主题」对话框中的「调整交易隔离等级」。
________________________________________
一旦您设定了隔离等级,在接下来的交易中,SQL Server工作阶段将执行锁定已确定该隔离等级。使用DBCC USEROPTIONS指令,可以发现SQL Server当前预设的是哪一个隔离等级。该指令的语法仅仅是个指令名称:
DBCC USEROPTIONS
这个指令传回的只是使用者已经设定或是当前处于启动状态的选项。如果您没有设定隔离等级(任其为SQL Server预设等级),当您执行DBCC USEROPTIONS指令时,将看不到隔离等级;但是如果您指定了非预设的等级,就会看到隔离等级。例如,如果您执行下面的陈述式,隔离等级将显示在DBCC USEROPTIONS的输出中:
USE pubs
GO
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
GO
DBCC USEROPTIONS
GO
DBCC USEROPTIONS
指令的传回结果就像这样:
Set Option Value
-------------------------------------------
textsize 64512
language us_english
dateformat mdy
datefirst 7
quoted_identifier SET
arithabort SET
ansi_null_dflt_on SET
ansi_defaults SET
ansi_warnings SET
ansi_padding SET
ansi_nulls SET
concat_null_yields_null SET
isolation_level serializable
(13 row(s) affected)
您可以在资料表层级使用锁定提示来覆盖掉预设的隔离等级,但是您只能在必需且充分理解它如何影响您的交易的情况下才使用该技术。要想获得更多关于可用的锁定提示之细节,请参阅本章稍后的 〈锁定提示〉 一节。
耐久性
最后一个ACID属性是耐久性。「耐久性」意味着一旦交易认可,交易的作用将在数据库中永久保持,即使系统出现故障也不会遗失。SQL Server交易记录文件和数据库备份提供了耐久性。如果服务器中的组件失败、操作系统失败或是SQL Server失败,数据库将在SQL Server重新启动时自动回复。SQL Server使用交易记录文件重新进行受系统崩溃所影响的已认可交易,并复原任何未确认的交易。
如果数据磁盘失败,数据遗失或损坏,您可以透过数据库备份和交易记录文件备份来回复数据库。如果您的备份计划够好,就有办法让您从每次的系统失败中回复。不幸的是,如果您的备份磁盘失败而遗失了需要用来回复系统的备份,您将无法再回复您的数据库。要想获得关于备份、回存数据库以及交易记录文件的细节,请参阅 第32章 和 33章 。现在,您已经理解了交易的属性,让我们来看看如何开始和终止一个交易。
交易模式
交易能以三种模式中的任意一种开始:「自动认可(autocommit)」、「外显(explicit)」或是「隐含(implicit)」。SQL Server的预设模式是自动认可。
自动认可模式
在「自动认可」模式中,每个T-SQL陈述式在完成后都将得到认可-这种模式不需要任何附加的陈述式来控制交易。换句话说,每个交易仅仅由一个T-SQL陈述式组成。当您使用交互式指令执行陈述式,如OSQL或SQL Server Query Analyzer的时候,自动认可模式是很有用的,因为您不必担心每个交易的明确开始和结束。您知道每个陈述式将被SQL Server作为自己的交易来处理,并且在完成时可以立即被认可。SQL Server的每个联机都会使用自动认可模式,直到您使用 BEGIN TRANSACTION开始一个外显(Explicit)交易,或是设定了隐含(implicit)模式。一旦外显交易结束或关闭隐含模式,SQL Server将返回自动认可模式。
外显模式
外显模式常常用于撰写应用程序、预存程序、触发器(triggers)和指令文件中。当您执行一组陈述式来执行任务时,您可能需要决定交易从哪开始、到哪结束,以使整组陈述式成功或者整组的修改都被复原。当您明确指定交易的起始点和结束点时,您就已经在使用外显模式,该交易就被称为「外显交易(explicit transaction)」。使用T-SQL陈述式或API函数,可以指定外显交易。在这个部分,我们将只讨论T-SQL方法;API函数超出了本书的范围。
________________________________________
相关信息
要想了解关于使用ADO和OLE-DB的外显交易的信息,请查阅在线丛书索引中的「外显交易」部分,并选择「找到的主题」对话框中的「外显交易」。请注意,ODBC API不支持外显交易,而仅支持隐含和自动认可交易。
________________________________________
________________________________________
真实世界 使用外显交易
让我们来看看什么情况下我们需要使用外显交易开始和终止一个任务。假设我们有一个命名为Place_Order的预存程序,它用来处理使用者订购项目的数据库任务。该程序的步骤包括选择使用者的当前账号信息、输入新的订购ID号码和订购的项目、计算含税的订购价格、更新客户账号余额以反映总价格,以及检查该项目是否有库存。
让我们来看看什么情况下我们需要使用外显交易开始和终止一个任务。假设我们有一个命名为Place_Order的预存程序,它用来处理使用者订购项目的数据库任务。该程序的步骤包括选择使用者的当前账号信息、输入新的订购ID号码和订购的项目、计算含税的订购价格、更新客户账号余额以反映总价格,以及检查该项目是否有库存。
我们希望这些步骤一起完成或是都不完成,以保持数据库中数据的一致性。为了达到这一点,我们将处理这些任务的陈述式组合为一个外显交易。如果我们不组合这些陈述式,我们可能得到不一致的数据。例如,如果在输入新的订购数已经执行之后而客户余额更新之前,客户端到服务器的网络连接被中断,数据库中客户有新的订单,而客户的账号却没有费用支出。SQL Server本身会尽可能快速地在每个陈述式完成后认可,在网络断线时预存程序只完成一半。但是在一个外显交易中如果定义了步骤,断线时SQL Server将自动复原整个交易,客户端可以稍候再联机并再次执行程序。要想了解更多的细节,请参阅本章稍后的 〈复原交易〉 一节。
如前面所述的例子,当您的任务由几个步骤组成时,使用外显交易是很有帮助的,因为无论您是否指定自己的ROLLBACK陈述式,当服务器出错时,如出现网络通讯中断、数据库或客户端系统崩溃或死结,SQL Server都将自动复原您的交易。死结将在本章稍后的 〈封锁和死结〉 一节中讨论。开始交易使用的是BEGIN TRANSACTION陈述式。结束交易指定使用COMMIT TRANSACTION或是ROLLBACK TRANSACTION陈述式。您可以在BEGIN TRANSACTION陈述式中选择性的为交易指定一个名称,这样就可以在COMMIT TRANSACTION或是ROLLBACK TRANSACTION陈述式中按名称引用至该交易。这三个陈述式的语法如下所示:
BEGIN TRAN[SACTION] [tran_name | @tran_name_variable]
COMMIT [TRAN[SACTION] [tran_name | @tran_name_variable]]
ROLLBACK [TRAN[SACTION] [tran_name | @tran_name_variable
| savepoint_name | @savepoint_name_variable]]
________________________________________
认可交易
上面曾提到过,一个已认可交易是所有的修改都已成为数据库永久部分的交易。在交易被认可之前,一份有关交易修改的记录与认可记录会被写入数据库交易记录文件。因此,已成为数据库永久部分的修改将会位在下列两个位置之一:修改要不是已确实写到磁盘成为数据库的永久部分,便是位于数据快取之中。如果系统发生错误,交易记录文件可以藉此让交易向前复原,交易将不会有任何遗失。
交易使用的所有资源,例如锁定,都会随着认可得到释放。如果每条陈述式都成功,交易就会成功得到认可。下面是一个名为update_state的小型外显交易,它将所有发行者的publishers数据表中state数据行为NULL值的都更新为XX:
USE pubs
GO
BEGIN TRAN update_state
UPDATE publishers SET state = 'XX'
WHERE state IS NULL
COMMIT TRAN update_state
GO
如果您执行该交易,您应该看到两个数据行发生了变化。要将数据表恢复到原始状态(就像发生了复原而不是认可),请执行下面的交易:
USE pubs
GO
BEGIN TRAN undo_update_state
UPDATE publishers SET state = NULL
WHERE state ='XX'
COMMIT TRAN undo_update_state
GO
您又可以看到有两个数据行发生了变化。伴随着COMMIT TRAN使用的交易名称update_state和undo_update_state将被SQL Server忽略-它仅仅是帮助程序设计人员确定哪个交易将要被认可。SQL Server在COMMIT开始之前自动认可最新的未认可交易,无论是否指定交易名称。
巢状交易
SQL Server允许「巢状交易(nested transactions)」或称「交易中的交易」。使用巢状交易时,您应当外显地认可每个内层交易,那么SQL Server才会知道交易已经完成,并在外层交易认可时,释放交易使用的资源。如果资源被锁定,别的使用者将不能存取那些资源。虽然每个交易都必须包含COMMIT,但要到最外层的交易被成功认可后,SQL Server才真正地认可内层的交易,并释放内层与外层交易使用的资源。如果最外层的交易认可失败,内层的交易也不会认可,而且所有的内外交易都将复原。如果最外层交易认可了,所有内层交易都将认可。换句话说,SQL Server基本上忽略了内层巢状交易的COMMIT陈述式,换之以等待外层交易的最终认可或复原,以决定所有内层交易的完成状况。(在底下的真实世界范例中会有更进一步的说明。)
此外在巢状交易里,如果外层或任何一个内层交易执行了ROLLBACK陈述式,所有的交易都将被复原。在ROLLBACK陈述式中使用内层交易名称是无效的,如果您已使用,SQL Server将传回错误讯息。您应在该陈述式中使用最外层的交易名称,根本不使用名称,或使用一个「储存点(savepoint)」名称(储存点将在下一部分讨论)。
________________________________________
真实世界 使用巢状交易
以下是使用预存程序的巢状交易的例子。预存程序包括一个外显交易,且此预存程序是从另一个外显交易中被呼叫。因此,预存程序中的交易成为内层巢状交易。下面的程序代码显示了用于建立预存程序的陈述式(为了方便起见,该范例使用了PRINT陈述式而不是真实数据修改陈述式)和呼叫预存程序的交易。
USE MyDB
GO
CREATE PROCEDURE Place_Order --Creates the stored procedure
AS
BEGIN TRAN place_order_tran
PRINT 'SQL Statements that perform order tasks go here'
COMMIT TRAN place_order_tran
GO
BEGIN TRAN Order_tran --Begins the outer transaction
PRINT 'Place an order'
EXEC Place_Order --Calls the stored procedure, which
--begins the inner transaction
COMMIT TRAN Order_tran --Commits the inner and outer
GO --transactions
执行这些程序代码后,您将会看到输出两个PRINT陈述式。Place_order_tran交易必须在预存程序中有一个COMMIT陈述式来标记交易的结束,但要到Order_tran交易认可时,才会真正地认可。Place_order_tran是认可还是复原,完全取决Order_tran是否认可。
尽管遇到COMMIT陈述式时SQL Server实际上没有认可内层交易,但它为每个遇到的COMMIT陈述式更新@@TRANCOUNT系统变量。该变量保持了每个使用者连接的启动交易数的追踪信息。当现在没有启动的交易时,@@TRANCOUNT是0。随着每个交易的开始(使用BEGIN TRAN),@@TRANCOUNT值一个一个递增,随着每个交易的认可,@@TRANCOUNT值一个一个递降。当@@TRANCOUNT达到0,认可最外层的交易。如果在外部或内层交易的某个地方执行了ROLLBACK陈述式,@@TRANCOUNT设定为0。请记住,您应该认可每一个内层交易,那么@@TRANCOUNT就能够正确的下降。您可以检测@@TRANCOUNT的值来确定现在是否存在交易活动。使用SELECT @@TRANCOUNT陈述式,就可以看到@@TRANCOUNT的值。
________________________________________
________________________________________
真实世界 使用 @@TRANCOUNT
以下是一个@@TRANCOUNT的例子。假设您有一个由两个交易组成的巢状交易,分别是一个内层交易和一个外层交易,就如上个例子一般。在两个交易都开始之后但在任一交易认可之前,@@TRANCOUNT的值是2。由于@@TRANCOUNT现为非0值,所以最外层的交易不能认可。当内层交易认可后,@@TRANCOUNT值下降到1。如果您对最外层交易执行COMMIT陈述式,@@TRANCOUNT下降到0,外层交易将得到认可。
下面的程序代码与上一个例子相仿,不过我们在其中加进撷取@@TRANCOUNT值的陈述式:
USE MyDB
GO
DROP PROCEDURE Place_Order
GO
CREATE PROCEDURE Place_Order --Creats the stored procedure.
AS
BEGIN TRAN place_order_tran --TRANCOUNT is incremented.
PRINT 'SQL Statements that perform order tasks go here'
SELECT @@TRANCOUNT as TRANCOUNT_2
COMMIT TRAN place_order_tran --TRANCOUNT is decremented.
GO
SELECT @@TRANCOUNT as TRANCOUNT_initial
BEGIN TRAN Order_tran --TRANCOUNT is incremented.
PRINT 'Place an order'
SELECT @@TRANCOUNT as TRANCOUNT_1
EXEC Place_Order --Calls the stored procedure,
--which begins the inner transaction.
SELECT @@TRANCOUNT as TRANCOUNT_3
COMMIT TRAN Order_tran --TRANCOUNT is decremented.
SELECT @@TRANCOUNT as TRANCOUNT_4
GO
如果您执行这些陈述式,您将看到一系列的@@TRANCOUNT值,先后为0、1、2、1、0。
________________________________________
________________________________________
说明
对于使用BEGIN TRAN的外显交易,您必须外显地认可每个交易。当您使用巢状交易时,SQL Server会到所有的内层交易都得到认可后,才能够认可最外层交易。
________________________________________
隐含模式
在「隐含模式(implicit mode)」中,当使用某些特定T-SQL陈述式时,交易会自动开始并持续到以COMMIT或ROLLBACK陈述式外显地结束交易。如果结束陈述式没有指定,交易将在使用者断线时复原。下面的陈述式将在隐含模式里开始新交易:
• ALTER TABLE
• CREATE
• DELETE
• DROP
• FETCH
• GRANT
• INSERT
• OPEN
• REVOKE
• SELECT
• TRUNCATE TABLE
• UPDATE
当这些陈述式之一用于开始一个隐含交易时,交易就将继续直到明确的指定结束,即使在该交易中执行这些陈述式中的另一个。在交易明确认可或复原之后,下次再执行这些陈述式之一,又会开始一个新的交易。该程序将持续到隐含模式被关闭为止。
要想设定隐含交易模式为ON,您可以使用下面的T-SQL指令:
SET IMPLICIT_TRANSACTIONS {ON | OFF}
ON启动该模式,OFF关闭该模式。当隐含模式关闭后,接下来就会使用自动认可模式。
当您执行的指令文件将会修改到那些需要在交易中保护的数据时,隐含模式是很有用的。您可以在指令文件开始时开启隐含模式,执行必要的修改,然后在结束时关闭该模式。为了避免并行性问题,在数据修改之后和浏览数据之前关闭隐含模式。因为如果一次认可之后的下一个陈述式是SELECT陈述式,在隐含模式中就会开始一个新的交易,而且直到SELECT认可后资源才会得到释放。
交易复原
有两种方式可以令交易复原:一是由SQL Server自动复原,另一则是手动地程序复原。在特定情况下,SQL Server会为您复原交易。不过要使您的程序能有逻辑的一致性,您必须在必要时呼叫ROLLBACK陈述式。让我们看一下这两个方法的更多细节。
自动复原
一如本章之前所提到的,如果交易因为严重的错误而失败,例如交易已开始执行却发生网络联机中断的情况,或是客户端应用程序或计算机出现错误,SQL Server将会自动地复原交易。复原将会回复交易造成的所有修改,并释放交易用到的所有资源。
如果一个「执行时期(run-time)」陈述式引发错误,例如条件约束或规则冲突,预设情况下SQL Server自动复原的仅仅是出错的陈述式。要变更这种行为,您可以使用SET XACT_ABORT陈述式。启动该选项指示SQL Server自动复原执行时期错误事件中的交易。该技术有时很有用,例如,当因违反了某一外来键条件约束而造成您的交易中某一陈述式失败;而且因为该陈述式失败,您不希望别的陈述式得以通过时,可以设定XACT_ABORT。在预设情况下,XACT_ABORT设为OFF。
在伺服器重新回复的期间,SQL Server也会使用自动复原。举例来说,如果交易正在执行时,发生了足以让系统重置(reboot)的严重故障,当SQL Server重新启动时,它会自动复原(automatic recovery)。数据库自动复原(automatic recovery)包括读取交易记录文件以对尚未写入磁盘的交易重新进行认可,并对错误发生时遗失(尚未被认可)的交易进行复原(roll back)。
程序复原
使用ROLLBACK叙述,您可以明确地指定交易中复原的发生点。ROLLBACK陈述式终结交易并恢复交易所作的所有变更。如果您在交易中间引发复原,交易的剩余部分将被忽略。举个例子,如果交易是一个整体的预存程序,而ROLLBACK在这个预存程序中发生,那么预存程序将会复原,而且系统将继续处理批次档中的下一个陈述式。
如果您想根据SELECT叙述传回的数据列数来决定是否复原交易,那么应该使用@@ROWCOUNT系统变量,@@ROWCOUNT含有查询传回的数据列数或受更新、删除所影响的数据列数。如果指定的数据列数并不符合,但您只是单纯地需要找出指定状况下某一列或数列是否存在,可将IF EXISTS陈述式与SELECT陈述式一起使用。此陈述式并不会传回任何数据列,只会传回「TRUE」或「FALSE」。如果结果是「TRUE」,接下来的陈述式将会执行;如果传回「FALSE」,接下来的陈述式将不会执行。IF EXISTS陈述式也可使用ELSE子句。
以下是一个使用IF EXISTS...ELSE子句的例子,假设下面的交易按两种版税税率(16% 和15%)更新了roysched数据表中的版税总数,但是如果被更新的两种版税税率都不存在,就不会执行任何UPDATE命令。交易使用ROLLBACK来确认这个结果。
BEGIN TRAN update_royalty --Begin the transaction.
USE pubs
IF EXISTS (SELECT titles.title, roysched.royalty FROM titles,
roysched
WHERE titles.title_id = roysched.title_id
AND roysched.royalty = 16)
UPDATE roysched SET royalty = 17 WHERE royalty = 16 --13 rows exist.
ELSE
ROLLBACK TRAN update_royalty --ROLLBACK is not executed.
IF EXISTS (SELECT titles.title, roysched.royalty FROM titles,
roysched
WHERE titles.title_id = roysched.title_id
AND roysched.royalty = 15) --No rows exist.
BEGIN
UPDATE roysched SET royalty = 20 WHERE royalty = 15
COMMIT TRAN update_royalty
END
ELSE --ROLLBACK is executed.
ROLLBACK TRAN update_royalty
GO
在这个交易中,第一个IF EXISTS (SELECT...) 陈述式找到了一些存在的数据列,因此执行了第一个UPDATE命令(显示出13个数据列有影响)。第二个SELECT叙述传回了0数据列,因此没有执行第二个UPDATE命令,而是执行了交易复原ROLLBACK TRANupdate_royalty。由于ROLLBACK将所有的修改回复至交易开始状态,第一个UPDATE也被复原。如果您再次执行SELECT叙述,您看到的仍是税率设为16的13列。将税率设为17的更新,因ROLLBACK陈述式而被复原。
________________________________________
说明
在这个交易中使用了一些新的关键词:IF、ELSE、BEGIN和END。这些关键词将在 第20章 详细讨论。
________________________________________
交易认可后就不能再复原。(请记住,直到外层交易认可后,内层交易才真正得到认可。)单一交易的情况下,必须在COMMIT之前呼叫ROLLBACK才会出现外显复原。巢状交易情况下,一旦最外层交易认可(因此内层交易也得到认可),就不能再复原任何交易。我们已经提到过,您不能只复原内层交易,而是必须复原整个交易。因此,如果您在ROLLBACK叙述中包含了交易名称,要确定使用最外层交易名称以避免混淆,否则SQL Server将会传来一个错误讯息。您可使用「储存点(savepoint)」,保留一些修改,以避免复原整个交易。
储存点
您可以透过使用「储存点(savepoint)」来避免复原整个交易,储存点可以从交易中的某一点而不是从交易的开始点复原。从开始到储存点的所有修改将保持有效并且不会被复原,但在储存点之后至ROLLBACK陈述式之间执行的陈述式将被复原。在ROLLBACK之后的陈述式将继续执行。如果没有指定储存点而复原交易时,所有的修改将返回到交易的开始状态。要注意当交易复原至储存点时,SQL Server并不会释放已锁定的资源,被所定的资源要到交易认可或整个交易都复原时,才会被释放出来。
要指定储存点,可用以下陈述式:
SAVE TRAN[SACTION] {savepoint_name | @savepoint_name_variable}
储存点在交易中的位置应该是您想要复原到的位置。要想复原到储存点,请使用有储存点名称的ROLLBACK TRAN陈述式,如下所示:
ROLLBACK TRAN savepoint_name
在ROLLBACK陈述式之后,您可以使用更多的T-SQL叙述来继续交易。在第一个ROLLBACK陈述式之后,要记得必须包含至少一个COMMIT叙述或另一个ROLLBACK叙述,如此才能完成整个交易。
________________________________________
相关信息
要想了解关于储存点的更多信息以及更佳的使用范例,请查阅在线丛书索引中的「储存点」,并选择「找到的主题」对话框中的「Save Transaction」。
________________________________________
交易锁定
SQL Server使用一个被称为「锁定(lock)」的对象来防止多个使用者同时对数据库作修改,并防止使用者读取被其它使用者变更的数据。锁定有助于确保交易和数据的逻辑完整性。锁定由SQL Server软件在内部管理,并且可以每个使用者联机为基础来获得。当使用者获得(或拥有)对资源的锁定时,锁定就象征着使用者对该资源拥有使用权。可以被使用者锁定的资源包括数据列、数据页、一个「范围(extent)」(八个连续的页面)、数据表或整个数据库。例如,如果使用者持有对某个数据分页的锁定,另一个使用者就不能对同一分页执行操作,否则会影响拥有锁定的使用者的作业。因此,使用者不能更新当前被另一使用者锁定并正在读取的数据分页。如果锁定已被某一使用者持有,另一使用者就不能获得与其冲突之锁定。例如,两个使用者不能同时都拥有更新同一分页的锁定。同一个锁定不能被超过一个使用者所使用。
按照使用者动作,SQL Server的锁定管理可以自动获得和释放锁定。DBA或程序设计人员不需要执行任何动作来管理锁定。然而,当在数据库上执行特定查询或修改时,您可以使用程序化提示来指示SQL Server要获得锁定的种类;这些将在本章稍后的 〈锁定提示〉 一节中讨论。
在这个部分,我们将看到几种不同层级的锁定以及锁定模式。但首先,让我们先了解一些增进SQL Server效能的锁定管理功能。
锁定管理功能
SQL Server支持数据列层级的锁定-即在数据分页或索引分页的一列上获得锁定的能力,数据列层级锁定是SQL Server中可获得锁定的最小数据点层级,这个较低层级的锁定提供了许多Online Transaction Processing(OLTP)应用程序更大的并行能力。当您在数据表和索引中执行数据列插入、更新和删除时,数据列层级锁定特别有用。
除了数据列层级锁定特性外,SQL Server让锁定设定的管理更为简化。您不再需要手动设定锁定配置参数来确定SQL Server可用之锁定的数目。在预设上,如果需要更多的锁定,SQL Server可以动态分配更多,直到SQL Server内存的上限。如果锁定已经分配但是不再使用,SQL Server将取消对它们的分配。SQL Server还可以针对资源最佳化选择获得锁定的类型,通常数据列层级锁定用于插入、更新和删除;分页层级锁定则用于数据表扫描。下一节将介绍更多锁定层级的细节。
________________________________________
相关信息
锁定设定选项的更多信息请参阅 第30章 。
________________________________________
锁定层级
锁定可以按资源的数目取得;资源的类型决定锁定的数据点层级。表19-2列出了SQL Server能够锁定的资源,按锁定数据点的层级从小到大排序。
表19-2 可锁定的资源
资源 锁定类型 说明
RID(ROW ID) 资料列层级 用来锁定数据表内的单一数据列。
索引键 资料列层级 用来锁定索引内的单一数据列。
分页 资料页层级 用来锁定数据表或索引内的单一8KB分页。
范围 范围层级 用来锁定一个范围(extent),八个数据页或索引页的连续群组。
资料表 资料表层级 用来锁定整个数据表。
数据库 数据库层级 用来锁定整个数据库。
随着数据点层级的递增,并行性就下降。例如,锁定整个数据表将封锁其它使用者存取该数据表。不过由于使用的锁定较少,系统消耗也会下降。随着数据点层级的递减-例如在分页层级或数据列层级锁定-并行性就上升,因为允许更多的使用者同时存取数据表中不同的分页或资料列。这样,系统消耗也会上升,因为许多数据列或分页被个别存取时将需要更多的锁定。
SQL Server自动选择适合该任务的锁定类型,以最小化锁定的系统消耗。SQL Server也依照每个交易要锁定的资源来自动决定锁定模式;下面将讨论这些模式。
锁定模式
「锁定模式(lock mode)」指定了并行使用者(或并行交易)可以如何使用资源。在这些模式的任一模式中,可以获得每种类型的锁定。可用的锁定模式有六种:「共享(Shared)」、「更新(Update)」、「独占(Exclusive)」、「意图(Intent)」、「结构描述(Schema)」和「大量更新(Bulk Update)」。
共享
共享锁定模式用于只读作业,例如使用SELECT陈述式时执行的操作。该模式允许并行交易同时读取同一资源,但并不允许任何交易修改该资源。一旦读取完成,共享锁定就会被释放,除非隔离等级被设为可重复读取或更高等级,或是一个覆盖该行为的锁定提示已被指定于交易里。
更新
当资源更新时使用更新锁定模式。某一时刻对某一资源只能有一个交易可以获得更新锁定。如果交易确实作了修改(例如,找到符合搜寻条件的数据列并进而修改),更新锁定就转为独占锁定(下面将说明);否则,转为共享锁定。
独占
独占锁定用于修改数据的操作,如更新、插入和删除。当交易对某一资源持有独占锁定,没有其它交易可以读取或修改该资源。这种锁定模式防止并行使用者同时更新同一数据,因为这样可能会造成无效数据。
意图
意图锁定模式用于建立一个锁定阶层架构。例如,资料表层级的意图锁定说明SQL Server意图在该资料表中取得一个分页、几个分页或几个数据列的锁定。如果交易需要取得某一资源的独占锁定,SQL Server首先会检查以确定那些资源上是否存在意图锁定。如果持有意图锁定的交易正在等待那些资源,第二个交易就不能获得独占锁定。如果没有交易持有意图锁定且正在等待那些资源,交易才能取得该资源的独占锁定。意图锁定模式有三种:
• 意图共享 表示交易意图在某一资源加上共享锁定。
• 意图独占 表示交易意图在某一资源加上独占锁定。
• 与意图独占共享 表示交易意图在某些资源加上共享锁定,而在另一些资源加上独占锁定。
要想详细了解这些模式类型,请查阅在线丛书索引中的「共同的锁定模式」,选择「找到的主题」对话框中的「了解SQL Server中的锁定」。
结构描述
当执行依存于数据表结构描述的作业时,会使用结构描述锁定模式,如新增一个数据行到数据表中,或进行编辑查询的工作。结构描述锁定有两种,分别是「结构描述修改(Sch-M)」锁定和「结构描述固定性(Sch-S)」锁定。「结构描述修改」锁定用于执行数据定义语言(DDL)操作时;「结构描述固定性」锁定用于编译查询(comiling queries)时。编译查询时,其它的交易可以同时在数据表中持有锁定并继续执行,即使是独占锁定也可以,不过DDL陈述式不能在已有「结构描述固定性」锁定的数据表中执行。
大量更新
大量更新锁定是在大量复制数据到数据表,以及已指定「TABLOCK」提示或使用sp_tableoption来设定table lock on bulk load数据表选项时使用。大量更新锁定的目的在于允许处理序将资料同时大量复制到一个数据表,而让并未大量复制数据的其它处理序无法存取该数据表。
封锁与死结
封锁(blocking)和死结(deadlock)是可能出现在并行交易中的另外两个问题,它们会引发系统更严重的问题,并使效能变慢甚至终止运作。这些问题在应用程序中都可以获得解决,且SQL Server会尽其可能地避免这些状况。本节仅仅做一个说明,让您知道它们并了解其概念。至于封锁和死结的避免以及解决,则留给程序设计人员来完成。
封锁发生在一个交易对某一资源持有锁定,而第二个交易却对同一资源要求冲突的锁定类型时。第二个交易必须等待第一个交易解除它的锁定,换句话说,它为第一个交易所封锁。封锁常常发生在交易长时间的持有锁定时,这会引起一连串交易的封锁,它们都在等待其它交易的完成以使其可以获得它们所要求的锁定。图19-1展示了一个链式封锁的例子。
图19-1 链式封锁
死结与封锁交易的区别是:死结指两个交易互相封锁并且等待。例如,假设一个交易对数据表1持有独占锁定,另一个交易对数据表2持有独占锁定。在任一独占锁定释放之前,第一个交易要求对数据表2的锁定,而第二个交易要求对数据表1的锁定。如今每个交易都在等待另一个解除它的独占锁定,然而每个交易都要到发生认可或复原以完成该交易后,才释放它的独占锁定。两个交易都无法完成,因为它们都需要另一个交易掌控的锁定以继续交易程序。图19-2描述了这种情况。
图19-2 死结
________________________________________
相关信息
想获得关于如何避免封锁和死结的基本信息,请在在线丛书中搜寻「封锁」,在底下的窗格中选择「了解与避免封锁」。您也可以查阅在线丛书索引中的「锁定,死结」,并选择「将死结数量降至最低」和「处理死结」。
________________________________________
锁定提示
「锁定提示(locking hints)」是在SELECT、INSERT、UPDATE和DELETE陈述式中,用于指示SQL Server使用正确的数据表层级锁定型别的T-SQL关键词,这些数据表层级锁定用于特定的叙述。您可以使用锁定提示来覆盖掉预设的交易隔离等级。只有在绝对需要的情况下,才可以使用该技术,因为如果您不够小心,可能会引起封锁和死结。
让我们看看在什么情况下使用锁定提示有用。假设您对所有的交易都正在使用预设的「读取认可」隔离等级。按照「读取认可」等级定义,当交易执行读取时,对资源持有共享锁定,直到读取完成,然后共享锁定就被释放。因此假设交易读取相同的数据两次,两次读取的结果可能会不同,因为其它交易可能获得锁定并更新了同一笔数据。
要想避免重复读取问题,您可以指定「序列化」隔离等级,但是这么做将会引起SQL Server掌握所有交易中SELECT陈述式需要的所有共享锁定,直到每个交易完成。换言之,一个交易的SELECT陈述式指定的数据表,其共享锁定将在整体交易中一直被持有。如果您不想在您的所有交易上强制「序列化能力」,您可以在指定的查询中加上锁定提示。SELECT叙述中的HOLDLOCK锁定提示指示SQL Server掌控对一个交易之SELECT陈述式指定的数据表的所有共享锁定直到交易结束,无论是何种隔离等级。因此,如果交易执行重复的读取,数据将是一致的(不为其它交易所变更)。使用锁定提示时,其它交易的隔离等级不受影响。
________________________________________
说明
SQL Server查询最佳化器(Query Optimizer)自动决定查询最方便的执行方案。由于SQL Server Query Optimizer自动选择正确的锁定类型,锁定提示仅仅在您相当的理解它们,并且绝对必要时使用,因为它们对并行作业可能会有害。
________________________________________
下面的清单对可用的数据表层级锁定提示作了说明:
• HOLDLOCK 保持shared锁定直到交易完成,而不是在不再需要数据表、数据分页或数据列时释放它。相当于SERIALIZABLE锁定提示。
• NOLOCK 仅仅用于SELECT叙述,不提交共享锁定也不接受独占锁定。该提示允许读取未认可数据(dirty读取)。
• PAGLOCK 在单一数据表的锁定可能被取走之处使用分页锁定。
• READCOMMITTED 当交易使用「读取认可」隔离等级(SQL Server预设的隔离等级)时,使用相同锁定行为执行扫描。
• READPAST 只应用于SELECT叙述,而且只使用数据列层级锁定应用至被锁定的数据列。交易略过通常出现于结果集的其它交易所锁定的数据列,而不是要封锁交易并等候其它交易释放对这些数据列的锁定。只能用于在「读取认可」隔离等级中执行的交易。
• READUNCOMMITTED 同NOLOCK。
• REPEATABLEREAD 当交易使用「可重复读取」隔离等级时,使用相同锁定行为执行扫描。
• ROWLOCK 使用数据列层级锁定代替分页或数据表层级锁定。
• SERIALIZABLE 当交易使用「序列化」隔离等级时,使用相同锁定行为执行扫描,同于HOLDLOCK。
• TABLOCK 使用数据表层级锁定而不是分页层级锁定或数据列层级锁定。SQL Server掌控该锁定直到叙述结束。
• TABLOCKX 对数据表使用独占锁定。该提示防止别的交易存取数据表。
• UPDLOCK 读取数据表时使用更新锁定替代共享锁定。该提示允许别的使用者只能读取数据而且允许您更新数据,因此确保自从您最后一次读取后没有别的使用者更新数据。
您可以结合兼容的数据表提示,如TABLOCK与REPEAT-ABLEREAD,但是您不能结合冲突的提示,如REPEATABLEREAD和SERIALIZABLE。要指明数据表的锁定提示,在T-SQL陈述式的数据表名称后的括号中包含该提示。下面的陈述式是在SELECT陈述式中使用TABLOCKX提示的例子:
USE pubs
SELECT COUNT(ord_num)
FROM sales (TABLOCKX)
WHERE ord_date > "Sep 13 1994"
GO
TABLOCKX提示指示SQL Server在销售数据表中保持exclusive数据表层级锁定,直到叙述完成。该提示确保了当查询计算数据表中订单时,没有别的交易能向sales数据表中插入数据。由于封锁了别的交易存取数据表,可能会引起别的交易等待并减慢响应时间,而且可能产生链式封锁,所以对这种提示要当心。还有,只有在绝对必要时,才能使用数据表锁定提示。
本章总结
本章中,我们学习了交易,包括交易的ACID属性和用于指定交易开始和结束的不同模式,还学会了SQL Server锁定管理功能、锁定的层级和锁定模式;我们还看到了封锁、死结以及锁定提示的使用。现在,您应该相当理解基本的交易和交易的锁定方法。在 第20章 中,我们将对在 第13章 中T-SQL介绍的基础上加以扩展,您将学到如何使用INSERT、UPDATE和DELETE T-SQL叙述,以及在写入交易时可能需要的其它陈述式。