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

Введение транзакций в ваше приложение предоставляет защиту вне буферирования записи и таблицы Visual FoxPro, путем размещения целой секции кода в защищенную и восстановливаемую единицу, действующую как единое целое. Вы можете вкладывать транзакции и использовать их для защиты буферированных обновлений. Транзакции Visual FoxPro доступно только для таблиц и представлений, содержащихся в контейнере базы данных.

Заключение сегментов кода в оболочки

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

NoteЗамечание

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

Команды, которые управляют транзакциями

Visual FoxPro предоставляет три команды и одну функцию для управления транзакциями.

 
Для ... используйте ...

начала транзакции

BEGIN TRANSACTION

определения текущего уровня транзакции

TXNLEVEL( )

сброса всех изменений, произведенных после самого последнего предложения BEGIN TRANSACTION

ROLLBACK

блокировки записей, записи на диск всех изменений в таблицах в базе данных начиная с самой последней BEGIN TRANSACTION, а затем разблокировки записей

END TRANSACTION

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

NoteЗамечание

При использовании данных, хранящихся в удаленных таблицах, команды транзакий управляют только обновлениями данных в локальной копии курсора представления; обновления в удаленной базе данных не подвергаются их воздействию. Для того, чтобы разрешить ручные транзакции на удаленных таблицах используйте SQLSETPROP( ), а затем управляйте транзакцией с помощью функций SQLCOMMIT( ) и SQLROLLBACK( ).

В общем случае, вы должны использовать транзакции скорее с буферами записей, чем с буферированием таблиц, за исключением обертки вызовов TABLEUPDATE(). Если вы помещаете в транзакцию команду TABLEUPDATE( ), вы можете откатить неудавшееся обновление, исследовать причину сбоя, а затем попытаться вызвать TABLEUPDATE() без потери данных. Это гарантирует обновление, случившееся в виде операции "все-или-ничего" ("all-or-nothing").

Хотя обработка простой транзакции обеспечивает безопасность операций обновления в нормальных ситуациях, она не обеспечивает глобальной защиты от системных сбоев. Если во время исполнения команды END TRANSACTION пропадет питание или случится иная неисправность системы, обновление данных может не состояться.

Используйте для транзакций приведенный шиблон кода:

 CopyCode imageСкопировать код
BEGIN TRANSACTION   
* Обновление записей
IF lSuccess = .F. && произошла ошибка
   ROLLBACK
ELSE && записывает изменения
   * проверяет данные
   IF && произошла ошибка
      ROLLBACK
   ELSE 
      END TRANSACTION
   ENDIF
ENDIF

К транзакциям применяются перечисленные ниже правила:

  • Транзакция начинается командой BEGIN TRANSACTION и заканчивается командой END TRANSACTION или ROLLBACK. Предложение END TRANSACTION без предшествовавшего ему BEGIN TRANSACTION сгенерирует ошибку.

  • Предложение ROLLBACK без предшествовавшего ему BEGIN TRANSACTION сгенерирует ошибку.

  • Начатая транзакция, остается в действии до тех пока, пока не начнется исполнение END TRANSACTION (или не будет выдана команда ROLLBACK ), даже с учетом исполнения программ или вызова функций, если не произошло прерывание исполнения приложения, которое вызовет откат.

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

  • Если исполнение приложения прерывается во время транзакции, все операции откатываются.

  • Транзакции работают только на контейнере базы данных.

  • Вы не можете использовать команду INDEX, если она переписывает существующий индексный файл, или в случае, если открыт любой файл индекса (.cdx).

  • Транзакции принадлежат сессиям данных.

Транзакции показывают приведенное ниже поведение блокировки:

  • Внутри транзакции, Visual FoxPro накладывает блокировку во время прямых или косвенных вызовов команды. Любые системные команды или прямые или косвенные команды разблокировки кэшируются до момента завершения транзакции командами ROLLBACK или END TRANSACTION.

  • Если вы внутри транзакции используете команды блокировки, такие как FLOCK( ) или RLOCK( ), предложение END TRANSACTION не снимет установленную блокировку. А таком случае, вы должны явно разблокировать любые наложенные во время транзакции блокировки. Кроме того, вы должны обеспечить минимальное время, насколько это возможно, действия транзакции, содержащей команды FLOCK( ) или RLOCK( ); в противном случае, пользователь может быть заблокирован для доступа к данным на длительное время.

