4

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

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

无论我们如何优化它,该例程都需要一段时间,因此我们不希望将整个事情作为单个事务处理。这些项目在处理后会被标记,因此如果过程中断,应该可以从我们离开的地方继续。

在 begin/commit tran 中包装循环的内容(“处理项目”)并不能解决问题......似乎整个语句

EXEC UpdateRemoteServer

被视为单个事务。如何将每个项目流程作为一个完整的、单独的事务处理?

请注意,我很乐意将这些作为“非事务更新”运行,但该选项仅在 2008 年可用(据我所知)。

4

5 回答 5

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 吗?也许您可以对更新请求进行排队,并在本地和远程服务器之间使用消息传递,并让远程服务器根据消息中的信息执行更新。它将表现得更好,因为两个服务器只需要处理本地事务,并且由于排队消息的松散耦合,您可以获得更好的可用性。

更新

游标实际上不会启动事务。典型的基于游标的批处理通常是基于游标和批量更新到一定大小的事务。这对于通宵作业来说相当普遍,因为它可以提供更好的性能(由于更大的事务大小而导致日志刷新吞吐量)并且可以中断和恢复作业而不会丢失所有内容。批处理循环的简化版本通常如下所示:

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)。很多时候,批次还在处理的每个项目上部署事务保存点,以便他们可以安全地跳过导致异常的项目,使用类似于我链接中的模式,但这不适用于分布式事务,因为保存点和 DTC 不混合.

于 2009-10-08T17:53:07.403 回答
1

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

斯图

原始答案:

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

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

如果您想使用 SQL Server 在数据库级别执行此操作,您确实有两个选择:

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

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

斯图

于 2009-10-08T18:02:05.607 回答
0

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

然后事务应该由您的应用程序处理,并且应该只锁定正在更新的项目/项目所在的页面。

于 2009-10-08T17:33:52.950 回答
0

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

于 2009-10-08T17:52:29.433 回答
0

只是一个想法..

  • 调用过程时只处理少数项目(例如只处理前 10 个项目)
  • 处理那些

希望这将是交易的结束。

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

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

于 2009-10-08T18:08:48.293 回答