Lock conflict on no wait transaction deadlock

This is actually not an error, but a normal exception caused by transaction management. You’ll get this message when one transaction tries to update or delete a record that was changed by another transaction after the current transaction has started. It only happens when the current transaction is a NO WAIT transaction, so it doesn’t wait for other transaction to finish, but rather reports the error and gives up immediately.

Please note that this is a normal event with NO WAIT transactions and your application should be prepared for it and able to deal with it. This is needed in order to maintain database consistency. Imagine for example that you have a table with record:

ID VALUE
— ——
10 15000

Now, we have two transactions starting at approximately the same time, and both run:

UPDATE t1 SET value = value + 20 WHERE >
You would expect the column to have value of 15040 after they both commit. But, let’s take a look what happens in a real database. Transaction one reads value 15000 from the column, adds 20 and puts 15020 — it does not commit yet, but goes on to do other things. Transaction two starts, reads 15000 from the column (as transaction one did not commit yet), adds 20 and puts 15020. If there was no error raised, you would end up with value 15020 instead of 15040 in the column. However, the exception will be raised. If it’s a NO WAIT transaction, the message would should up at once. On the other hand, WAIT transaction would wait for other transaction to finish before going further (if the first one rolls back) or throwing an error (if the first one commits).

When you get such message, it often helps to change transactions from NO WAIT to WAIT, although you should investigate the issue closely as bad transaction management can lead to poor performance.

Teoría y Práctica sobre Firebird

Lock conflict on no wait transaction. Deadlock.

Si al querer hacerle un UPDATE o un DELETE a una tabla recibes este mensaje de error ¿qué significa?

Que la transacción T1 hizo un UPDATE o un DELETE y antes de que la transacción T1 terminara (con un COMMIT o con un ROLLBACK) la transacción T2 también intentó hacerle un UPDATE o un DELETE a la misma tabla. Eso provocó un conflicto. Y como el modo de bloqueo de la transacción T2 es NO WAIT entonces cuando ocurre un conflicto inmediatamente recibe el mensaje de error correspondiente.

Captura 1. Si haces clic en la imagen la verás más grande

¿Y cuál es la solución?

Eso depende de las circunstancias.

  • Si la operación que quiso hacer la transacción T2 (es decir, el UPDATE o el DELETE) puede esperar entonces se termina la transacción T2 (con un ROLLBACK) y más tarde se inicia una transacción T3 para que realice la operación fallida.
  • Si la operación que quiso hacer la transacción T2 no puede esperar (quizás porque la transacción T1 jamás podría finalizar normalmente con un COMMIT o con un ROLLBACK debido a que dicha transacción quedó “colgada” porque un usuario presionó las teclas CTRL+ALT+DEL o algo similar) entonces hay que eliminar a la transacción problemática. Este es el caso más complicado.

Eliminando la transacción que causa el bloqueo

En la Captura 1. podemos ver que hay un conflicto y la causa de ese conflicto es que la transacción 133608 está bloqueando los UPDATE y los DELETE a las filas de una tabla. El programa que había iniciado a la transacción 133608 fue cerrado abruptamente por el “Administrador de tareas” del Windows y es por lo tanto imposible que alguna vez la transacción 133608 finalice con un COMMIT o con un ROLLBACK, como debería ser.

Entonces debemos eliminar a la transacción problemática (la número 133608, en este ejemplo) de forma manual. Eso lo conseguiremos con el siguiente comando:

La tabla MON$TRANSACTIONS es una tabla de monitoreo, interna del Firebird, o sea que siempre existe en todas las Bases de Datos. En esa tabla se guardan los datos de cada transacción. Si se elimina una fila de esa tabla entonces la transacción correspondiente deja de existir.

Por supuesto, no te olvides de terminar tu transacción eliminadora con un COMMIT.

¿Pero y si falla el DELETE?

Hay ocasiones en las que el DELETE a una fila de la tabla MON$TRANSACTIONS te fallará. ¿Qué puedes hacer entonces? Finalizar a la transacción T2 con un ROLLBACK. Si eso no es suficiente, salir de todos los programas que usan a esa Base de Datos. Y si sigue sin ser suficiente entonces detener el Servidor del Firebird y luego reiniciarlo (algo que no siempre es posible porque los demás usuarios se quejarán hasta en chino).

Pero … cuidado, porque esto no termina aquí. La transacción problemática (la 133608 en este ejemplo) dejó basura dentro de la Base de Datos y esa basura deberás eliminarla alguna vez (haciendo un sweep). No hagas el sweep cuando mucha gente está usando la Base de Datos porque el sweep siempre causa que todas las operaciones se vuelvan muy lentas.

Кузьменко Дмитрий, www.ibase.ru, 11.10.2000, обновление – 29.07.2003, 11.11.2003, 17.11.2004, 17.03.2005, 10.03.2008, 10.04.2011, 16.05.2011, 01.10.2014, 02.12.206.