Вложение транзакций

Вложенные транзакции предоставляют логические группы операций обновления таблиц, которые изолированы от конкурентных процессов. Пары BEGIN TRANSACTION...END TRANSACTION не обязательно должны быть в одной и той же функции или процедуре. Приведенные ниже правила применяются ко вложенным транзакциям:

  • Вы можете иметь уровень вложенности до пяти пар BEGIN TRANSACTION...END TRANSACTION.

  • Обновления, произведенные во вложенных транзакциях не фиксируются до вызова внешней END TRANSACTION.

  • Во вложенных транзакциях END TRANSACTION работает только на транзакции, инициированной последней выданной BEGIN TRANSACTION.

  • Во вложенных транзакциях оператор ROLLBACK работает только на транзакции инициированной последней выданной BEGIN TRANSACTION.

  • Наиболее вложенное обновление в наборе вложенных транзакций на одних и тех же данных имеет преимущество над всеми остальными в том же самом блоке вложенных транзакций .

Уведомление в приведенном ниже примере вызвано тем, что так как изменения во вложенной транзакции не записаны на диск, а только в буфер транзакции, то внетренняя транзакция будет переписывать изменения, сделанные в том же самом поле STATUS поредшествующей транзакции:

 CopyCode imageСкопировать код
BEGIN TRANSACTION &&  транзакция 1
   UPDATE EMPLOYEE ; &&  первое изменение
      SET STATUS = "Contract" ;
      WHERE EMPID BETWEEN 9001 AND 10000
   BEGIN TRANSACTION &&  транзакция 2
      UPDATE EMPLOYEE ;
         SET STATUS = "Exempt" ;
         WHERE HIREDATE > {^1998-01-01}  &&  перепись
   END TRANSACTION &&  transaction 2
END TRANSACTION    &&  transaction 1

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

Пример изменения записей во вложенных транзакциях
Код Комментарий

DO WHILE TXNLEVEL( ) > 0

ROLLBACK

ENDDO

Очистка от других транзакций.

CLOSE ALL

SET MULTILOCKS ON

SET EXCLUSIVE OFF

Установка среды для буферизации.

OPEN DATABASE test

USE mrgtest1

CURSORSETPROP('buffering',5)

GO TOP

Разблокировка оптимистического буферирования таблицы.

REPLACE fld1 WITH "changed"

SKIP

REPLACE fld1 WITH "another change"

MESSAGEBOX("modify first field of both" + ;

"records on another machine")

Изменение записи. Изменение другой записи.

BEGIN TRANSACTION

lSuccess = TABLEUPDATE(.T.,.F.)

Запуск транзакции 1 и попытка обновить все измененные записи без force.

IF lSuccess = .F.

ROLLBACK

AERROR(aErrors)

DO CASE

CASE aErrors[1,1] = 1539

...

CASE aErrors[1,1] = 1581

...

CASE aErrors[1,1] = 1582

Если обновление не случилось, откат тразакции. Получение ошибки из AERROR( ). Определение причины ошибки. Если траггер не удалось выполнить, то производится его обслуживание. Если поле не принимает null-значения, обслуживается этот случай. Если было нарушено правило поля, обслуживается этот случай.

CASE aErrors[1,1] = 1585

nNextModified = getnextmodified(0)

DO WHILE nNextModified <> 0

GO nNextModified

RLOCK()

FOR nField = 1 to FCOUNT()

cField = FIELD(nField)

if OLDVAL(cField) <> CURVAL(cField)

Если запись была изменена другим пользователем, ищется первая измененая запись. Поиск проводится через все измененные записи, начиная с первой записи. Блокировка каждой записи для гарантии, что вы можете произвести обновление. Проверка каждого поля на любое изменение. Проверка буферированного значения против значения на диске, а затем выдача сообщения пользователю.

nResult = MESSAGEBOX;

("Data was changed " + ;

"by another user — keep"+ ;

"changes?", 4+48, ;

"Modified Record")

 

