即使在为操作设置超时属性后也会出现超时异常

ILo*_*low 7 c# sql-server ado.net sqlbulkcopy timeoutexception

超时问题:

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

我有17亿条记录要转储到我的应用程序数据库中.这1200万条记录是2条数据库记录之间比较操作的结果.

我比较了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
        }
    }
Run Code Online (Sandbox Code Playgroud)

有没有其他方法或解决方案我缺少并可以解决此超时问题?

更新:设置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'文件组已满.通过删除不需要的文件,删除文件组中的对象,向文件组添加其他文件或为文件组中的现有文件设置自动增长来创建磁盘空间.

这是在catch部分,我试图删除旧数据,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);
                    }
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

小智 3

前言/注意:这是一个适合某些需求的解决方案,但可能不/不推荐用于所有情况,并且应该测试/评估它是否是您正在做的事情的最佳解决方案。

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

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

我使用这种方法的方法是将 SP 调用放入我的脚本中,它将运行,如果日志文件太大,它将等待给日志文件时间缩小,然后再继续。

你这样称呼它

EXEC dbo.LogFileFullCheckAndWaitFor 
     @DBNameToCheck = 'DBALocal', 
     @WaitForDealyToUse = '00:00:05.00', 
     @LogFileUsedPercentToCheck = '10'
Run Code Online (Sandbox Code Playgroud)

@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
Run Code Online (Sandbox Code Playgroud)