Как заставить клиентское приложение Firebird ждать разблокировки строки

Мое знакомство с миром Microsoft SQL Server с использованием ADO (dbGo), и я написал много приложений для этой среды. Теперь у меня есть устаревшее приложение Delphi 7 с базой данных Firebird 2.5, которую я должен поддерживать.

НО я обнаружил, что если 2 клиентских приложения выполняют это:

SQLQuery.SQL.Text := 'Update mytable set field1 = 11 where keyfield = 99'
SQLQuery.Execute;

примерно в то же время, второе приложение немедленно получает ошибку "тупика". В SQL Server будет период ожидания

ADOConnection.Isolationlevel = ilCursorstability;
ADOConnection.CommandTimeout := 5;

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

Теперь мои попытки использовать ту же методологию у клиента Firebird оказались бесплодными, потому что "тупик" (фактически, используемая запись) происходит немедленно.

Если движок базы данных не может быть настроен на то, чтобы немного ждать улучшения условий (блокировки записи должны быть выпущены), ответственность теперь должна быть оставлена ​​разработчиком клиентского приложения, который должен писать безумно медленный код, чтобы преодолеть то, что мне кажется быть серьезным провалом Firebird.

Как только обнаружен "тупик", условие не очищается, кроме как отсоединив компонент соединения

while rowsupdated = 0 and counter < 5 do
begin
  try
    rowsupdated := SQLQuery.Execute;
  except
    SQLConnection.Connected := False;
    SQLConnection.Connected := True;
  end;
  Inc(Counter)
end;

Как вы создаете надежные многопользовательские клиенты для обновления таблиц, если у вас нет существенного блокирующего допуска в Firebird, используя DBX в Delphi?

Ответы

Ответ 1

Клиент может указать, должна ли транзакция ждать разрешения взаимоблокировки. Если в вашем случае тупик происходит немедленно, это возможно из-за вашей конфигурации (с использованием параметра транзакции nowait на клиенте). Не использование nowait приведет к тому, что серверная сторона обнаружит тупик и (после настраиваемого таймаута) вызовет на клиенте исключение.

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

Ответ 2

Транзакция Firebird может быть сконфигурирована как nowait или wait (с или без определенного таймаута). Как это можно настроить, зависит от драйвера, и поскольку я не знаком с Delphi, я не могу прокомментировать это. Nowait обычно является значением по умолчанию, так как в большинстве случаев ожидание будет только задерживать неизбежное.

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

Ответ 3

Я использую кнопку "ответ на свой вопрос". Я нашел решение.

  • Установите драйвер ODBC с открытым исходным кодом IBPhoenix для Firebird/Interbase
  • Настройте DSN ODBC для подключения к Firebird.fdb с отключенным nowait, а LockTimeout устанавливается как требуется в секундах. Я выбрал 15 секунд.
  • Используйте Delphi 7 ADO (dbGo) TADOConnection, настроенный для использования Microsoft OLE DB Provider для драйверов ODBC.
  • Это важный бит: установите для ADOConnection.TransactionIsolation либо ilReadUncommited, либо ilDirtyRead.

То, что это делает, заставляет TADOQuery.ExecSQL фактически ждать (до 15 секунд), если обнаружит, что запись уже была обновлена ​​в транзакции, которая еще не совершена или откат!

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

Итак, если оба запроса делают это

Update MYTABLE set NUM = NUM + 1 where keyvalue = 99;

а начальное значение (до любого обновления) равно 0, значение NUM после совершения обеих транзакций равно 2. Как и ожидалось.

Снова снова с NUM = 0. Если первый откат транзакции транзакции, вторая транзакция может совершить (или откат). И значение после того, как второе обновление зафиксировано, равно 1.

Я не знаю, как и почему это работает так хорошо, особенно, поскольку Firebird не должен поддерживать ReadUnComited или DirtyRead, но я просто счастлив, что он работает так, как я хочу.