SqlTransaction завершена - PullRequest
       15

SqlTransaction завершена

24 голосов
/ 04 марта 2009

У меня есть приложение, которое потенциально делает тысячи вставок в базу данных 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, указывающую, что ошибка в .Net 2.0 framework может вызвать аналогичные проблемы (http://support.microsoft.com/kb/912732). Однако предоставленное исправление не решает эту проблему.

Ответы [ 9 ]

8 голосов
/ 06 марта 2009

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

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

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

7 голосов
/ 04 марта 2009

Трудно помочь, не видя код. Из вашего описания я предполагаю, что вы используете транзакцию для фиксации после каждой вставки 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

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

6 голосов
/ 05 марта 2009

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

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

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

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

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

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

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

3 голосов
/ 29 октября 2010

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

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

1 голос
/ 24 ноября 2011

Согласно этому посту: 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

1 голос
/ 27 июня 2011

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

1 голос
/ 05 марта 2009

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

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

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

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

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

0 голосов
/ 10 апреля 2013

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

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'.

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

0 голосов
/ 05 марта 2009

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

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

...