IF nResult = 7

TABLEREVERT(.F.)

UNLOCK record nNextModified

ENDIF

Если пользователь ответил "No," возврат одной записи к ее прежнему состоянию и ее разблокировка.

EXIT

ENDIF

ENDFOR

Выход из цикла Break "FOR nField...".

ENDDO

Получить следующую измененную запись.

BEGIN TRANSACTION

TABLEUPDATE(.T.,.T.)

END TRANSACTION

UNLOCK

Запуск транзакции 2 и обновление всех не возвращенных к прежнему состоянию записей с force. Завершение транзакции 2. Снятие блокировки.

CASE aErrors[1,1] = 109

...

CASE aErrors[1,1] = 1583

...

CASE aErrors[1,1] = 1884

...

OTHERWISE

MESSAGEBOX( "Unknown error "+;

"message: " + STR(aErrors[1,1]))

ENDCASE

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

ELSE

END TRANSACTION

ENDIF

Завершение транзакции 1.

Защита удаленных обновлений

Транзакции могут защитить вас от генерируемых системой ошибок во время обновления данных на удаленных таблицах. Приведенный ниже пример используется транзакции для обертывания операций записи данных на удаленной таблице.

Пример транзакции на удаленной таблице
Код Комментарий

hConnect = CURSORGETPROP('connecthandle')

SQLSETPROP(hConnect, 'transmode',

DB_TRANSMANUAL)

Получаем дескриптор соединения и разрешаем ручные транзакции.

BEGIN TRANSACTION

Начинаем ручную транзакцию.

lSuccess = TABLEUPDATE(.T.,.F.)

IF lSuccess = .F.

SQLROLLBACK (hConnect)

ROLLBACK

Пытаемся обновить все записи без force. Если не удалось, откатываем транзакицю на соединении для курсора.

AERROR(aErrors)

DO CASE

Получаем ошибку из AERROR( ).

CASE aErrors[1,1] = 1539

...

Если триггер не удалось выполнить, обрабатывается этот случай.

CASE aErrors[1,1] = 1581

...

Если поле не принимает null-значений, обрабатывается этот случай.

CASE aErrors[1,1] = 1582

...

Если нарушено правило поля, обрабатывается этот случай.

CASE aErrors[1,1] = 1585

nNextModified = GETNEXTMODIFIED(0)

DO WHILE nNextModified <> 0

GO nNextModified

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

FOR nField = 1 to FCOUNT()

cField = FIELD(nField)

IF OLDVAL(cField) <> CURVAL(cField)

nResult = MESSAGEBOX;

("Data has been changed ;

by another user. ;

Keep changes?",4+48,;

"Modified Record")

Проверяется каждое поле на любое изменение. Проверяется буферированное значение против значения на диске, а затем пользователю выводится сообщение.

IF nResult = 7

TABLEREVERT(.F.)

ENDIF

EXIT

ENDIF

ENDFOR

nNextModified = ;

GETNEXTMODIFIED(nNextModified)

ENDDO

Если пользователь ответил "No," возврат одной записи к ее прежнему состоянию . Выход из цикла "FOR nField..."  Получаем следующую измененную запись.

TABLEUPDATE(.T.,.T.)

SQLCOMMIT(hConnect)

Обновление всех не возвращенных к прежнему состоянию записей с force и выдача фиксации.

CASE aErrors[1,1] = 109

* Handle the error

Ошибка 109 указывает, что запись используется другим пользователем.

CASE aErrors[1,1] = 1583

* Handle the error

Ошибка 1583 указывает, что было нарушено правило строки.

CASE aErrors[1,1] = 1884

* Handle the error

Ошибка 1884 указывает, что была нарушена уникальность индекса.

OTHERWISE

* Handle generic errors.

 

MESSAGEBOX("Unknown error message:" ;

+ STR(aErrors[1,1]))

ENDCASE

Пользователю выводится диалоговое окно. Конец обработки ошибок.

ELSE

SQLCOMMIT(hConnect)

END TRANSACTION

ENDIF

Если все ошибки были обработаны и вся транзакция была осуществлена, выдается фиксация и завершение транзакции.

Смотрите также