Транзакции в InterBase и Firebird. Кузьменко Дмитрий, www.
- Словарик по FireBird Как установить на LINUX второй экземпляр Firebird SQL004. Можно ли в Firebird делать запросы к нескольким базам данных.
- Встроенная функция. Позволяет извлекать дату и время. Извлекаемая часть должна присутствовать в значении. Например, не стоит извлекать год из.
- Функция, Описание. CURRENT_DATE, Контекстная переменная возвращает текущую дату. CURRENT_TIME, Контекстная переменная.
Типы данных SQL-сервера Firebird используются при создании доменов, таблиц, просмотров, хранимых процедур, объявления контекстных. Внимание! Почти с каждым новым релизом сервера Firebird в оператор SELECT добавлялась новая возможность или параметр. В связи с этим.
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. Отречемся от старого мира.
Работа с InterBase у большинства программистов, как и у меня, началась с BDE. Т. сначала было освоение Delphi, а затем IB. Разумеется, самым естественным способом работы с IB из Delphi было использование BDE. Однако BDE, если вспомнить историю его создания, ориентирован на максимальное упрощение работы с SQL-серверами для тех программистов, кто раньше работал только с настольными базами данных (dBase, FoxPro, Access и т.
Универсальность BDE многие годы не давала возможности воспользоваться всем потенциалом IB (как собственно, и потенциалом других SQL-серверов.
Мало того, что BDE скрывает массу особенностей работы с SQL-серверами от разработчиков (см. статью "Архитектура BDE "), но и не дает воспользоваться богатством средств управления транзакциями.
BDE поддерживает только одну транзакцию на соединение с сервером (Database). На самом деле в Interbase или Firebird может быть сколько угодно транзакций в одном соединении, и выбор их параметров намного больше, чем предлагает BDE. Если вспомнить.
то BDE обеспечивает всего два типа транзакций - Read Committed и Repeatable Read, и два варианта завершения транзакций для каждого типа - Commit или Commit Retaining (с сохранением запросов открытыми в последнем случае). Если посмотреть на свойство TDatabase.
TransIsolation, то в вариантах выбора есть еще один уровень изоляции - tiDirtyRead. Но в IB такой уровень не поддерживается, и BDE его автоматически заменяет на tiReadCommitted. В 1997 году Borland выпустил Delphi 3, в которой были произведены значительные изменения в иерархии компонент для работы с базами данных. Основным новшеством было введение полуабстрактного класса TDataSet и выделение функций работы с BDE в специальный класс TBDEDataSet. Большинство разработчиков так и не заметило, что произошло, поскольку иерархия наследования не нарушилась, и компоненты TTable и TQuery остались почти теми же. Однако внутренние изменения были весьма существенными, и позволяли строить собственных наследников от TDataSet для работы с произвольными API и форматами данных.
Так, например, среди примеров Delphi 3 (и поставляется до сих пор) есть TextData, способный читать данные из текстового файла. Поскольку TTextDataSet является наследником TDataSet, то его можно подключать к TDataSource и работать с ним как с TTable или TQuery. Однако даже для чтения текстовых файлов у TDataSet необходимо переопределить около 15-ти методов, прежде чем все это заработает. Подобная сложность привела к тому, что написание собственных TDataSet до сих пор является уделом избранных. Свой 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 возникают какие то проблемы с транзакциями. Транзакции живут на сервере, а стартует их всегда только клиентское приложение. В триггерах и процедурах стартовать и завершать транзакции невозможно.
Да и ни к чему это. Представьте себе, что в одной процедуре у вас в случае ошибки вызывается rollback. Пишется приложение, которое стартует транзакцию, в которой должна быть выполнена эта процедура и еще другая. Вообще считается что содержимое транзакции и реакция на ошибки определяется тем, кто эту транзакцию начал. Так вот, при rollback в первой процедуре - в какой транзакции будет выполняться вторая. Собственно, IB обладает возможностью в одном коннекте стартовать много транзакций, что очень удобно (потом трудно будет привыкнуть к отсутствию данной возможности в других серверах).
Приложение должно стартовать транзакцию, выполнить набор операторов, составляющих эту транзакцию, и завершить транзакцию. Причем еще раз подчеркну, что именно клиентское приложение решает, допустимы ли ошибки при выполнении операторов в транзакции, и как завершать транзакцию с учетом наличия или отсутствия ошибок. Например, если в транзакции всегда выполняется только один оператор, то ее можно завершать по 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, то окажется что совпадает только один - Read Committed. Dirty Read и Serialized отсутствуют, а SNAPSHOT близок к стандартному Repeatable Read, хотя и "сильнее" его (см.
статью "Критика. ". Соответствие типов транзакций BDE и транзакций IB указано в статье "Архитектура BDE ". О транзакциях - подробно. Совсем подробно о транзакциях InterBase написано в документации. В Programmers Guide и API Guide (Руководство программиста и Руководство по API соответственно в переводном варианте).
Programmers Guide на самом деле достаточно вредная книга, т. она описывает в основном синтаксис Embedded SQL, который обрабатывается только препроцессором GPRE, и который невозможно использовать при прямой работе с API или из Delphi и C++Builder.
С другой стороны, описание транзакций в этих двух книгах хоть и одинаково, но в Programmers Guide более понятно. Поэтому для начала нужно свести термины Programmers Guide и API Guide воедино. примечание. Programmers Guide в документации InterBase 6 и последующих версий назван Embedded SQL Guide специально для исключения упомянутой путаницы.
Поскольку самая настольная книга - Language Reference, то приведу оттуда полное описание синтаксиса управления транзакциями:. (позволю себе вырезать часть синтаксиса, которая используется только препроцессором GPRE.
Желающие могут сравнить с оригиналом). Жирным шрифтом выделены параметры по умолчанию. Для read committed умолчательным является режим no record_version. Как вы поняли, это синтаксис Embedded SQL. Т.
"запихнуть" такой оператор в компонент Query не получится. Однако объяснять такой синтаксис проще, чем набор сухих констант из API Guide. Поэтому объяснение синтаксиса пойдет параллельно с константами. примечание. константы параметров транзакций IB API имеют префикс isc_tpb_.
ISC - это Interbase Software Corporation, а tpb - transaction parameter buffer. Компоненты FIBC и IBX позволяют не указывать префикс в IBTransaction. Params. Таким образом, вместо например isc_tpb_wait можно написать просто wait. В таком виде константы параметров транзакций и будут упоминаться далее.
примечание. "по умолчанию" означает, что именно такое значение используется, если данный параметр не указывать совсем. READ WRITE / READ ONLY (константы write и read) - операторы внутри транзакции могут или не могут модифицировать данные. По умолчанию READ WRITE, т. допускается и чтение и запись. Для readonly баз данных IB 6 транзакции могут стартовать как read или write, но любые операции изменения данных будут вызывать сообщение об ошибке.
в Firebird, Yaffil и InterBase 6. 5 транзакции read_committed read (read only) стартуют сразу в состоянии committed, поэтому не удерживают мусорные записи. Т. такая транзакция может длиться часами без ущерба для производительности сервера.
Наиболее характерный пример использования - работа со справочниками. WAIT / NO WAIT (константы wait и nowait) - Режимы обработки конфликтов блокировок.
Если транзакция стартует в режиме WAIT (по умолчанию), и при выполнении операции (как правило, изменения данных, за исключением режима no_rec_version) обнаруживается конфликт, то операция "замораживается" до разрешения конфликта. В режиме NO WAIT сообщение о конфликте выдается приложению немедленно (возникает ошибка), а операция, которая привела к конфликту, отменяется.
В случае взаимоблокировки двух wait-транзакций сервер автоматически обнаруживает эту ситуацию, и разблокирует одну из транзакций (как будто она стартовала как nowait) через интервал времени, определенный в IBCONFIG параметром DEADLOCK_TIMEOUT, который по умолчанию равен 10 секундам. примечание. в Yaffil введен дополнительный параметр LOCK_TIMEOUT, который относится ко всем транзакциям режима WAIT - если такая транзакция попадает на конфликт обновления, то через LOCK_TIMEOUT секунд она будет переведена (однократно) в режим NOWAIT и получит сообщение о конфликте. Используется в случаях, когда используются WAIT-транзакции, и "долгоживущие" транзакции, которые своими обновлениями могут надолго заблокировать wait-транзакции (классическая ситуация - "оператор вышел покурить или пообедать").
В Firebird 2. 0 для WAIT добавлен дополнительный параметр LOCK TIMEOUT seconds (константа isc_tpb_lock_timeout = 21, для IBX ее придется прописать в исходниках вручную - добавить к константам isc_tpb). Этот параметр позволяет транзакции WAIT через интервал seconds выдавать ошибку в случае обнаружения блокировки.
SNAPSHOT (константа concurrency) - уровень изоляции, эквивалентный Repeatable Read. На самом деле этот уровень изоляции ближе к "изолированность образа", т. не допускает фантомов. Все операции в транзакции с данным уровнем изоляции видят только те данные, которые существовали (committed) на момент старта этой транзакции (даже если они впоследствии были изменены или удалены другими транзакциями).
По умолчанию в IB/FB API. SNAPSHOT TABLE STABILITY (константа consistency) - изолированность образа (воспроизводимое чтение), при обращении к таблицам блокирует к ним доступ (как минимум на изменения, целиком для всей таблицы, см. дальше резервирование таблиц).
READ COMMITTED (константа read_committed) - уровень изоляции ReadCommitted. Т. в данной транзакции все изменения, которые были подтверждены другими транзакциями, будут видны немедленно. Имеет две опции:. NO RECORD_VERSION (константа no_rec_version) - если при чтении пакета версий записи (о версионности "в двух словах см.
www. ibase. ru/devinfo/mga. htm ) обнаруживается non-committed версия, то выдается или deadlock (в режиме no wait) или транзакция зависает на блокировке (в режиме wait).
По умолчанию для режима READ COMMITTED в IB API. RECORD_VERSION (константа REC_VERSION) - игнорирует non-committed версии, читая последнюю committed-версию (см. выше no_rec_version). Именно этот режим является умолчательным в BDE (начиная с версии BDE 4.
01. см.
документ ), и рекомендуется для нормальной работы в режиме read committed. примечание. как выяснилось (в 2010) году, многие разработчики драйверов (Firebird ODBC, Firebird. Net driver (DNET-337 ) и т.
) почему-то считают, что для read_committed нормальным является режим no record_version, вызывающий блокировки по чтению non-committed данных.
Это является неестественным, т. InterBase и Firebird ни при каком уровне изолированности не допускают чтения non-committed данных. Более того, версионность в первую очередь предназначена для того, чтобы обеспечивать бесконфликтное чтение подтвержденных (committed) данных.
В любом случае, если вы видите deadlock при чтении, значит ваш драйвер или компоненты используют именно no record_version для read committed. Если возможно (позволяет драйвер или компоненты), попытайтесь это исправить настройками параметров транзакции.
В большинстве компонентов прямого доступа (IBX, FIBPlus и т. ) умолчательным для режима ReadCommitted как раз является record_version, не выдающий блокировок при чтении non-committed данных. RESERVING - указывает необходимость блокирования таблиц, shared или protected, read или write (см.
далее раздел "резервирование таблиц". Итак, если вы в уме подсчитывали константы, которые были названы как умолчательные, то у вас должно получиться. или, в виде параметров IBTransaction:. Такую транзакцию стартуют WISQL, ISQL и другие инструменты, у которых не задаются параметры транзакций, а также IBX (InterBase Express components) (FIBPlus по умолчанию стартует read_committed). То же самое относится и к IB API, т. когда транзакция стартует без параметров.
В буквальном смысле это означает, что если вы не указали никаких параметров в IBTransaction. Params, то транзакция стартует в режиме RepeatableRead, да еще и будет "зависать" при конфликтах обновления или удаления записей. Наиболее употребительным режимом является. что в виде констант для IBTransaction. Params выглядит как. именно такой уровень изоляции будет видеть изменения, производимые другими транзакциями (но не раньше их завершения по commit), и минимально конфликтовать при обновлении или удалении данных. Если продолжать о совместимости с BDE, то эквивалентом его RepeatableRead будет следующее содержимое IBTransaction.
Params:. или вообще просто nowait. В о всех последних версиях InterBase (6. 0 и выше), Firebird и Yaffil, очень удобно для работы со справочниками стартовать транзакцию. такая транзакция стартует в состоянии committed, поэтому не оказывает никакого влияния на sweep, версии и т.
и поэтому может "жить" очень долго (сутками, месяцами. ). Отсутствие параметра nowait в данном случае обусловлено тем, что читающая транзакция с rec_version не может в принципе попасть ни на какой конфликт обновления записей. Поэтому wait/nowait в данном случае значения не имеет. Физическое отличие READ_COMMITTED от SNAPSHOT. Если не влезать в дебри исходных текстов, то все достаточно просто.
Транзакции имеют 4 состояния - active, committed, rolled back, in limbo (подробнее см. документ ). Это состояние (2 бита) для всех существующих или завершенных транзакций (начиная с Oldest transaction) хранится на так называемых Transaction Inventory Pages (TIP) в базе данных. При старте транзакций read_committed все они определяют "видимость" версий записей обращаясь к "глобальному" TIP - если версия записи committed, то ее можно читать. Если нет - нельзя.
При старте snapshot для транзакции делается "снимок" TIP. Т.
он копируется локально для этой транзакции, и таким образом состояния транзакций "замораживаются" на момент старта этого snapshot. Именно поэтому snapshot "не видит" никаких, даже committed изменений, сделанных в БД после своего старта. Понятно, что чем больше стартует транзакций в режиме snapshot, тем больше делается "локальных копий" TIP. С одной стороны, даже 200 тысяч транзакций займут примерно 50 килобайт (в одном байте помещается четыре двухбитовых "состояния" транзакции), то это не так страшно для современных серверов. Однако, на практике встречаются базы данных с гораздо большим числом транзакций в TIP (по разным причинам, в основном просто потому что не делается backup или не запускается sweep). В результате количество памяти, отводимое под локальный TIP для snapshot может оказаться большим.
Кроме того, совершенно естественно, что время старта snapshot будет все дольше и дольше, чем больше состояний транзакций хранится в TIP (затраты времени на копирование страниц TIP в локальную копию TIP). Еще раз подчеркну, что размер TIP в байтах определяется как разница между Oldest transaction и Next transaction (gstat -h db. gdb), деленная на четыре. Резервирование таблиц. Этот режим весьма интересен. Давайте рассмотрим взаимодействие транзакций, резервирующих таблицы. Для этого я сделал простенькое приложение, которое открывает одну-единственную таблицу (любую в вашей базе данных).
Вверху грид, в котором можно редактировать данные (для простоты использован компонент IBTable, автоматически формирующий запросы), ниже - навигатор, кнопки старта и завершения транзакции, и окно для модификации параметров транзакций. Если стартовать транзакции consistency (т. SNAPSHOT TABLE STABILITY) то она:. безусловно стартует. перед чтением из таблицы пытается поставить на нее блокировку protected-read. перед записью в таблицу пытается поставить на нее блокировку protected-write.
Основное отличие транзакций этого типа от транзакций concurrency (SNAPSHOT) в том, что транзакции concurrency ставят на таблицы блокировки shared, а не protected. Shared - разделяемые, protected - защищенные. Вот таблица совместимости различных блокировок:.