Исключение тайм-аута даже после установки свойства тайм-аута для операции - PullRequest
0 голосов
/ 25 апреля 2018

Время ожидания:

Истекло время ожидания. Время ожидания истекло до завершения операции, или сервер не отвечает. \ R \ nНе выполняется оператор.

У меня есть 17 миллионов записей для сохранения в моей базе данных приложения. Эти 12 миллионов записей являются результатом операции сравнения двух записей базы данных.

Я сравниваю 2 записи базы данных, затем заполняю записи несоответствия (на основе некоторых критериев) в таблице данных, и как только эта таблица данных достигает некоторого предела, например 1000 или 500 и т. Д. Я отправляю эту таблицу данных в массовую копию SQL для массового импорта, а затем очищаю таблицу данных.

Я делаю всю эту операцию внутри транзакции, чтобы я вставил Х-записи, и в процессе сравнения возникает любая ошибка, поэтому я откатив эти Х-записи.

Но из-за этого у меня возникает проблема тайм-аута при выполнении массового копирования.

Я проверил различные варианты batchsize like 5000,1000,500,300 и т. Д. У меня возникают проблемы с тайм-аутом во всем этом размере пакета.

Как только я установил тайм-аут для массового копирования на 0, но затем я вижу следующую ошибку:

Журнал транзакций для моей базы данных полон.

С 1000 записей он достигает 2,7 миллиона, а затем выбрасывает проблемы тайм-аута,

С 500 записями он достиг около 2,1 миллиона записей, а затем выдает ошибку.

С 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:

Не удалось выделить место для объекта 'dbo.Sales'. 'PK_dbo.Sales' в базе данных 'XYZ', поскольку файловая группа 'PRIMARY' заполнена. Создайте место на диске, удалив ненужные файлы, удалив объекты в файловой группе, добавив дополнительные файлы в файловую группу или установив автоматический рост для существующих файлов в файловой группе.

Обновление 2: Я удалил транзакцию, и я открою и закрою соединение для каждого пакета, и, при возникновении сброса любого пакета, если произойдет ошибка, я удалю все ранее сохраненные данные, используя testId. Теперь это работает до сброса 3 millions of data, тогда я получаю эту ошибку:

Не удалось выделить место для объекта 'dbo.Sales'. 'PK_dbo.Sales' в базе данных 'XYZ', поскольку файловая группа '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);
                    }
                }
            }
        }

Ответы [ 2 ]

0 голосов
/ 30 апреля 2018

Таким образом, по второму варианту вы фактически смогли «сбросить» данные, используя код, в базу данных только для того, чтобы узнать, что у вас заканчивается размер файла в базе данных.

Это может произойти, если для автоматического увеличения задано падение или если вы достигли максимального размера файла, доступного на вашем диске.

Ваша первая попытка не удалась, так как Транзакция стала слишком большой, чтобы поддерживать использование ресурсов вашего сервера.

Первые 2 вещи:

  1. Сделайте резервную копию вашего журнала транзакций, если ваша база данных полностью восстановлена режим или «проверить» базу данных, чтобы убедиться, что у вас есть пространство журнала.
  2. Разбейте вашу таблицу по нескольким файлам, вы сделаете это, создав 1 файловую группу, содержащую несколько файлов, лучше всего распределить их по нескольким дисковым массивам / контроллерам, чтобы вы могли распараллеливать записи

Тогда

  • Вам придется заново создавать индексы, поскольку индексы стали отключен после такой ошибки.
  • статистика данных будет очень плохой после такого большое обновление
  • каждый индекс будет замедлять вставку в несколько раз, если индексы плохие, это будет действительно замедлять вещи в зависимости от того, вы получите много разделений индекса (если ваши данные вставляются в программу порядок индекса или против него, если против него это как складывая ящики с пивом снизу, а не сверху, если вы пойми о чем я.
  • если у вас версия Enterprise использовала функцию секционирования, это значительно ускорит действие, так как вы можете действительно параллельный процесс данные и уменьшите блокировку на разделенные данные.

Попробуйте выполнить резервное копирование в середине вашего «импорта», так как резервная копия сохранит данные, принятые транснационально, и ваш LDF-файл будет менее подвержен стрессу.

0 голосов
/ 30 апреля 2018

Предисловие / ПРИМЕЧАНИЕ. Это решение, которое хорошо работает для некоторых нужд, но не может / не рекомендуется для всех ситуаций и должно быть проверено / оценено, если это лучшее решение для того, что вы делаете.

Это чтобы решить проблему с заполнением журнала транзакций:

У меня была похожая проблема, когда я работал над чем-то, что интенсивно занимало файл журнала, и я заполнял его пару раз.Файл журнала будет уменьшен после отправки или удаления данных из файла журнала, но это займет 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» (ЧЧ: ММ: СС: ММ), вы можете не указывать ММ (миллисекунды)

@ LogFileUsedPercentToCheck = Это число с двумя десятичными знакамичто вы передадите, и если файл журнала превысит этот процент, он вызовет 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
...