Отречемся от старого мира

Работа с InterBase у большинства программистов, как и у меня, началась с BDE. Т. е. сначала было освоение Delphi, а затем IB. Разумеется, самым естественным способом работы с IB из Delphi было использование BDE. Однако BDE, если вспомнить историю его создания, ориентирован на максимальное упрощение работы с SQL-серверами для тех программистов, кто раньше работал только с настольными базами данных (dBase, FoxPro, Access и т. п.). Универсальность BDE многие годы не давала возможности воспользоваться всем потенциалом IB (как собственно, и потенциалом других SQL-серверов. Мало того, что BDE скрывает массу особенностей работы с SQL-серверами от разработчиков (см. статью "Архитектура BDE"), но и не дает воспользоваться богатством средств управления транзакциями.

BDE поддерживает только одну транзакцию на соединение с сервером (TDatabase). На самом деле в Interbase или Firebird может быть сколько угодно транзакций в одном соединении, и выбор их параметров намного больше, чем предлагает BDE. Если вспомнить, то BDE обеспечивает всего два типа транзакций – Read Committed и Repeatable Read, и два варианта завершения транзакций для каждого типа – Commit или Commit Retaining (с сохранением запросов открытыми в последнем случае).

Если посмотреть на свойство TDatabase.TransIsolation, то в вариантах выбора есть еще один уровень изоляции – tiDirtyRead. Но в IB/FB такой уровень не поддерживается, и BDE его автоматически заменяет на tiReadCommitted.

В 1997 году Borland выпустил Delphi 3, в которой были произведены значительные изменения в иерархии компонент для работы с базами данных. Основным новшеством было введение полуабстрактного класса TDataSet и выделение функций работы с BDE в специальный класс TBDEDataSet. Большинство разработчиков так и не заметило, что произошло, поскольку иерархия наследования не нарушилась, и компоненты TTable и TQuery остались почти теми же. Однако внутренние изменения были весьма существенными, и позволяли строить собственных наследников от TDataSet для работы с произвольными API и форматами данных. Так, например, среди примеров Delphi 3 (и поставляется до сих пор) есть TextData, способный читать данные из текстового файла. Поскольку TTextDataSet является наследником TDataSet, то его можно подключать к TDataSource и работать с ним как с TTable или TQuery. Однако даже для чтения текстовых файлов у TDataSet необходимо переопределить около 15-ти методов, прежде чем все это заработает. Подобная сложность привела к тому, что написание собственных TDataSet до сих пор является уделом избранных.

  • Статья по компонентам IBX (InterBase eXpress)
  • Список компонент и драйверов для Firebird и InterBase

Свой API всегда ближе к телу

Представьте себе, что когда-то не было BDE. При этом писать приложения, работающие с SQL-сервером, приходилось используя клиентский API этого сервера. А поскольку стандарта на этот API не существовало, то производители что хотели, то и вытворяли. BDE, собственно, удалось объединить общие свойства разных API в один унифицированный интерфейс. Для каждого SQL-сервера был свой, так называемый, SQL Link. Фактически это обертка над функциями конкретного клиентского API. Инструментарий для создания подобных оберток у Borland был, но он был настолько засекречен, что о нем почти никто не знал. Только Borlland мог выпускать SQL Links, и этой чести удостаивались только самые популярные SQL-серверы – DB2, Informix, Sybase, MS SQL, Oracle и разумеется, Interbase. До сих пор, кстати, существует наивное мнение, что Delphi (в смысле BDE) очень сильно "заточена" на работу с IB. Ничего подобного, и вы сами можете в этом убедиться, если внимательно рассмотрите архитектуру BDE. Все SQL Links равноправны между собой. Возможно, более высокое качество IB SQL Link обусловлено как качественным IB API так и тем, что команда разработчиков IB работала внутри Borland. Однако даже столь тесная близость не избавляла от багов – то поменяли умолчательный уровень изоляции в BDE 3.0, то не могли выставить флаг транзакции rec_version до версии BDE 4.01, то . В общем, IB SQL Link доставалось не меньше, чем остальным.

И вот, где-то в начале 1998 года, некий Gregory Deatz из адвокатской фирмы Hoagland, Longo, Moran, Dunst & Doukas, выпустил в свет набор компонент, называющийся FreeIBComponents. Это всего 4 компонента для Delphi, работающих напрямую с IB API, и совместимых по наследованию с TDataSet. Раз уж мы говорим о транзакциях, то основным компонентом для нас будет FIBTransaction. Впоследствии на базе исходных текстов FIBC был создан набор компонент IB Express (IBX), который ныне поставляется в Delphi 5 и C++Builder 4. Также года с 1998 существует набор компонент IBObjects, который также работает с IB API. Сути дела это не меняет – программисты получили возможность работать напрямую с IB, и полностью контролировать параметры транзакций (и не только, но это тема для отдельной статьи).

Итак, чего же в этих транзакциях такого, что надо было бы знать? Или если еще проще – почему у программистов, пользовавшихся BDE, при переходе на fibc/IBX/IBO возникают какие то проблемы с транзакциями?

Где они живут

Транзакции живут на сервере, а стартует их всегда только клиентское приложение. В триггерах и процедурах стартовать и завершать транзакции невозможно (начиная с Firebird 2.5 в процедурах и триггерах можно использовать автономные транзакции). Да и ни к чему это. Представьте себе, что в одной процедуре у вас в случае ошибки вызывается rollback. Пишется приложение, которое стартует транзакцию, в которой должна быть выполнена эта процедура и еще другая. Вообще считается что содержимое транзакции и реакция на ошибки определяется тем, кто эту транзакцию начал. Так вот, при rollback в первой процедуре – в какой транзакции будет выполняться вторая?

Собственно, IB/FB обладает возможностью в одном коннекте стартовать много транзакций, что очень удобно (потом трудно будет привыкнуть к отсутствию данной возможности в других серверах). Приложение должно стартовать транзакцию, выполнить набор операторов, составляющих эту транзакцию, и завершить транзакцию. Причем еще раз подчеркну, что именно клиентское приложение решает, допустимы ли ошибки при выполнении операторов в транзакции, и как завершать транзакцию с учетом наличия или отсутствия ошибок. Например, если в транзакции всегда выполняется только один оператор, то ее можно завершать по commit, потому что если оператор не выполнился по ошибке, то все сделанные им изменения все равно будут автоматически отменены.

Словарик

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

  • транзакция – набор логически связанных операций, работающих с данными базы данных, и либо переводящий базу данных в из одного целостного состояния в другое, либо нет (т. е. оставляющий БД в целостном состоянии, существовавшем до начала транзакции).
  • уровень изолированности (или уровень изоляции) – как транзакция взаимодействует с другими, конкурирующими транзакциями.
  • commit, committed – завершение транзакции с применением изменений. Ничего лучше чем "подтверждение" или "подтвержденные" в качестве перевода этого термина я не нашел. В литературе также используется перевод "фиксация" и "зафиксированные".
  • rollback, rolled back – завершение транзакции с отменой всех изменений, которые были произведены в ее контексте. Русский перевод – отмена, отменены.

Существует 4 стандартных уровня (ANSI SQL-92) изолированности транзакций:

  • Dirty Read – "грязное" (или "незафиксирование") чтение. Транзакция может читать не подтверджденные изменения, сделанные в других транзакциях. В IB этот режим не поддерживается. Например, если транзакции A и B стартовали, и поменяли записи, то они обе видят изменения друг друга.
  • Read Committed – невоспроизводимое (или неповторяемое) чтение. Транзакция может читать только те изменения, которые были подтверждены другими транзакциями. Например, если транзакции A и B стартовали и поменяли записи, то они не видят изменения друг друга. Транзакция А увидит изменения транзакции B только тогда, когда транзакция B завершится по commit. Перечитывание данных в транзакции может выдавать разные результаты.
  • Repeatable Read – воспроизводимое (или повторяемое) чтение. Транзакция видит только те данные, которые существовали на момент ее старта.
  • Serialized – сериализуемость. Транзакция выполняются так, как будто никаких других транзакций в этот момент не существует. Или, транзакции выполняются так, как будто они выполняются последовательно. Не поддерживается явно в IB, но может быть сэмулировано.

Эти 4 уровня были формализованы на основе некоего мифического "планировщика блокировок", который если присмотреться к определению уровней изолированности в стандарте, был более чем реален (использовалось поведение некоторых существовавших на тот момент SQL-серверов). В результате стандартный Repeatable Read по определению допускает появление "фантомов", т. е. записей, которые не должны быть видны транзакции данного уровня, но тем не менее они видны. На эту тему существует очень интересная статья "Критика уровней изолированности в стандарте ANSI SQL", где подробно расписаны все 4 уровня, а также вводится понятие уровней Cursor Stability (устойчивость курсора) и Snapshot Isolation (изолированность образа или "снимок". В InterBase/Firebird это и есть SNAPSHOT).

Если приводить в соответствие стандартные уровни изолированности и транзакции IB/FB, то окажется что совпадает только один – Read Committed. Dirty Read и Serialized отсутствуют, а SNAPSHOT близок к стандартному Repeatable Read, хотя и "сильнее" его (см. статью "Критика. ".).

Соответствие типов транзакций BDE и транзакций IB указано в статье "Архитектура BDE".

Оцените статью
Ремонт оргтехники
Добавить комментарий