首页 > 解决方案 > 即使在为操作设置超时属性后超时异常

问题描述

超时问题:

超时已过。在操作完成之前超时时间已过或服务器没有响应。\r\n语句已终止。

我的应用程序数据库中有 1700 万条记录要转储。这 1200 万条记录是两个数据库记录之间比较操作的结果。

我比较了 2 个数据库记录,然后在数据表中填充不匹配记录(基于某些标准),一旦该数据表达到某个限制,如 1000 或 500 等,我将此数据表发送到 SQL 批量复制以进行批量导入,然后清空数据表.

我在事务中执行整个操作,以便插入 X 记录,并且在比较过程中出现任何错误,因此我将回滚这些 X 记录。

但正因为如此,我在进行批量复制时遇到了超时问题。

我检查了各种不同batchsize like 5000,1000,500,300的等。我在所有这些批量大小中都遇到了超时问题。

一旦我将批量复制超时设置为 0,然后我会出现以下错误:

我的数据库的事务日志已满。

1000 条记录达到 270 万条,然后引发超时问题,

有 500 条记录时,它达到了大约 210 万条记录,然后引发错误。

使用 300,200,100 也会引发超时错误。

我还将连接字符串中的连接超时设置为 30 分钟。

代码 :

public class SaveRepo : IDisposable
    {
        DataTable dataTable;
        SqlConnection connection;
        string connectionString;
        SqlTransaction transaction;
        SqlBulkCopy bulkCopy;
        int testId,

        public SaveRepo (int testId)//testId=10364
        {
            this.connectionString = connectionString;
            dataTable = new DataTable();
            connection = new SqlConnection(connectionString);
            connection.Open();
            transaction = connection.BeginTransaction();
            bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction);
            bulkCopy.BulkCopyTimeout = 60;
            bulkCopy.EnableStreaming = true;
            bulkCopy.DestinationTableName = "dbo.Sales";
            bulkCopy.BatchSize = 100;
            bulkCopy.SqlRowsCopied +=
                  new SqlRowsCopiedEventHandler(OnSqlRowsCopied);
            bulkCopy.NotifyAfter = 100;
        }

       void Dump()
        {
            try
            {
                bulkCopy.WriteToServer(dataTable);
            }
            catch(Exception ex) // timeout error
            {
                throw ex;
            }
        }

    void FillDatatable(object[] row)
    {
        if (dataTable.Rows.Count == 100)
        {
           Dump();
           dataTable.Clear();
        }
        dataTable.Rows.Add(row);
    }

        public void End()
        {
            transaction.Commit();
            //dispose the stuffs also
        }
    }

有没有我遗漏的其他方法或解决方案可以解决这个超时问题?

更新:设置BulkCopyTimeout为 0 并让batchsize =1000我收到此错误后3593000 records bulk copied

无法为数据库 'XYZ' 中的对象 'dbo.Sales'.'PK_dbo.Sales' 分配空间,因为 'PRIMARY' 文件组已满。通过删除不需要的文件、删除文件组中的对象、向文件组添加其他文件或为文件组中的现有文件设置自动增长来创建磁盘空间。

更新 2:我删除了事务,我将打开和关闭每个批次的连接,如果发生错误,则在转储任何批次时,我将使用删除所有以前保存的数据testId。现在这适用于转储,3 millions of data然后我收到此错误:

无法为数据库 'XYZ' 中的对象 'dbo.Sales'.'PK_dbo.Sales' 分配空间,因为 'PRIMARY' 文件组已满。通过删除不需要的文件、删除文件组中的对象、向文件组添加其他文件或为文件组中的现有文件设置自动增长来创建磁盘空间。

这在我尝试删除旧数据的捕获部分中,testId但它需要很长时间,然后它会抛出这个错误:

我的数据库的事务日志已满。

void Dump()
        {
            using (SqlConnection connection =
                  new SqlConnection(connectionString))
            {
                connection.Open();
                using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString))
                {
                    bulkCopy.DestinationTableName = "dbo.Sales";
                    bulkCopy.EnableStreaming = true;
                    try
                    {
                        bulkCopy.WriteToServer(dataTable);
                    }
                    catch(Exception ex)
                    {
                        connection.Close();
                        SalesRepo.Delete(connectionString, testId);
                    }
                }
            }
        }

标签: c#sql-serverado.netsqlbulkcopytimeoutexception

解决方案


前言/注意:这是一种适用于某些需求的解决方案,但可能不适用于/不推荐用于所有情况,如果它是您正在做的最佳解决方案,则应进行测试/评估。

这是为了解决事务日志填充问题:

我有类似的问题,我正在处理日志文件密集型的东西,我填了几次。数据从日志文件传送/删除后,日志文件将缩小,但这需要 3-8 分钟(取决于数据库和服务器设置)。为了缓解这个问题,我创建了一个 SP 来检查日志文件,如果它达到一定的大小,它将等待给定的时间段。所有这些值都是您传递给 SP 的变量。

