存储过程中循环内的事务

时间:2009-10-08 17:27:24

标签: sql sql-server sql-server-2005 tsql distributed-transactions

我正在开发一个程序,它将使用本地数据库中的记录更新远程服务器上的大量项目。这是伪代码。

CREATE PROCEDURE UpdateRemoteServer
    pre-processing
    get cursor with ID's of records to be updated
    while on cursor
        process the item

无论我们对它进行多少优化,例程都需要一段时间,因此我们不希望将整个事务作为单个事务处理。这些项目在处理后会被标记,因此如果进程中断,应该可以选择我们停止的位置。

在开始/提交tran中包装循环的内容(“处理项目”)并不起作用......似乎整个语句

EXEC UpdateRemoteServer

被视为单个交易。如何将每个项目处理作为完整的单独交易?

请注意,我很乐意将其作为“非交易更新”运行,但该选项仅在2008年提供(据我所知)。

5 个答案:

答案 0 :(得分:3)

EXEC程序创建交易。一个非常简单的测试将显示:

create procedure usp_foo
as
begin
  select @@trancount;
end
go

exec usp_foo;

usp_foo中的@@ trancount为0,因此EXEC语句不会启动隐式事务。如果您在进入UpdateRemoteServer时启动了一个事务,则意味着有人启动了该事务,我不能说是谁。

话虽这么说,使用远程服务器和DTC来更新项目将会表现得非常糟糕。其他服务器至少也是SQL Server 2005吗?也许您可以将请求排队以更新并在本地和远程服务器之间使用messaging,并让远程服务器根据消息中的信息执行更新。它会表现得更好,因为两台服务器只需要处理本地事务,并且由于排队消息的松散耦合,您可以获得更好的可用性。

<强>更新

游标实际上并不启动事务。典型的基于游标的批处理通常基于游标和批量更新到特定大小的事务中。这对于夜间作业来说相当常见,因为它允许更好的性能(由于更大的事务大小而导致日志刷新吞吐量),并且可以中断和恢复作业而不会失去任务。批处理循环的简化版本通常如下:

create procedure usp_UpdateRemoteServer
as
begin
  declare @id int, @batch int;
  set nocount on;
  set @batch = 0;

  declare crsFoo cursor 
    forward_only static read_only 
    for 
    select object_id 
    from sys.objects;

  open crsFoo;

  begin transaction
  fetch next from crsFoo into @id ;
  while @@fetch_status = 0
  begin

    -- process here

    declare @transactionId int;
    SELECT @transactionId = transaction_id 
      FROM sys.dm_tran_current_transaction;
    print @transactionId;

    set @batch = @batch + 1
    if @batch > 10
    begin
      commit;
      print @@trancount;
      set @batch = 0;
      begin transaction;
    end
    fetch next from crsFoo into @id ;
  end
  commit;
  close crsFoo;

  deallocate crsFoo;
end
go

exec usp_UpdateRemoteServer;

我省略了错误处理部分(开始尝试/开始捕获)和花哨的@@ fetch_status检查(静态游标实际上不需要它们)。此演示代码显示在运行期间启动了几个不同的事务(不同的事务ID)。很多时候批处理也deploy transaction savepoints at each item处理,因此他们可以安全地跳过导致异常的项目,使用类似于我链接中的模式,但这不适用于分布式事务,因为保存点和DTC不混合。

答案 1 :(得分:1)

编辑:正如下面Remus所指出的,游标默认情况下不会打开一个交易;因此,这不是OP提出的问题的答案。我仍然认为有比光标更好的选择,但这并没有回答这个问题。

斯图

原始答案:

您描述的特定症状是由于游标默认打开一个事务,因此无论您如何工作,只要您使用游标,您就会有一个长时间运行的事务(除非你完全避免锁定,这是另一个坏主意。)

正如其他人指出的那样,游标SUCK。你99.9999%的时间都不需要它们。

如果要在SQL Server的数据库级别执行此操作,确实有两个选项:

  1. 使用SSIS执行操作;非常快,但在您特定的SQL Server风格中可能无法使用。

  2. 因为您正在处理远程服务器,并且您担心连接,您可能必须使用循环机制,因此请改用WHILE并一次提交批处理。虽然WHILE有许多与游标相同的问题(循环在SQL中仍然很糟糕),但是你可以避免创建外部事务。

  3. 斯图

答案 2 :(得分:0)

你只能从sql server或app中运行吗?如果是,请获取要处理的列表,然后在应用程序中循环以仅根据需要处理子集。

然后交易应由您的应用处理,并且只应锁定正在更新的项目/项目所在的页面。

答案 3 :(得分:0)

在进行交易工作时,切勿在循环中一次处理一个项目。您可以遍历它们的记录处理组,但从不会一次执行一条记录。改为使用基于集合的插入,您的性能将从几小时变为几分钟甚至几秒。如果您使用游标插入更新或删除,并且每个语句中没有处理至少1000行(每次都不是一个),那么您做错了。游标是一种非常糟糕的做法。

答案 4 :(得分:0)

只是一个想法..

  • 只调用程序时处理一些项目(例如,只处理TOP 10项目)
  • 处理那些

希望这将是交易的结束。

然后编写一个调用该过程的包装器,只要有更多工作要做(使用简单的count(..)来查看是否有项目或者让过程返回true表示还有更多的工作要做

不知道这是否有效,但也许这个想法很有用。