使用 API 函数和 Transact-SQL 语句,可以将事务作为显式、自动提交或隐式事务来启动。
显式事务
显式事务是指这样的事务:您在其中通过 API 函数或发出 Transact-SQL BEGIN TRANSACTION、COMMIT TRANSACTION、COMMIT WORK、ROLLBACK TRANSACTION 或 ROLLBACK WORK Transact-SQL 语句明确定义事务的开始和结束。 当事务结束时,连接将返回到启动显式事务前所处的事务模式,这可能是隐式模式,或自动提交模式。
当连接以隐式事务模式进行操作时,数据库引擎实例将在提交或回滚当前事务后自动启动新事务。 无须描述事务的开始,只需提交或回滚每个事务。 隐性事务模式生成连续的事务链。 通过 API 函数或 Transact-SQL SET IMPLICIT_TRANSACTIONS ON 语句,将隐性事务模式设置为打开。 此模式也称为 Autocommit OFF,请参阅 setAutoCommit Method (SQLServerConnection)。
为连接启用隐性事务模式后,数据库引擎实例会在首次执行下列任意语句时自动启动一个事务:
ALTER TABLE
CREATE
DELETE
DENY
DROP
FETCH
GRANT
INSERT
OPEN
REVOKE
SELECT
TRUNCATE
UPDATE
批处理级事务
只能应用于多个活动结果集 (MARS),在 MARS 会话中启动的 Transact-SQL 显式或隐式事务变为批处理级事务。 当批处理完成时没有提交或回滚的批处理级事务自动由数据库引擎进行回滚。
分布式事务
分布式事务跨越两个或多个称为资源管理器的服务器。 称为事务管理器的服务器组件必须在资源管理器之间协调事务管理。 如果分布式事务由 Microsoft 分布式事务处理协调器 (MS DTC) 之类的事务管理器或其他支持 Open Group XA 分布式事务处理规范的事务管理器来协调,则在这样的分布式事务中,每个数据库引擎的实例都可以作为资源管理器来运行。 有关详细信息,请参阅 MS DTC 文档。
如果批处理中出现运行时语句错误(如违反约束),则数据库引擎中的默认行为是只回滚产生该错误的语句。 可以使用 SET XACT_ABORT ON 语句更改此行为。 在执行 SET XACT_ABORT ON 之后,任何运行时语句错误将导致当前事务的自动回滚。 编译错误(如语法错误)不受 SET XACT_ABORT 的影响。 有关详细信息,请参阅 SET XACT_ABORT (Transact-SQL)。
CREATE TABLE TestBatch (ColA INT PRIMARY KEY, ColB CHAR(3));
GO
INSERT INTO TestBatch VALUES (1, 'aaa');
INSERT INTO TestBatch VALUES (2, 'bbb');
INSERT INTO TestBatch VALUSE (3, 'ccc'); -- Syntax error.
GO
SELECT * FROM TestBatch; -- Returns no rows.
GO
CREATE TABLE TestBatch (ColA INT PRIMARY KEY, ColB CHAR(3));
GO
INSERT INTO TestBatch VALUES (1, 'aaa');
INSERT INTO TestBatch VALUES (2, 'bbb');
INSERT INTO TestBatch VALUES (1, 'ccc'); -- Duplicate key error.
GO
SELECT * FROM TestBatch; -- Returns rows 1 and 2.
GO
CREATE TABLE TestBatch (ColA INT PRIMARY KEY, ColB CHAR(3));
GO
INSERT INTO TestBatch VALUES (1, 'aaa');
INSERT INTO TestBatch VALUES (2, 'bbb');
INSERT INTO TestBch VALUES (3, 'ccc'); -- Table name error.
GO
SELECT * FROM TestBatch; -- Returns rows 1 and 2.
GO
--Transaction 1
BEGIN TRAN;
SELECT ID
FROM dbo.employee
WHERE ID > 5 AND ID < 10;
--The INSERT statement from the second transaction occurs here.
SELECT ID
FROM dbo.employee
WHERE ID > 5 and ID < 10;
COMMIT;
--Transaction 2
BEGIN TRAN;
INSERT INTO dbo.employee (Id, Name)
VALUES(6 ,'New');
COMMIT;
注意:数据库引擎不支持对元数据进行版本控制。 因此,对于在快照隔离下运行的显式事务中可以执行的 DDL 操作存在限制。 在 BEGIN TRANSACTION 语句之后,使用快照隔离时不允许使用以下 DDL 语句:ALTER TABLE、CREATE INDEX、CREATE XML INDEX、ALTER INDEX、DROP INDEX、DBCC REINDEX、ALTER PARTITION FUNCTION、ALTER PARTITION SCHEME 或任何公共语言运行时 (CLR) DDL 语句。 在隐式事务中使用快照隔离时,允许使用这些语句。 根据定义,隐式事务为单个语句,这使得它可以强制应用快照隔离的语义,即便使用 DDL 语句也是如此。 违反此原则会导致错误 3961: Snapshot isolation transaction failed in database '%.*ls' because the object accessed by the statement has been modified by a DDL statement in another concurrent transaction since the start of this transaction. It is not allowed because the metadata is not versioned. A concurrent update to metadata could lead to inconsistency if mixed with snapshot isolation.
OLE DB
启动事务时,使用 OLE DB 的应用程序会调用 ITransactionLocal::StartTransaction,isoLevel 设置为 ISOLATIONLEVEL_READUNCOMMITTED、ISOLATIONLEVEL_READCOMMITTED、ISOLATIONLEVEL_REPEATABLEREAD、ISOLATIONLEVEL_SNAPSHOT 或 ISOLATIONLEVEL_SERIALIZABLE。
在自动提交模式下指定事务隔离级别时,OLE DB 应用程序可以将 DBPROPSET_SESSION 属性 DBPROP_SESS_AUTOCOMMITISOLEVELS 设置为 DBPROPVAL_TI_CHAOS、DBPROPVAL_TI_READUNCOMMITTED、DBPROPVAL_TI_BROWSE、DBPROPVAL_TI_CURSORSTABILITY、DBPROPVAL_TI_READCOMMITTED、DBPROPVAL_TI_REPEATABLEREAD、DBPROPVAL_TI_SERIALIZABLE、DBPROPVAL_TI_ISOLATED 或 DBPROPVAL_TI_SNAPSHOT。
数据库引擎使用意向锁来确保共享 (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 锁。
意向排他 (IX) 锁与 IX 锁模式兼容,因为 IX 表示打算只更新部分行而不是所有行。 还允许其他事务尝试读取或更新部分行,只要这些行不是其他事务当前更新的行即可。 此外,如果两个事务尝试更新同一行,将在表级和页级上授予这两个事务 IX 锁。 但是,将在行级授予一个事务 X 锁。 另一个事务必须在该行级锁被删除前等待。
排他 (X) 锁放置在与名称 Bob 对应的索引项上。 其他事务可以在删除了值 Bob 的行前后插入或删除值。 但是任何试图读取、插入或删除与值 Bob 匹配的行的事务都将被阻止,直到删除的事务提交或回滚为止。 (READ_COMMITTED_SNAPSHOT 数据库选项和 SNAPSHOT 隔离级别还允许从之前提交状态的行版本中读取。)
在事务持续时间内,所有修改的行上都放置 TID 锁。 在与值 Bob 对应的索引行的 TID 上获取锁。 使用优化锁定,更新时将继续获取页锁和行锁,但每行更新后,每个页锁和行锁都会释放。 TID 锁可保护行在事务完成之前不被更新。 任何试图读取、插入或删除具有值 Bob 的行的事务都将被阻止,直到删除的事务提交或回滚为止。 (READ_COMMITTED_SNAPSHOT 数据库选项和 SNAPSHOT 隔离级别还允许从之前提交状态的行版本中读取。)
RangeI-N 模式键范围锁放置在与名称 David 对应的索引行上,以测试范围。 如果已授权锁,则插入包含值 Dan 的行,并将排他 (X) 锁放置在插入的行上。 RangeI-N 模式键范围锁仅对测试范围是必需的,而不在执行插入操作的事务期间保留。 其他事务可以在插入包含值 Dan 的行前后插入或删除值。 任何试图读取、插入或删除具有值 Dan 的行的事务都将被阻止,直到插入的事务提交或回滚为止。
DELETE FROM LogMessages
WHERE LogDate < '2024-09-26'
通过一次删除几百个行,可以显著减少每个事务累积的锁数量,并防止锁升级。 例如:
DECLARE @DeletedRows int;
WHILE @DeletedRows IS NULL OR @DeletedRows > 0
BEGIN
DELETE TOP (500)
FROM LogMessages
WHERE LogDate < '2024-09-26'
SELECT @DeletedRows = @@ROWCOUNT;
END;
BEGIN TRAN;
SELECT *
FROM mytable WITH (UPDLOCK, HOLDLOCK)
WHERE 1 = 0;
WAITFOR DELAY '1:00:00';
COMMIT TRAN;
此查询将获取 IX 表的 mytable 锁,并持有该锁一个小时,这会阻止在此时间段内该表发生锁升级。 此批处理作业不会修改任何数据或阻止其他查询(除非另一个查询使用 TABLOCK 提示强制执行表锁,或者管理员已禁用 mytable 的索引的页锁或行锁)。
你还可以使用跟踪标志 1211 和 1224 禁用所有或某些锁升级。 不过,这些跟踪标志会对整个数据库引擎实例全局禁用所有锁升级。 锁升级在数据库引擎中发挥了重要作用,通过最大限度地提高效率来弥补因获取和释放数千个锁的开销而导致的查询速度下降。 此外,锁升级还可以帮助最大程度地减少跟踪锁所需的内存。 数据库引擎可以为锁结构动态分配的内存是有限的,因此,如果禁用锁升级并且锁内存增长到足够大,则尝试为任何查询分配额外的锁可能会失败并出现以下错误:Error: 1204, Severity: 19, State: 1 The SQL Server cannot obtain a LOCK resource at this time. Rerun your statement when there are fewer active users or ask the system administrator to check the SQL Server lock and memory configuration.
-- Session creates a histogram of the number of lock escalations per database
CREATE EVENT SESSION [Track_lock_escalation] ON SERVER
ADD EVENT sqlserver.lock_escalation
(
SET collect_database_name=1,collect_statement=1
ACTION(sqlserver.database_id,sqlserver.database_name,sqlserver.query_hash_signed,sqlserver.query_plan_hash_signed,sqlserver.sql_text,sqlserver.username)
)
ADD TARGET package0.histogram
(
SET source=N'sqlserver.database_id'
)
GO
获取调节锁后,锁结构将存储在内存中,然后即可对其进行访问和可能的修改。 将锁访问分布到多个资源中有助于消除在 CPU 之间传输内存块的需要,这有助于提高性能。
实现和监视锁分区
默认情况下,对于具有 16 个或更多 CPU 的系统,锁分区是打开的。 启用锁分区后,将在 SQL Server 错误日志中记录一条信息性消息。
获取已分区资源的锁时:
只能获取单个分区的 NL、Sch-S、IS、IU 和 IX 锁模式。
对于以分区 ID 0 开始并且按照分区 ID 顺序排列的所有分区,必须获取非 S、X、NL、Sch-S 和 IS 模式的共享 (IU) 锁、排他 (IX) 锁和其他锁。 已分区资源的这些锁会比相同模式中未分区资源的锁占用更多的内存,因为每个分区都是一个有效的单独锁。 内存的增加由分区数决定。 SQL Server 锁性能计数器会显示已分区锁和未分区锁使用的内存的相关信息。
以下代码示例说明了锁分区。 在这些示例中,为了显示一个具有 16 个 CPU 的计算机系统上的锁分区行为,在两个不同的会话中执行了两个事务。
这些 Transact-SQL 语句创建了后续示例中使用的测试对象。
-- Create a test table.
CREATE TABLE TestTable
(
col1 int
);
GO
-- Create a clustered index on the table.
CREATE CLUSTERED INDEX ci_TestTable ON TestTable (col1);
GO
-- Populate the table.
INSERT INTO TestTable
VALUES (1);
GO
示例 A
会话 1:
在一个事务中执行一个 SELECT 语句。 由于 HOLDLOCK 锁提示,此语句将获取并保留一个对此表的意向共享锁(IS 锁)(此例中忽略行锁和页锁)。 IS 锁只能在分配给事务的分区中获取。 对于此示例,假定 IS 锁是在 ID 为 7 的分区中获取。
-- Start a transaction.
BEGIN TRANSACTION;
-- This SELECT statement will acquire an IS lock on the table.
SELECT col1
FROM TestTable
WITH (HOLDLOCK);
会话 2:
事务启动,并且在此事务下运行的 SELECT 语句将获取共享锁 (S) 并将其保留在表中。 将获取所有分区的 S 锁,这将产生多个表锁,每个分区一个。 例如,在具有 16 个 CPU 的系统上,将对锁分区 ID 为 0-15 的锁分区发出 16 个 S 锁。 由于 S 锁与分区 ID 7 上由会话 1 中的事务持有的 IS 锁兼容,因此事务之间没有阻塞。
BEGIN TRANSACTION;
SELECT col1
FROM TestTable
WITH (TABLOCK, HOLDLOCK);
会话 1:
将在会话 1 下仍处于活动状态的事务下执行以下 SELECT 语句。 由于排他 (X) 表锁提示,事务将尝试获取该表的 X 锁。 但是,会话 2 中事务持有的 S 锁会阻塞分区 ID 0 的 X 锁。
SELECT col1
FROM TestTable
WITH (TABLOCKX);
示例 B
会话 1:
在一个事务中执行一个 SELECT 语句。 由于 HOLDLOCK 锁提示,此语句将获取并保留一个对此表的意向共享锁(IS 锁)(此例中忽略行锁和页锁)。 IS 锁只能在分配给事务的分区中获取。 对于此示例,假定 IS 锁是在 ID 为 6 的分区中获取。
-- Start a transaction.
BEGIN TRANSACTION;
-- This SELECT statement will acquire an IS lock on the table.
SELECT col1
FROM TestTable
WITH (HOLDLOCK);
会话 2:
在一个事务中执行一个 SELECT 语句。 由于 TABLOCKX 锁提示,事务将尝试获取表的排他 (X) 锁。 请记住,必须获取从分区 ID 0 开始的所有分区的 X 锁。 将获取所有分区 ID 0-5 的 X 锁,但它会被为分区 ID 6 获取的 IS 锁阻塞。
对于尚未获取 X 锁的分区 ID 7-15,其他事务可以继续获取锁。
BEGIN TRANSACTION;
SELECT col1
FROM TestTable
WITH (TABLOCKX, HOLDLOCK);
USE AdventureWorks2022;
GO
-- Enable snapshot isolation on the database.
ALTER DATABASE AdventureWorks2022 SET ALLOW_SNAPSHOT_ISOLATION ON;
GO
-- Start a snapshot transaction
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
GO
BEGIN TRANSACTION;
-- This SELECT statement will return
-- 48 vacation hours for the employee.
SELECT BusinessEntityID, VacationHours
FROM HumanResources.Employee
WHERE BusinessEntityID = 4;
在会话 2 上:
USE AdventureWorks2022;
GO
-- Start a transaction.
BEGIN TRANSACTION;
-- Subtract a vacation day from employee 4.
-- Update is not blocked by session 1 since
-- under snapshot isolation shared locks are
-- not requested.
UPDATE HumanResources.Employee
SET VacationHours = VacationHours - 8
WHERE BusinessEntityID = 4;
-- Verify that the employee now has 40 vacation hours.
SELECT VacationHours
FROM HumanResources.Employee
WHERE BusinessEntityID = 4;
在会话 1 上:
-- Reissue the SELECT statement - this shows
-- the employee having 48 vacation hours. The
-- snapshot transaction is still reading data from
-- the older, versioned row.
SELECT BusinessEntityID, VacationHours
FROM HumanResources.Employee
WHERE BusinessEntityID = 4;
在会话 2 上:
-- Commit the transaction; this commits the data
-- modification.
COMMIT TRANSACTION;
GO
在会话 1 上:
-- Reissue the SELECT statement - this still
-- shows the employee having 48 vacation hours
-- even after the other transaction has committed
-- the data modification.
SELECT BusinessEntityID, VacationHours
FROM HumanResources.Employee
WHERE BusinessEntityID = 4;
-- Because the data has been modified outside of the
-- snapshot transaction, any further data changes to
-- that data by the snapshot transaction will cause
-- the snapshot transaction to fail. This statement
-- will generate a 3960 error and the transaction will
-- terminate.
UPDATE HumanResources.Employee
SET SickLeaveHours = SickLeaveHours - 8
WHERE BusinessEntityID = 4;
-- Undo the changes to the database from session 1.
-- This will not undo the change from session 2.
ROLLBACK TRANSACTION;
GO
USE AdventureWorks2022;
GO
-- Enable READ_COMMITTED_SNAPSHOT on the database.
-- For this statement to succeed, this session
-- must be the only connection to the AdventureWorks2022
-- database.
ALTER DATABASE AdventureWorks2022 SET READ_COMMITTED_SNAPSHOT ON;
GO
-- Start a read-committed transaction
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
GO
BEGIN TRANSACTION;
-- This SELECT statement will return
-- 48 vacation hours for the employee.
SELECT BusinessEntityID, VacationHours
FROM HumanResources.Employee
WHERE BusinessEntityID = 4;
在会话 2 上:
USE AdventureWorks2022;
GO
-- Start a transaction.
BEGIN TRANSACTION;
-- Subtract a vacation day from employee 4.
-- Update is not blocked by session 1 since
-- under read-committed using row versioning shared locks are
-- not requested.
UPDATE HumanResources.Employee
SET VacationHours = VacationHours - 8
WHERE BusinessEntityID = 4;
-- Verify that the employee now has 40 vacation hours.
SELECT VacationHours
FROM HumanResources.Employee
WHERE BusinessEntityID = 4;
在会话 1 上:
-- Reissue the SELECT statement - this still shows
-- the employee having 48 vacation hours. The
-- read-committed transaction is still reading data
-- from the versioned row and the other transaction
-- has not committed the data changes yet.
SELECT BusinessEntityID, VacationHours
FROM HumanResources.Employee
WHERE BusinessEntityID = 4;
在会话 2 上:
-- Commit the transaction.
COMMIT TRANSACTION;
GO
在会话 1 上:
-- Reissue the SELECT statement which now shows the
-- employee having 40 vacation hours. Being
-- read-committed, this transaction is reading the
-- committed data. This is different from snapshot
-- isolation which reads from the versioned row.
SELECT BusinessEntityID, VacationHours
FROM HumanResources.Employee
WHERE BusinessEntityID = 4;
-- This statement, which caused the snapshot transaction
-- to fail, will succeed with read-committed using row versioning.
UPDATE HumanResources.Employee
SET SickLeaveHours = SickLeaveHours - 8
WHERE BusinessEntityID = 4;
-- Undo the changes to the database from session 1.
-- This will not undo the change from session 2.
ROLLBACK TRANSACTION;
GO
启用基于行版本控制的隔离级别
数据库管理员可以通过在 READ_COMMITTED_SNAPSHOT 语句中使用 ALLOW_SNAPSHOT_ISOLATION 和 ALTER DATABASE 数据库选项来控制行版本控制的数据库级别设置。
当 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON 时,用于支持该选项的机制将立即激活。 设置 READ_COMMITTED_SNAPSHOT 选项时,数据库中只允许存在执行 ALTER DATABASE 命令的连接。 在 ALTER DATABASE 完成之前,数据库中不允许有其他打开的连接。 数据库不必一定要处于单用户模式下。
以下 Transact-SQL 语句启用 READ_COMMITTED_SNAPSHOT:
ALTER DATABASE AdventureWorks2022 SET READ_COMMITTED_SNAPSHOT ON;
ALLOW_SNAPSHOT_ISOLATION 数据库选项设置为 ON 时,数据库中数据已修改的所有活动事务完成之前,数据库引擎实例不会为已修改的数据开始生成行版本。 如果存在活动的修改事务,数据库引擎将该选项的状态设置为 PENDING_ON。 所有修改事务完成后,该选项的状态更改为 ON。 在该选项处于 SNAPSHOT 状态之前,用户无法在数据库中启动 ON 事务。 同样,数据库管理员将 PENDING_OFF 选项设置为 ALLOW_SNAPSHOT_ISOLATION 时,数据库将跳过 OFF 状态。
以下 Transact-SQL 语句启用 ALLOW_SNAPSHOT_ISOLATION:
ALTER DATABASE AdventureWorks2022 SET ALLOW_SNAPSHOT_ISOLATION ON;
下表列出并说明了 ALLOW_SNAPSHOT_ISOLATION 选项的各个状态。 同时使用 ALTER DATABASE 和 ALLOW_SNAPSHOT_ISOLATION 选项不会妨碍当前正在访问数据库数据的用户。
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
BEGIN TRANSACTION;
SELECT t1.col5, t2.col5
FROM Table1 as t1
INNER JOIN SecondDB.dbo.Table2 as t2
ON t1.col1 = t2.col2;
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
BEGIN TRANSACTION;
SELECT t1.col5, t2.col5
FROM Table1 as t1 WITH (READCOMMITTED)
INNER JOIN SecondDB.dbo.Table2 as t2
ON t1.col1 = t2.col2;
启动事务时,使用 OLE DB 的应用程序可以调用 ITransactionLocal::StartTransaction,并将 isoLevel 设置为所需的事务隔离级别。 在自动提交模式下指定隔离级别时,使用 OLE DB 的应用程序可以将 DBPROPSET_SESSION 属性 DBPROP_SESS_AUTOCOMMITISOLEVELS 设置为所需的事务隔离级别。
USE AdventureWorks2022;
GO
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
GO
BEGIN TRANSACTION;
SELECT BusinessEntityID
FROM HumanResources.Employee;
COMMIT;
GO
USE AdventureWorks2022;
GO
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
GO
BEGIN TRANSACTION;
GO
SELECT JobTitle
FROM HumanResources.Employee WITH (NOLOCK);
GO
-- Get information about the locks held by
-- the transaction.
SELECT resource_type,
resource_subtype,
request_mode
FROM sys.dm_tran_locks
WHERE request_session_id = @@SPID;
-- End the transaction.
ROLLBACK;
GO
SET QUOTED_IDENTIFIER OFF;
GO
SET NOCOUNT OFF;
GO
CREATE TABLE TestTrans
(
ColA INT PRIMARY KEY,
ColB CHAR(3) NOT NULL
);
GO
CREATE PROCEDURE TransProc
@PriKey INT,
@CharCol CHAR(3)
AS
BEGIN TRANSACTION InProc;
INSERT INTO TestTrans VALUES (@PriKey, @CharCol);
INSERT INTO TestTrans VALUES (@PriKey + 1, @CharCol);
COMMIT TRANSACTION InProc;
GO
/* Start a transaction and execute TransProc. */
BEGIN TRANSACTION OutOfProc;
GO
EXEC TransProc 1, 'aaa';
GO
/* Roll back the outer transaction, this will
roll back TransProc's nested transaction. */
ROLLBACK TRANSACTION OutOfProc;
GO
EXECUTE TransProc 3,'bbb';
GO
/*
The following SELECT statement shows only rows 3 and 4 are
still in the table. This indicates that the commit
of the inner transaction from the first EXECUTE statement of
TransProc was overridden by the subsequent roll back of the
outer transaction.
*/
SELECT *
FROM TestTrans;
GO