即使在为操作

时间:2018-04-25 13:10:15

标签: c# sql-server ado.net sqlbulkcopy timeoutexception

超时问题:

  

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

我在我的应用程序数据库中有1700万条记录要转储。这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
        }
    }

我是否还有其他方法或解决方案可以解决此超时问题?

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

  

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

更新2:我删除了该事务,我将打开并关闭每个批处理的连接,并在转发任何批处理时发生错误,然后我将使用{{1删除所有以前保存的数据现在这可以转储testId然后我收到这个错误:

  

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

这是在catch部分,我尝试删除基于3 millions of data的旧数据,但它花了这么长时间然后它抛出了这个错误:

  

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

testId

2 个答案:

答案 0 :(得分:3)

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

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

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

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

你可以通过

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

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

@WaitForDealyToUse =恢复脚本之前要等待的时间(脚本使用WAITFOR DELAY)。必须采用这种格式&00; 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

答案 1 :(得分:3)

因此,在您的第二个选项中,您实际上能够将使用代码的数据“转储”到数据库中,只是为了找出数据库中的文件大小不足。

当自动增长设置为下降并且您已达到磁盘上可用的最大文件大小时,可能会发生这种情况。

您的第一次尝试失败,因为交易变得很大,以维持使用您的服务器所拥有的资源。

前两件事:

  1. 如果数据库处于完全恢复状态,请备份事务日志 模式或“检查”数据库以确保您拥有日志空间。
  2. 将表格划分为多个文件,通过制作包含多个文件的1个文件组来实现此目的,最好是将它们分散到多个磁盘阵列/控制器上以便您可以并行写入
  3. 然后,

    • 您必须在索引成为时重新创建索引 在发生此类错误后被禁用。
    • 这样的数据统计数据会非常糟糕 大更新
    • 每个索引都会减慢插入的因素,如果是 索引很糟糕,这会让事情变慢,具体取决于方式 您得到的许多索引拆分(如果您将数据插入到软件中) 索引的顺序或反对它,如果反对它就像 如果你的话,从底部而不是在顶部堆放啤酒箱 明白我的意思。
    • 如果你有一个企业版本使用分区功能,这个 因为你可以真正并行处理,所以会大大加快动作 数据并减少对分区数据的锁定。

    尝试在“导入”中进行备份,因为备份将保留跨国提交的数据,并且您的LDF文件压力较小。

    希望有所帮助

    沃尔特