SqlTransaction завершено

У меня есть приложение, которое потенциально делает тысячи вставок в базу данных SQL Server 2005. Если сбой по какой-либо причине невозможен (ограничение внешнего ключа, длина поля и т.д.), Приложение предназначено для регистрации ошибки вставки и продолжения.

Каждая вставка не зависит от других, поэтому транзакции не нужны для целостности базы данных. Однако мы хотим использовать их для повышения производительности. Когда я использую транзакции, мы получим следующую ошибку примерно на 1 из каждых 100 коммитов.

This SqlTransaction has completed; it is no longer usable.
   at System.Data.SqlClient.SqlTransaction.ZombieCheck()
   at System.Data.SqlClient.SqlTransaction.Commit()

Чтобы попытаться отследить причину, я поместил инструкции трассировки при каждой операции транзакции, чтобы я мог убедиться, что транзакция не была закрыта перед вызовом commit. Я подтвердил, что мое приложение не закрывает транзакцию. Затем я снова запустил приложение, используя те же самые входные данные, и он преуспел.

Если я выключу регистрацию, он снова сработает. Верните его, и он преуспеет. Это включение/выключение осуществляется через app.config без необходимости перекомпиляции.

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

Я видел одну запись MS KB, указывающую на ошибку с. framework 2.0 framework, которая может вызвать подобные проблемы (http://support.microsoft.com/kb/912732). Однако исправление, которое они предоставили, не решает эту проблему.

Ответ 1

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

Но недостатком является: если вставка не удалась, любые другие вставки в текущей партии N будут отброшены при откате транзакции.

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

using(SqlConnection connection = ...)
{
    connection.Open();
    using(SqlTransaction transaction = connection.BeginTransaction())
    {
        ... do stuff ...
        transaction.Commit(); // commit if all is successful
    } // transaction.Dispose will be called here and will rollback if not committed
} // connection.Dispose called here

Пожалуйста, отправьте код, если вам нужна дополнительная помощь.

Ответ 2

Спасибо за отзывы. Я работал с кем-то из MSFT на форумах MSDN, чтобы выяснить, что происходит. Оказывается, проблема связана с тем, что одна из вставок не работает из-за проблемы с преобразованием даты.

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

У меня есть полная программа для тиражирования этой проблемы. Если кто-то хочет его увидеть или обменять с помощью MSFT, вы можете найти поток в новостных группах MSFT в файле microsoft.public.dotnet.framework.adonet под потоком ошибок SqlTransaction.ZombieCheck.

Ответ 3

Имейте в виду, что ваше приложение не является единственным участником транзакции - SQL Server также задействован.

Ошибка, которую вы указываете:

Это SqlTransaction завершено; Это больше не используется. в System.Data.SqlClient.SqlTransaction.ZombieCheck() в System.Data.SqlClient.SqlTransaction.Commit()

не указывает, что транзакция завершена, только она завершена.

Мое первое предложение состоит в том, что ваш сервер отключил транзакцию, потому что он либо слишком долгое время (эллипсовое время на стене), либо слишком велико (слишком много изменений или слишком много блокировок).

Мое второе предложение - проверить, что вы правильно очищаете соединения и транзакции. Возможно, вы столкнулись с проблемами, потому что иногда вы изматываете пул некоторого ресурса, прежде чем вещи автоматически перерабатываются.

Например, DbConnection реализует IDisposable, поэтому вам нужно убедиться, что вы правильно очистите - с помощью используя выражение, если вы можете, или, позвонив Dispose() напрямую, если вы можете "т. 'DbCommand' аналогичен, поскольку он также реализует IDisposable.

Ответ 4

Это исключение вызывается из-за того, что фактическая транзакция БД уже откатна, поэтому объект .NET, представляющий ее на стороне клиента, уже является "зомби".

Более подробное объяснение здесь. В этом сообщении объясняется, как написать правильный код отката транзакций для таких сценариев.

Ответ 5

Хорошо, сначала IMO, вы не должны ожидать, что ваше приложение будет иметь дело с такими "жесткими" ошибками. Ваша заявка должна понимать, что такое эти бизнес-правила, и учитывать их. НЕ принуждайте базу данных к бизнес-правилу или коду ограничения ограничений. Это должен быть только кодекс правил данных и при этом быть грациозным в отношении интерфейса с RETURN и другими методами. Компоновка схемы не должна сильно затягиваться.

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

Попробуйте это. Вставьте строки, которые не сбой с ограничением базы данных, или другие ошибки, и обработайте вставки с фиксацией. Посмотрите, что произойдет. Затем запишитесь в некоторые записи, которые не пройдут, обработайте фиксацию и посмотрите, получите ли вы свою прекрасную ошибку. В-третьих, снова запустите ошибки, выполните принудительный откат и убедитесь, что вам удалось.

Просто некоторые идеи. Опять же, в качестве сводки, я думаю, что это связано с тем, что не изъятие определенных ошибок из базы данных изящным способом для вещей, которые являются "жесткими" ошибками, и ожидает, что перед ними будет работать с ними. Опять же, мое экспертное мнение, NOPE, этого не делают. Это беспорядок. Ваше приложение должно накладываться на знания о правилах на оборотной стороне. Эти вещи существуют только для того, чтобы убедиться, что это не происходит во время тестирования, и случайную ошибку, которая выглядит так, как показано на рисунке, чтобы затем поставить перед собой задачу обработать забытый поиск в таблице внешних ключей.

Надеюсь, что это поможет.

Ответ 6

Моя проблема была похожей, и оказалось, что я делал это сам с собой. Я запускал кучу скриптов в проекте VS2008 для создания хранимых процедур. В одном из процессов я использовал транзакцию. script выполнял создание proc внутри транзакции, а не включал код транзакции в качестве части процедуры. Поэтому, когда он дошел до конца без ошибок, он совершил транзакцию для script. Код .Net также использовал и затем совершил транзакцию, и эффект зомби пришел, когда он попытался закрыть транзакцию, которая уже была закрыта внутри SQL script. Я удалил транзакцию из SQL script, опираясь на транзакцию, открытую и проверенную в коде .Net, и это решило проблему.

Ответ 7

Согласно этому сообщению: http://blogs.msdn.com/b/dataaccesstechnologies/archive/2010/08/24/zombie-check-on-transaction-error-this-sqltransaction-has-completed-it-is-no-longer-usable.aspx

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

    public static void TryRollback(this System.Data.IDbTransaction t)
    {
        try
        {
            t.Rollback();
        }
        catch (Exception ex)
        {
            // log error in my case
        }
    }

    public static void TryCommit(this System.Data.IDbTransaction t)
    {
        try
        {
            t.Commit();
        }
        catch (Exception ex)
        {
            // log error in my case
        }
    }

Проверьте этот пример на веб-сайте msdn: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction.aspx

Ответ 8

Вы доказали, что ваши данные в порядке, вне всяких разумных сомнений.
FWIW, Я бы предпочел переместить вставку в SPROC и вообще не использовать транзакцию.
Если вам нужно, чтобы пользовательский интерфейс был отзывчивым, используйте фоновый рабочий, чтобы выполнить хруст базы данных.
Для меня транзакция предназначена для взаимосвязанных действий, а не для экономии времени. Стоимость вставки должна оплачиваться где-то вдоль линии.

Недавно я использовал профилировщик ANTS в приложении базы данных и был поражен, увидев прерывистые исключения SQlClient, демонстрирующие прочный код. Ошибки глубоки в структуре при открытии соединения. Они никогда не выходят на поверхность и не обнаруживаются кодом клиента. Так... Пункт?
Это не все твердые камни, перемещайте тяжелую работу с U.I. и принять стоимость. НТН Боб

Ответ 9

Я также столкнулся с той же проблемой.

This SqlTransaction has completed; it is no longer usable

После ряда исследований я обнаружил, что размер файла журнала базы данных составляет 40 ГБ.
Это большой объем данных, который вызывает транзакцию моей транзакции sql.

Итак, Мое решение - размер файла журнала shrink, увидев эту ссылку ссылки.

CREATE PROCEDURE sp_ShrinkLog_ByDataBaseName(
@DataBaseName VARCHAR(200)
)
AS
BEGIN

DECLARE @DBName VARCHAR(200)
DECLARE @LogFileName VARCHAR(200)
SET @DBName = @DataBaseName

SELECT @LogFileName = [Logical File Name]
FROM
(
SELECT
    DB_NAME(database_id)  AS "Database Name", 
type_desc             AS "File Type", 
name                  AS "Logical File Name", 
physical_name         AS "Physical File", 
state_desc            AS "State"
FROM
    SYS.MASTER_FILES
WHERE
    database_id = DB_ID(@DBName)
    and type_desc = 'LOG'
) AS SystemInfo
SELECT LogFileName = @LogFileName

EXECUTE(
'USE ' + @DBName + ';

-- Truncate the log by changing the database recovery model to SIMPLE.
ALTER DATABASE ' + @DBName + '
SET RECOVERY SIMPLE;

-- Shrink the truncated log file to 1 MB.
DBCC SHRINKFILE (' + @LogFileName + ', 1);

-- Reset the database recovery model.
ALTER DATABASE ' + @DBName + '
SET RECOVERY FULL;

')
END

Затем выполните его EXEC sp_ShrinkLog_ByDataBaseName 'Yor_DB_NAME'.

Если это не работает для вас, вот другое решение, которое, я думаю, будет работать.