我使用它的方式是将 SP 调用放在我的脚本中,它会运行,如果日志文件太大,它会等待让日志文件有时间在继续之前缩小。

你叫它

EXEC dbo.LogFileFullCheckAndWaitFor 
     @DBNameToCheck = 'DBALocal', 
     @WaitForDealyToUse = '00:00:05.00', 
     @LogFileUsedPercentToCheck = '10'

@DBNameToCheck = 您要检查的数据库日志文件

@WaitForDealyToUse = 在恢复脚本之前要等待的时间(脚本使用 WAITFOR DELAY)。它必须采用这种格式 '00:00:05.00' (HH:MM:SS:MM),您可以省略 MM(毫秒)

@LogFileUsedPercentToCheck = 这是您将通过的小数点后 2 位的数字,如果日志文件超过此百分比,它将触发 WAIT。它还将立即在 SQL 输出窗口中显示一条消息(无需缓冲任何内容)。它通过使用 RAISERROR 来做到这一点,但注意它使用低严重性错误编号,因此它不会触发 try/catch 块的错误(这是我发现在没有正常缓冲时间的情况下立即显示消息的唯一方法)。如果您不在 Management Studio 中执行,则可能不需要此操作。

根据您的权限级别,这可能/可能不起作用。

USE [DBALocal]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO


create PROCEDURE [dbo].[LogFileFullCheckAndWaitFor] (
    @DBNameToCheck VARCHAR(250),
    @WaitForDealyToUse VARCHAR(50),
    @LogFileUsedPercentToCheck DECIMAL(10,2)
)

AS
BEGIN

    SET NOCOUNT ON;


    BEGIN TRY
            -- table to hold the data returned from 
            DECLARE @LogSize AS TABLE (
                DatabaseName VARCHAR(250), 
                LogSize DECIMAL(10,2), 
                LogUsedPercent DECIMAL(10,2), 
                Status INT
            )

            DECLARE @LogUsedPercent AS DECIMAL(10,2)
            DECLARE @RaiseErrorMessage AS VARCHAR(1000)

            -- build out the error message here
            SET @RaiseErrorMessage = 'LOG FILE REACHED ' + CAST(@LogFileUsedPercentToCheck AS VARCHAR(50)) + ' full so pausing for ' + CAST(@WaitForDealyToUse AS VARCHAR(50)) + ' minutes'

            /*
                -- removed the below because may need higher permissions, so using query below below this instead

                INSERT INTO @LogSize
                EXEC('DBCC SQLPERF(LOGSPACE) WITH NO_INFOMSGS;')    

                SELECT @LogUsedPercent = LogUsedPercent
                --select *,  CAST(LogSize*(LogUsedPercent * .01) AS DECIMAL(10,2)) AS TotalSizeUsed, CAST(LogSize - (LogSize*(LogUsedPercent * .01)) AS DECIMAL(10,2)) AS LogSizeLeft
                FROM @LogSize 
                WHERE DatabaseName = @DBNameToCheck 
            */

                --- this has lower required permissions then the above
                -- this gets the log file used percent
                SELECT @LogUsedPercent = cast(pc2.cntr_value*100.0/pc1.cntr_value as dec(5,2))
                FROM sys.dm_os_performance_counters (NOLOCK) AS pc1
                INNER JOIN sys.dm_os_performance_counters (NOLOCK) AS pc2 ON pc1.instance_name = pc2.instance_name
                WHERE  pc1.object_name LIKE '%Databases%'
                AND pc2.object_name LIKE '%Databases%'
                AND pc1.counter_name = 'Log File(s) Size (KB)'
                AND pc2.counter_name = 'Log File(s) Used Size (KB)'
                AND pc1.instance_name not in ('_Total', 'mssqlsystemresource')
                AND pc1.cntr_value > 0
                AND pc1.instance_name = @DBNameToCheck


            -- now if the current log file used percent is > what is passed, it displays a message, and waits for the time passed
            IF (@LogUsedPercent > @LogFileUsedPercentToCheck)
                BEGIN
                    SET @RaiseErrorMessage += ' Current Log Used Percent is: ' + CAST(@LogUsedPercent AS VARCHAR(50)) + ' '

                    -- Do this so it displays message immediatly, it is a low error message number so it will not be caught by the try catch blocks
                    -- but using the "WITH NOWAIT" displays the message instantly instead of waiting for  buffer to display
                    RAISERROR(@RaiseErrorMessage, 0, 1) WITH NOWAIT

                    -- now wait for the allowted time
                    WAITFOR DELAY @WaitForDealyToUse 
                END

            -- return the percent if they want to capture it
            SELECT @LogUsedPercent



    END TRY
    BEGIN CATCH

        -- run your catch logic here


    END CATCH
END

推荐阅读