具有事务和表变量的批处理存储过程

时间:2015-03-11 13:07:25

标签: sql sql-server sql-server-2008 tsql

我本周末进行了数据迁移,并发现我无法获得DBA的帮助,因此缺乏SQL Server知识。所有其他DBA都是Oracle,不会触及SQL Server。我的安全性有限,我无法创建一个作业或SSIS包来处理这个问题。

我有一个脚本,我正在批量运行。在这个批处理中,我正在运行带有逻辑的存储过程。存储过程有表变量,我只是读到你无法将它们作为事务运行。有人可以看到这种整体方法,看看我是否遗漏了任何东西,或者我可以更有效地运行它? BigTable有大约25M的记录,所有索引,FK,约束都被删除。我打算暂时为这批次添加一些索引。它将运行大约5天。

Create Procedure ConvertStuff AS
BEGIN

declare @id uniqueIdentifier
declare @importdate DateTime
declare @Data varchar(max)

declare @tableX table 
    ---
declare @tableY table 
    ---
declare @tableZ table 
    ---


SET NOCOUNT ON
    select top 1 @ID = bt.ID, @Data = bt.RawData, @importDate = bt.ImportDate from Processed p with (NOLOCK)
        Inner join BigTable bt with (NOLOCK) on p.ID = bt.ID where p.isProcessed = 0

    while (not @ID is null)
    Begin
        BEGIN TRY
            --Do stuff here
        END TRY
        BEGIN CATCH

            DECLARE @ErrorMessage NVARCHAR(4000);
            DECLARE @ErrorSeverity INT;
            DECLARE @ErrorState INT;

            SELECT @ErrorMessage = ERROR_MESSAGE(),
                   @ErrorSeverity = ERROR_SEVERITY(),
                   @ErrorState = ERROR_STATE();

            RAISERROR (@ErrorMessage,
                       @ErrorSeverity,
                       @ErrorState
                       );


            update bigTable set isProcessed = -1 where ID = @ID
            break
        END CATCH
    select top 1 @ID = bt.ID, @Data = bt.RawData, @importDate = bt.ImportDate from Processed p with (NOLOCK)
        Inner join BigTable bt with (NOLOCK) on p.ID = bt.ID where p.isProcessed = 0
    END
    --Do I need to drop the @ tables here? Should I convert these to # ?
END

--------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Running this....

-- This will be dropped once the migration is done
CREATE TABLE [Processed]
(
    [ID] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
    [isProcessed] [bit] Default(0) NOT NULL,
)

CREATE NONCLUSTERED INDEX [idx_isProcessed] ON [Processed]
(
    [isProcessed] ASC
)

GO

SET ROWCOUNT 25000

declare @msg varchar(50)
DECLARE @ErrorMessage NVARCHAR(4000)
DECLARE @ErrorSeverity INT
DECLARE @ErrorState INT

While (1=1) 
BEGIN
    BEGIN TRY
    BEGIN TRANSACTION
    Insert into [Processed] (ID, isProcessed) 
        Select ID, 0 from BigTable where recordUpdated = 0

    exec ConvertStuff

    IF @@ROWCOUNT = 0
    BEGIN
        Print @@ROWCOUNT
        COMMIT TRANSACTION
        BREAK
    END

    COMMIT TRANSACTION

    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION


        SELECT @ErrorMessage = ERROR_MESSAGE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE();
        RAISERROR (@ErrorMessage, -- Message text.
                   @ErrorSeverity, -- Severity.
                   @ErrorState -- State.
                   );
        BREAK
    END CATCH
END

drop table Processed

1 个答案:

答案 0 :(得分:2)

这是在不查杀系统的情况下有效批量复制表的正确方法。仅当表在复制期间是只读时,此策略才有效。如果您的表格可以更改,则必须将其与其他策略配对,以便跟踪和更新已更改的记录。

批量复制方法将阻止您对表进行4天锁定,并允许您定期备份事务日志。如果您需要停止或失败,它还会阻止4天的回滚。

在发布之前运行此操作,然后在发布到表格上限期间再次运行。与往常一样,在尝试使用实时系统之前练习运行并停止脚本。

DECLARE @CurrentId UNIQUEIDENTIFIER,
        @BatchSize INT;
SET @BatchSize = 50000;

SELECT TOP 1
    @CurrentId = ID
FROM NewTable
ORDER BY ID DESC;

SELECT
    @LastId = ID
FROM OldTable
ORDER BY ID DESC;

IF (@CurrentId IS NULL)
    SET @CurrentId = '00000000-0000-0000-0000-000000000000';

PRINT 'Copying from ' + CONVERT(VARCHAR(40), @CurrentId) + ' to ' + CONVERT(VARCHAR(40), @LastId);

CREATE TABLE #Batch
(
    ID UNIQUEIDENTIFIER
);

WHILE (@CurrentId < @LastId)
BEGIN
    PRINT CONVERT(VARCHAR(40), @CurrentId);

    TRUNCATE TABLE #Batch;

    -- Get your new batch
    INSERT INTO #Batch
    SELECT TOP (@BatchSize)
        *
    FROM OldTable
    WHERE ID > @CurrentId
    ORDER BY ID;

    -- I'd recommend being specific with columns, you might also need to turn on 'SET IDENTITY_INSERT <Table> ON'
    INSERT INTO NewTable
    SELECT *
    FROM OldTable
    INNER JOIN #Batch ON #Batch.ID = OldTable.ID
    LEFT JOIN NewTable ON NewTable.ID = OldTable.ID
    WHERE NewTable.ID IS NULL;

    IF (@@ERROR <> 0)
        BREAK

    SELECT TOP 1
        @CurrentId = ID
    FROM #Batch
    ORDER BY ID DESC;
END