    23 ГЛАВА 

    Совместный доступ к данным 

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

    Разграничение транзакций 

    Разграничение транзакций (transaction isolation) — это механизм, который 
используется в системах управления базами данных для изоляции одной транзакции 
от воздействия другой. В Delphi разграничение транзакций организовано на трех 
отдельных уровнях, каждый из которых имеет собственные характеристики. Эти 
уровни разграничения транзакций (TIL — Transaction Isolation Level) 
воздействуют на возможность доступа одной транзакции к изменениям, 
производимым другими параллельно выполняющимися транзакциями. 
    Обычно Delphi в создаваемых приложениях устанавливает заданный по 
умолчанию уровень разграничения транзакций, а также автоматически запускает 
транзакции и сохраняет результаты их работы при модификации базы данных 
приложением. Если вам необходимо большая свобода действий, то можно управлять 
транзакциями с помощью компонента TDatabase или посредством режима Passthrough 
SQL. Управление транзакциями с использованием компонента TDatabase более 
предпочтительно, поскольку имеется гарантия того, что Delphi "видит" 
проводимую вами обработку транзакций. 

    Выбор соответствующего уровня разграничения транзакций 

    Большинство серверов баз данных, включая InterBase, поддерживают три 
отдельных уровня разграничения транзакций. Строго говоря, с точки зрения 
управления, выгодно использовать один уровень во всем приложении. 
Использование одного уровня разграничения транзакций для всех приложений баз 
данных еще более предпочтительно. Такая стратегия защищает целостность базы 
данных и обеспечивает адекватное "ннесение изменений. 
    Однако требования конкретного приложения могут помешать использованию 
этого упрощенного подхода. Использование подхода "один размер подходит всем" к 
разграничению транзакций может привести к разрушению или нарушению целостности 
базы данных. В итоге вы можете столкнуться с неожиданной блокировкой таблиц 
или, казалось бы, немотивированным отказом в модификации базы данных. 
    Для успешной реализации разграничения транзакций сначала обеспечьте 
целостность базы данных, а затем уделите внимание эффективности, оптимизируя 
схему разграничения транзакций. 

    Глава 23. Совместный доступ к данным 437 

    Классические проблемы разграничения транзакций 

    Проблемы, с которыми сталкиваются схемы разграничения транзакций, могут 
быть разделены на пять основных групп. 
    О Чтение без проверки (dirty read). Происходит, когда одна транзакция 
обращается к данным, которые были изменены, но еще не сохранены другой 
транзакцией. Поскольку несохраненные изменения могут быть отменены, то 
транзакция, которая прочитала их перед отменой, получит ошибочные данные. 
    О  Неповторяемое чтение (nonrepeatable read). Происходит, когда одна 
транзакция изменяет строки в момент выполнения операции чтения другой 
транзакцией. Чтение называется неповторяемым, поскольку при повторном чтении, 
вследствие изменений, внесенных первой транзакцией, возвращаются другие 
данные. Неповторяемое чтение допускается в режиме READ COMMITTED, поскольку в 
этом режиме транзакции могут считывать данные, измененные другими 
транзакциями, после того как эти изменения сохраняются в базе данных. 
    Q  Фантомные строки (phantom row). Появляются, когда транзакция может 
выбрать неполный набор новых строк, записанных второй транзакцией. Фантомные 
строки не предотвращаются уровнем разграничения транзакций READ COMMITTED. 
    Q  Потерянные изменения (lost update). Возникают, когда одна транзакция 
перезаписывает изменения, сделанные другой, выполняющейся одновременно с 
первой, транзакцией. 
    Q  Побочные эффекты изменений (update side effect). Могут проявляться, 
когда значения в одной строке зависят от значений в другой, и эти зависимости 
не защищены соответствующими ограничениями целостности. Когда две (или более) 
транзакции одновременно считывают и модифицируют одни и те же данные, могут 
происходить нежелательные побочные эффекты, если первая транзакция копирует из 
одной строки в другую какое-то значение, а в это время вторая транзакция 
изменяет данное значение в исходной строке. Такое нарушение целостности 
называется перемежающимися (interleaved) транзакциями. 
    Выбор уровня разграничения транзакций с помощью свойства Translsolation 
компонента TDatabase определяется в зависимости от используемой СУБД и от 
наличия поддержки данного уровня разграничения транзакций на сервере. Обе эти 
темы подробнее обсуждаются в следующих разделах. 

    Управление транзакциями с помощью компонента TDatabase 

    Управление транзакциями осуществляется посредством использованием свойства 
Translsolation и методов StartTransaction, Commit И Rollback компонента TDatabase. 
    Свойство Translsolation управляет уровнем разграничения транзакций на 
сервере базы данных для одного подключения. Как упоминалось ранее, уровень 
разграничения транзакций для каждого подключения к серверу определяет 
возможность доступа конкурирующих транзакций к внесенным вами изменениям и 
способность вашей транзакции "видеть" изменения, внесенные другими транзакциями. 
    Свойство Translsolation может принимать три значения: tiDirtyRead, 
tiReadCommitted и tiRepeatableRead. По умолчанию используется значение 
tiReadCommitted. Эти три значения свойства Translsolation имеют следующий смысл. 
    Q  tiDirtyRead — видимы несохраненные изменения, сделанные другими 
транзакциями. Q   tiReadCommitted — видимы только сохраненные изменения. 
    Q   tiRepeatableRead — изменения, внесенные другими транзакциями в ранее 
прочитанные данные невидимы, т.е. на этом уровне разграничения каждый раз, 
когда транзакция считывает данную запись, она всегда получает одну и ту же запись. 
    Метод StartTransaction компонента TDatabase инициализирует транзакцию — 
группу изменений базы данных, которую вы хотите обрабатывать как единое целое. 
Они будут либо все применены к базе данных либо ни одно из них. 
    Метод Commit сохраняет внесенные в ходе транзакции изменения в базе 
данных. Его можно рассматривать как команду базы данных save. 
    Метод Rollback отменяет все внесенные в ходе транзакции изменения базы 
данных. Его можно рассматривать как команду базы данных undo. 

    438 IV. Темы повышенной сложности 

    Свойство Transisoiation и уровни разграничения транзакций СУБД 
    Реализация уровней разграничения, определяемых свойством Transisoiation 
компонента TDatabase, зависит от используемой СУБД. Поэтому на том или ином 
сервере они могут отличаться от описанных выше или вообще не поддерживаться 
сервером. Если необходимый вам уровень Transisoiation не поддерживается 
сервером, осуществляется переход на следующий, более высокий уровень разграничения. 
    В табл. 23.1 перечислены уровни разграничения транзакций, определяемые 
значением свойства Transisoiation, при реализации на различных платформах СУБД. 

    Таблица 23.1. Свойство Transisoiation и уровни разграничения транзакций СУБД 

    Значения Transisoiation	InterBase	Oracle	Sybase и Microsoft SQL Server 
    
    tiDirtyRead	Read commited	Read commited	Read commited 

    tiRedCommittited	Read commited	Read commited	Read commited 

    tiRepeatable		Repeatable read	Repeatable read для данных, доступных только 
для чтения, для всех остальных — Error 


    Управление транзакциями с помощью SQL 

    Управлять обработкой транзакций на сервере можно также с использованием 
режима Passthrough SQL. Для этого используются команды SQL, которые управляют 
обработкой транзакций на сервере СУБД. Примеры в следующих разделах используют 
синтаксис SQL для InterBase. 
    Следует помнить, что установка уровня разграничения транзакций с помощью 
SQLPASSTHRUMODE со значениями SHARED AUTOCOMMIT или SHARED NOAUTOCOMMIT может 
стать причиной того, что новая установка уровня разграничения транзакций 
случайно повлияет на Другие транзакции, инициализированные приложением. 

    Разграничение транзакций 

    Чтобы установить уровень разграничения транзакций с помощью SQL, 
используйте команду SET TRANSACTION. InterBase поддерживает три уровня 
разграничения транзакций — SNAPSHOT, SNAPSHOT TABLE STABILITY и READ 
COMMITTED. Чтобы установить один из них как новый уровень разграничения 
транзакций, используйте команду SQL SET TRANSACTION ISOLATION LEVEL. 
    Приведем функции каждого уровня разграничения транзакций SQL. 
    О  SNAPSHOT. В момент начала транзакции создается "моментальный снимок" 
(snapshot) базы данных. При использовании этого уровня разграничения 
транзакций изменения, сделанные другими активными транзакциями, являются невидимыми. 
    Q  SNAPSHOT TABLE STABILITY. Блокируются таблицы, из которых транзакция 
считывает и в которые записывает данные. Другие транзакции могут получить 
доступ к этим таблицам только для чтения. 
    Q READ COMMITTED. Каждая транзакция работает только с самой последней 
сохраненной версией строки, независимо от ранее вносимых в эту строку 
изменений. Использование данного уровня позволяет транзакциям вносить 
изменения, которые не вызывают конфликтов с другими транзакциями. READ 
COMMITTED поддерживает следующие два необязательных параметра. 
    •    NO RECORD_VERSION (используется по умолчанию). Транзакция получает 
доступ только к самой последней версии строки. Если в команде SET TRANSACTION 
был использован параметр WAIT, транзакция ожидает до тех пор, пока самая 
последняя версия записи не будет сохранена или отменена, а затем Повторяет 
операцию чтения. 
    •    RECORD_VERSION. Транзакция считывает самую последнюю сохраненную 
версию строки, даже если в базе данных имеется_более новая несохраненная версия. 

    Глава 23. Совместный доступ к данным 439 

    Разграничение транзакций на сервере и классические проблемы разграничения транзакций 
    Как правило, все разработчики СУБД используют собственные способы решения 
классических проблем разграничения транзакций. Каждый уровень разграничения 
транзакций, поддерживаемый сервером, должен некоторым образом решать 
упомянутые выше проблемы. В табл. 23.2 содержится информация о том, как 
решаются эти вопросы в InterBase с помощью уровней разграничения транзакций. 
    Таблица 23.2. Уровни разграничения транзакций InterBase и пять 
классических проблем управления транзакциями 

    Уровень разграничения транзакций      Проблема 
    
    Решение 

    SNAPSHOT 

    Потерянные изменения 

    Другие транзакции не должны модифицировать строки, которые модифицируются 
такой транзакцией 

    Чтение без проверки 

    Не нужно считывать изменения, сделанные другими транзакциями; другие 
транзакции видят предыдущую версию строки, модифицируемой этой транзакцией 

    Неповторяемое чтение 

    Можно считать только ту версию строки, которая была сохранена в начале транзакции 

    Фантомные строки 

    Можно считать только ту версию строки, которая была сохранена в начале транзакции 

    Побочные эффекты изменений 

    Не нужно считывать изменения, сделанные другими транзакциями; другие 
транзакции видят предыдущую версию строки, модифицируемой этой транзакцией 

    READ COMMITTED 

    Потерянные изменения 

    Другие транзакции не должны модифицировать строки, которые модифицируются 
такой транзакцией 

    Чтение без проверки 

    Другие транзакции "видят" предыдущую или сохраненную версию строки, 
модифицируемой этой транзакцией 

    Неповторяемое чтение     Допускаются по определению 

    Фантомные строки 

    Могут встречаться, поскольку на этом уровне разграничения транзакции 
"видят" сохраненные изменения, сделанные другими транзакциями 

    Побочные эффекты изменений 

    Другие транзакции "видят" предыдущую или сохраненную версию строки, 
модифицируемой такой транзакцией 

    SNAPSHOT TABLE STABILITY     Потерянные изменения 

    Предотвращение   модификаций  таблиц,   контролируемых другими транзакциями 

    Чтение без проверки 

    Предотвращение доступа других транзакций к модифицируемым таблицам 

    Неповторяемое чтение 

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

    Фантомные строки 

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

    Побочные эффекты изменений 

    Предотвращение   модификаций   в   контролируемых таблицах другими транзакциями 

    440 IV. Темы повышенной сложности 

    Выбор соответствующего уровня разграничения транзакций 

    Как вы могли заметить, каждая проблема разграничения транзакций с 
некоторыми исключениями решается в той или иной степени всеми тремя уровнями 
разграничения транзакций InterBase. Выбор уровня зависит в основном от 
потребностей ваших приложений. 
    По умолчанию используется уровень разграничения транзакций SNAPSHOT. Для 
большинства приложений следует выбирать либо SNAPSHOT либо READ COMMITTED. 
SNAPSHOT TABLE STABILITY может на неопределенное время заблокировать доступ 
других пользователей к необходимым им таблицам. Поэтому такого уровня 
разграничения транзакций следует избегать, если вам не требуются использовать 
его особенности. 
    Выбор между SNAPSHOT и READ COMMITTED определяется тем, нужно ли 
транзакциям позволять "увидеть" сохраненные модификации других транзакций. 
Если нет — используйте SNAPSHOT, в противном случае лучшим выбором будет READ 
COMMITTED. Вообще говоря, уровень разграничения транзакций READ COMMITTED 
создает меньше конфликтов блокировок. 

    Управление транзакциями 

    Управление транзакциями в InterBase осуществляется с помощью команд SQL 
SET TRANSACTION, COMMIT и ROLLBACK. SET TRANSACTION имеет различное 
применение, включая упомянутую ранее установку уровня разграничения 
транзакций. Команда COMMIT работает так же, как и метод Commit компонента 
TDatabase; она аналогична команде базы данных save. Команда ROLLBACK 
функционирует аналогично методу Rollback компонента TDatabase; она отменяет 
изменения, сделанные в базе данных, после последнего использования команды COMMIT. 

    SET TRANSACTION 

    Для начала новой транзакции используется команда SET TRANSACTION. SET  TRANSACTION 
    Если вы хотите начать транзакцию только для чтения, можете использовать 
необязательное ключевое СЛОВО READ   ONLY. 

    SET TRANSACTION READ ONLY 

    Многие СУБД поддерживают также поименованные транзакции. Это, в частности, 
дает возможность создавать вложенные транзакции. В InterBase SQL команды 
модификации данных (включая INSERT, UPDATE H'DELETE) могут также напрямую 
использовать поименованные транзакции. Вот пример команды InterBase для 
запуска поименованной транзакции. 

    SET  TRANSACTION   :UpdateCustomers 

    Обратите внимание, что переменная базового языка : UpdateCustomers должна 
быть предварительно объявлена и инициализирована. 
    Вот синтаксис Sybase Transact-SQL для выполнения такой же операции. 

    BEGIN  TRANSACTION  UpdateCustomers 
    COMMIT И ROLLBACK 

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

    На заметку 

    Старайтесь помещать между командами SET TRANSACTION и COMMIT или ROLLBACK 
только те операторы, которые на самом деле модифицируют данные. Все поиски или 
другие операции сбора данных выполняйте перед инициализацией транзакции с 
помощью команды SET TRANSACTION. Это поможет минимизировать блокировку других 
пользователей вне ресурсов базы данных. 

    Глава 23. Совместный доступ к данным	441 

    Утилита WISQL InterBase начинает транзакцию автоматически (выдавая 
эквивалент команды InterBase SET TRANSACTION) при первой загрузке. При выходе 
из утилиты необходимо решить, хотите ли вы сохранить результаты своей работы. 
Кроме того, можно сохранить или отменить сделанные изменения в любое время при 
работе с этой утилитой, используя команды Commit Work и Rollback Work из меню File. 

    Системы управления доступом 

    Понятия, лежащие в основе систем управления доступом, относительно просты, 
если их выразить обычными словами. Например, систему управления доступом можно 
было бы сравнить с системой управления поездами на железной дороге. Если в 
движении находится только один поезд, то никакой проблемы доступа нет. Если же 
к системе добавляется несколько поездов, вы должны составить расписание так, 
чтобы поезда не сталкивались, а грузы при этом доставлялись своевременно. 
Подобные проблемы возникают и в базах данных. Вы должны найти способ 
предохранения модификаций, которые делают пользователи в базе данных, от 
"столкновений" (от перезаписи друг друга), обеспечивая при этом функциональные 
возможности, необходимые пользователям. 
    Очевидно, если запрос к базе данных посылаете только вы, никаких проблем 
доступа не возникнет. Они появятся тогда, когда два или больше пользователей 
попытаются изменить одни и те же данные в одно и то же время. Когда изменения, 
сделанные пользователем в базе данных, теряются или не допускаются вследствие 
изменений, сделанных другим пользователем, происходит потеря изменений (lost 
update) или конфликт изменений (update conflict). Эффективные системы 
управления доступом решают эти проблемы без снижения производительности или 
ухудшения функциональных возможностей базы данных. 
    Системы управления доступом можно разделить на две основные разновидности: 
пессимистические и оптимистические. Эти системы часто называют 
пессимистическими и оптимистическими системами блокировки (locking system), 
поскольку блокировка ресурсов базы данных является самым распространенным 
методом решения проблем доступа. Реакция систем на попытку конкурирующих 
транзакций одновременно изменить одни и те же данные различна. 
Пессимистическая система управления доступом допускает вероятность этого, 
тогда как оптимистическая система считает такую ситуацию аварийной и 
обрабатывают ее как исключительную. Оптимистическая система управления 
доступом предполагает, что большинство запросов только считывают данные и что 
изменение одного элемента данных разными запросами не может происходить в одно 
и то же время. 

    Пессимистическое управление доступом 

    Поскольку пессимистическая система управления доступом считает конфликты 
обновления допустимыми, то она блокирует ресурсы, используемые данной 
транзакцией, для устранения возможности конфликта. Другие транзакции, которым 
необходим заблокированные транзакцией ресурсы, должны будут ждать момента 
завершения данной транзакции. 
    Не смотря на то, что пессимистические системы управления могут 
использовать блокировки, запрещающие как чтение, так и запись, блокировка 
одного лишь чтения в реальных условиях никогда не используется. Обычно 
используются блокировки, разрешающие только чтение данных, или разрешающие как 
чтение так и запись. Такая блокировка обычно поддерживается на определенном 
уровне — начиная от полной блокировки нескольких таблиц до блокировки 
отдельного поля. 
    Чаще всего блокируется таблица, строка и страница (SQL Server), иногда это 
происходит последовательно — т.е. вам часто придется сталкиваться с 
блокировкой нескольких строк, за которой следует блокировка всей таблицы, в 
которой они расположены. Это называется распространением блокировки (lock 
escalation), и происходит, когда сервер базы данных считает, что имеется 
достаточное количество заблокированных строк и страниц для того, чтобы полная 
блокировка таблицы была оправдана. Например, в СУБД Sybase поддерживается по 
умолчанию применяется распространение блокировки страницы на всю таблицу, если 
в ходе транзакции возникло более 200 блокировок страниц данной таблицы. 
    На практике блокировка отдельных полей никогда не используется; она 
слишком требовательна к ресурсам и не приносит никакой конкретной пользы. 
Предотвращение изменений поля в строке, которая не модифицируется другой 
транзакцией, почти всегда бесполезно. 
    Чем ниже уровень блокировки, тем больше пользователей могут одновременно 
получить доступ к ее ресурсам. По мере увеличения использования увеличивается 
также число блокировок. Очевидно, если вы блокируете только одну таблицу, то 
вы имеете только одну блокировку. Но если вы блокируете несколько строк в 
нескольких таблицах, вы получите много блокировок. Если количество блокировок 
увеличивается, ресурсы, необходимые серверу для управления ими, а также время 
для их создания и удаления, также уве

    442 IV. Темы повышенной сложности 

личиваются. Как всегда, увеличение функциональных возможностей влечет за 
собой повышение требований к ресурсам. 
    Из-за склонности блокировать все таблицы, пессимистические системы 
управления восприимчивы к ситуациям взаимной блокировки. Взаимная блокировка 
происходит, когда транзакция 1 блокирует таблицу, необходимую транзакции 2, и 
наоборот. Поскольку ни одна из транзакций не может разблокировать таблицы, 
необходимые для ее завершения, они обе блокируются в тупиковой ситуации и не 
могут завершиться без вмешательства извне. 
    Серверы баз данных, как правило, реализуют схемы пессимистического 
управления. Это наследие тех времен, когда для реализации механизмов 
оптимистического управления не существовало ресурсов на уровне рабочей 
станции. Поскольку Delphi реализует схему оптимистического управления, то при 
разработке приложений баз данных вы можете использовать оба подхода. 
    Большинство серверов баз данных блокируют соответствующие элементы 
автоматически при внесении изменений в базу данных. Например, большинство 
серверов блокируют строку при ее модификации, чтобы запретить другим 
пользователям одновременно модифицировать эту строку. 
    Sybase и Microsoft SQL Server обеспечивают блокировку таблиц и страниц, но 
не блокируют отдельные строки, подобно Oracle. При попытке изменить строку на 
время обновления блокируется страница, на которой она находится. Обновление 
обычно происходит в группе строк, сконцентрированных в одной области базы 
данных. При постраничной блокировке можно одновременно блокировать несколько 
строк одной страницы. При этом экономятся ресурсы сервера, но строки этой же 
страницы, не связанные с модификацией, могут оказаться недоступными. При 
использовании такого подхода обновление отдельных строк становится 
нерациональным. Несмотря на это, подход, используемый в SQL Server, работает 
так же хорошо, как и подходы, используемые другими производителями СУБД. 

    На заметку 

    В Microsoft SQL Server уже добавлены средства блокировки строк в некоторых 
случаях, однако эта возможность еще не достигла уровня поддержки построчной 
блокировки Oracle, В Sybase тоже уже появился механизм построчной блокировки, 
но в коммерческих версиях он еще отсутствует, 

    Оптимистическое управление доступом 

    В оптимистических системах управления доступом предполагается, что 
большинство модификаций являются неконфликтными и большинство пользователей 
лишь читают, но не обновляют базу данных. Лучше всего продемонстрировать 
работу оптимистической системы управления на примере. Представьте себе 
человека, который работает в издательстве и отвечает за подготовку книг к 
изданию. После поступления рукописи в издательство он делает ее копии и 
распределяет их между редакторами. Редакторы правят копии и отправляют их 
обратно ведущему редактору, который должен объединить правки и внести их в 
исходную рукопись. После внесения правок редакторы получат исправленную 
версию, чтобы не повторять редакторскую работу, которая уже была сделана. 
    Это удачный пример оптимистического управления доступом по двум причинам. 
Во-первых, обратите внимание, что исходная рукопись всегда остается у ведущего 
редактора. В оптимистических системах управления доступом сначала изменяется 
копия данных, а не сами данные. Внесение изменений на сервере выполняется 
таким образом, чтобы предотвратить перезапись изменений, сделанных другими пользователями. 
    Заметьте также, что изменения, вносимые редакторами, различаются по типу. 
Один редактор может проверять правописание и стиль изложения, другой — 
техническую правильность рукописи и т.д. Как правило, два редактора никогда 
одновременно не проверяют одну рукопись на предмет одних и тех же ошибок. Это 
— основная предпосылка оптимистических системы управления доступом. 
Модификации базы данных организации обычно разбиваются по ее отделам, уровням 
управления или другим критериям. Этот подход хорошо работает в оптимистических 
системах, потому что ресурсы базы данных изначально не блокируются. 

    Оптимистическое управление доступом в приложениях Delphi 

    В Delphi оптимистическое управление доступом реализуется с помощью 
свойства UpdateMode компонентов ТТаЫе и TQuery. Как и во всех оптимистических 
системах управления доступом, Delphi получает копию строки с сервера, 
позволяет вам изменить ее, а затем отправляет изменения на сервер с помощью 
команды SQL UPDATE. Команда UPDATE содержит оператор WHERE, определяющий 
строку, которая должна быть изменена. Свойство updateMode определяет, какие 
столбцы таблицы перечислены в операторе WHERE. 

    Глава 23. Совместный доступ к данным	443 

    Свойство UpdateMode может принимать одно из трех значений. Q  upWhereAll 
(используется по умолчанию) — оператор WHERE включает все столбцы таблицы. 
    Q  upWhereChanged — оператор WHERE включает ключевые столбцы набора данных 
и столбцы, которые были изменены. 
    Q  upWhereKeyOnly — оператор WHERE включает только ключевые столбцы набора 
данных (используйте его только в том случае, если вы имеете исключительное 
право использования таблицы базы данных). 
    Оба компонента, ТТаЫе и TQuery, обладают свойством UpdateMode. По 
умолчанию оно принимает значение upWhereAll. Это означает, что при модификации 
строки в таблице генерируется оператор WHERE, в котором перечислены все 
столбцы таблицы. Это довольно сложно, особенно при работе с большими 
таблицами. В качестве альтернативы для ускорения работы можно использовать 
значение upWhereChanged. Тогда генерируется оператор WHERE, включающий только 
ключевые столбцы таблицы и столбцы, которые были изменены. 
    Допустим, в приложении Delphi было изменено поле LastName таблицы 
CUSTOMER. При использовании значения upWhereAll будет сгенерирован следующий 
оператор SQL (обратите внимание на длину оператора WHERE). 

    UPDATE CUSTOMER 
    SET LastName='newlastname' 
    WHERE CustomerNumber=l 
    AND LastName='Doe' 
    AND FirstName='John' 
    AND StreetAddress='123 SunnyLane' 
    AND City='Anywhere' 
    AND State='OK' 
    AND Zip='73115' 

    Для сравнения приведем оператор, сгенерированный для значения upWhereChanged. 

    UPDATE CUSTOMER SET LastName='newlastname' WHERE CustomerNumber=l AND LastName='Doe' 

    Обратите внимание, насколько эта команда короче. Заметьте также, что во 
втором случае исключается перезапись значения поля LastName другим 
пользователем, поскольку в оператор WHERE включено исходное значение LastName. 
Если другой пользователь изменяет поле LastName в промежуток времени между 
чтением строки и модификацией, выполненной текущим пользователем, то команда 
UPDATE, сгенерированная для значения upWhereChanged, не будет выполнена, что и 
требовалось. Конечно, этот метод не настолько надежен, как метод upWhereAll. 
Другой пользователь может удалить строку после того, как приложение прочитает 
ее, а затем добавить новую запись в таблицу, возможно, с тем же ключом и 
значением LastName, что и исходная запись. При выполнении команды UPDATE для 
вашей записи будет модифицирована неверная строка. Но подобная ситуация обычно 
возникает очень редко. 
    Значение upWhereKeyOnly свойства UpdateMode еще менее надежно, но и оно 
находит свое применение. В этом режиме проверяются только значения ключей 
изменяемых строк. При этом не учитывается, что другой пользователь может 
изменить обновляемое вами поле после того, как вы изначально прочитали запись. 
В большинстве многопользовательских приложений небезопасно рассчитывать на то, 
что другой пользователь не изменил запись, которую вы перед этим считали в 
приложении. Следовательно, этот вид оптимизации следует выполнять только в 
редких случаях. Поскольку оператор WHERE, сгенерированный для значения 
upWhereKeyOnly, естественно будет короче, чем оператор, включающий 
дополнительные поля, то, вероятно, он будет работать быстрее. Тем не менее, 
прежде чем использовать это значение, следует посоветоваться с администратором 
базы данных, поскольку это может привести к потере данных. 
    На практике следует придерживаться значения upWhereAll, если у вас нет 
особой причины отказаться от него. Значение upWhereChanged стоит выбирать 
только при работе с таблицей, содержащей большое количество столбцов, 
обработка которой в режиме upWhereAll происходит слишком медленно. Значение 
upWhereKeyOnly можно использовать только в том случае, если вы имеете 
исключительный доступ к таблице — в других случаях не пользуйтесь этим 
значением. Одно из практических применений upWhereKeyOnly — получение данных 
из определенной машины. Если вам необходимо обновить значения в таблице базы 
данных с использованием полученных данных, то вы, вероятно, окажетесь 
единственным пользователем, работающим с этой таблицей. При этом быстрое 
выполнение команды UPDATE может оказаться достаточно важным фактором. 
Возможно, в этом случае upWhereKeyOnly — это то, что вам нужно, но я советую 
сначала проконсультироваться с администратором базы данных. 

    444 IV. Темы повышенной сложности 

    Ведение журнала регистрации транзакций 

    Вас когда-нибудь интересовал вопрос о том, как серверы баз данных 
программно защищают информацию от разрушения при сбоях в электропитании или 
при поломке компьютера? Хранит ли сервер какой-либо список проведенных 
изменений? Как он находит список транзакций, которые следует отменить при 
восстановлении базы данных? 
    Ответ на все эти вопросы один: все изменения сначала регистрируются в 
журнале регистрации транзакций базы данных, а затем сохраняются в самой базе 
данных после успешного завершения транзакции. Это означает, что, невзирая на 
название, журнал регистрации транзакций — это не просто файл регистрации. Он 
принимает самое активное участие в работе сервера! Если сбой питания на 
компьютере, используемого для работы сервера СУБД, произошел до того, как 
сделанные вами изменения были сохранены в базе данных, то нет никакой 
необходимости проводить восстановительную работу — база данных не 
модифицировалась. Поскольку несохраненные изменения применяются только строго 
согласно журналу регистрации транзакций, сервер СУБД может легко отменить 
незавершенные транзакции без воздействия на базу данных. 
    При разработке приложений баз данных журнал регистрации транзакций обычно 
выпадает из поля зрения. Его обычно связывают со сферой администрирования базы 
данных, поэтому разработчики приложений зачастую, к сожалению, не уделяют 
должного внимания ведению журнала регистрации транзакций. 
    Однако, существуют два правила управления журналом регистрации транзакций, 
которые должен учитывать компетентный разработчик баз данных архитектуры 
клиент/сервер. Первое: свести к минимуму информацию, которая заносится в 
журнал регистрации транзакций. Второе: разбить большие транзакции на несколько 
мелких, чтобы не перегружать журнал регистрации. Обсудим каждое из этих правил. 
    Сведение к минимуму информации, вносимой в журнал регистрации 
    Общей ошибкой разработчиков приложений клиент/сервер, исходящих из баз 
данных с плоской моделью файлов, таких как dBASE и Paradox, является 
использование для выполнения групповой модификации данных программных 
конструкций, а не операторов SQL. Это хорошо заметно на следующем примере. 
Допустим, вам необходимо преобразовать все поля фамилий в таблице CUSTOMER к 
верхнему регистру. Программист, который раньше создавал приложения для dBASE, 
может написать для выполнения этой модификации следующие операторы Object Pascal. 

    With taCUSTOMER do begin First; 
    While not EOF do begin FieldByName('LastName').AsString:= 
    Uppercase(FieldByName('LastName').AsString); Next; 
    end; end; 

    Такой подход является не только медленным, но приводит также к 
нежелательному эффекту инициализации и сохранения транзакций для каждой строки 
таблицы. Более того, если в процессе выполнения "цикла возникает проблема, то 
часть строк будет модифицирована, а другая — нет. Часть фамилий будет 
переведена в верхний регистр, а часть останется неизменной. Проблему 
модификации можно было бы решить, вызывая до начала цикла метод 
StartTransaction компонента TDatabase. Однако, проблема неэффективных 
модификаций при этом сохраняется. На каждой итерации цикла будет 
генерироваться отдельная команда SQL UPDATE с собственным оператором WHERE для 
размещения следующей строки в таблице. Если таблица CUSTOMER очень велика, то 
процесс будет происходить очень медленно. 
    Выполнение этой операции можно ускорить, используя для выполнения 
модификаций компонент TQuery или TStoredProc (при этом по назначению 
используется журнал регистрации транзакций). Оператор SQL, выполняющий те же 
изменения таблицы CUSTOMER, выглядит следующим образом. 

    UPDATE CUSTOMER 
    SET LastName=UPPER(LastName) 

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

    Глава 23. Совместный доступ к данным	445 

    Ограничить регистрируемую информацию, генерируемую транзакцией, можно и 
по-другому — избегая команд SQL DELETE. Каждая строка, удаляемая с помощью 
команды DELETE, сначала копируется в журнал регистрации транзакций, чтобы при 
необходимости транзакцию можно было отменить. При работе с большой таблицей 
журнал регистрации транзакций очень быстро заполнится. Если вам необходимо 
удалить все строки в таблице, вместо команды DELETE используйте команду, 
используемую для этих целей в вашей СУБД. Многие серверы поддерживают команду, 
аналогичную команде dBASE ZAP, которая быстро удаляет содержимое таблицы. Для 
этой цели в Sybase и Microsoft SQL Server используется команда TRUNCATE TABLE. 
Она не только работает быстрее, чем команда DELETE, но и не сохраняется в 
журнале регистрации. Если ваш сервер не поддерживает команды удаления всех 
строк таблицы, то проблему можно решить простым сбросом и повторным созданием 
таблицы, в зависимости от способа ее использования в других местах базы данных 
или друг ими приложениями. 
    На платформах Sybase и Microsoft существует еще один способ избежать 
излишней регистрации — использовать команду SELECT INTO вместо INSERT SELECT 
при копировании строк из одной таблицы в другую. Строки, вставленные с помощью 
INSERT SELECT, сохраняются в журнале регистрации, поэтому следует избегать 
больших вставок. Команда SELECT INTO реализована на платформе SQL Server как 
нерегистрируемая операция — она не добавляет никаких записей в журнал 
регистрации. Однако не все СУБД поддерживают такой синтаксис (InterBase, 
например, не поддерживает), поэтому, возможно, вам не всегда удастся 
воспользоваться этим методом. 
    Команда SELECT  INTO диалекта Transact-SQL СУБД SQL Server имеет следующий синтаксис. 

    SELECT LastName, INTO NEWCUSTOMER FROM CUSTOMER 
    FirstName 

    Здесь  создается  таблица  под  названием   NEWCUSTOMER,   в   которую  
вставляются  поля   LastName   и FirstName из таблицы CUSTOMER. Эта операция 
функционально эквивалентна следующему оператору. 

    CREATE  TABLE  NEWCUSTOMER (LastName   CHAR(30), FirstName  CHAR(30)) 
    INSERT   INTO NEWCUSTOMER   (LastName,   FirstName) SELECT  LastName,   
FirstName FROM  CUSTOMER 

    Команду SELECT INTO можно использовать, чтобы избежать создания записи в 
журнале регистрации, в следующих случаях. 
    О	Если необходимо создать таблицу, а затем скопировать в нее строки из 
другой таблицы. 
    Q	Если все поля в новой таблице необходимо заполнить значениями из другой таблицы. 
    Q	Если вы уверены, что возможность отмены вставки строк вам не понадобиться. 
    Дробление больших транзакций 
    Бывают случаи, когда необходимо выполнить массовую модификацию данных 
некоторого вида, что неизбежно создает большое количество записей в журнале 
регистрации. Такие модификации могут привести к переполнению журнала 
регистрации и возникновению серьезных проблем на сервере. Они могут также 
генерировать блокировки, запрещающие другим пользователям доступ ко всей 
таблице. Такие большие транзакции целесообразно разбить на более мелкие. Для 
этого существуют два способа. Выбор конкретного способа зависит от выбранной 
СУБД и потребностей вашего приложения. 
    Во-первых, можно разделить команды модификации данных по горизонтали: одну 
команду, UPDATE или DELETE, можно разбить на несколько команд UPDATE или 
DELETE, каждая из которых использует более ограниченный оператор WHERE. 
Вернемся к предыдущему примеру. Если таблица CUSTOMER содержит миллион 
записей, то для приведения к верхнему регистру каждой фамилии в каждой записи 
можно выполнить следующее. 

    SET TRANSACTION; 
    UPDATE CUSTOMER 
    SET LastName=UPPER(LastName) 
    WHERE CustomerNumber between 1 and 100000; 
    COMMIT; 

    446 IV. Темы повышенной сложности 

    SET TRANSACTION; 
    UPDATE CUSTOMER 
    SET LastName=UPPER(LastName) 
    WHERE CustomerNumber between 100001 and 200000; 
    COMMIT; 
    SET TRANSACTION; 
    UPDATE CUSTOMER 
    SET LastName=UPPER(LastName) 
    WHERE CustomerNumber between 200001 and 300000; 
    COMMIT; 

    Этот простой прием может превратить невыполнимую модификацию в возможную. 
    Во-вторых, можно ограничить число строк, которые команда UPDATE или DELETE 
изменяет за один проход. Этот подход поддерживают не все СУБД (SQL Server, 
например, поддерживает, a InterBase — нет). 
    Для этого используйте поддерживаемую выбранным вами сервером СУБД команду, 
которая ограничивает количество строк, обрабатываемых командой UPDATE или 
DELETE. Затем повторите команду UPDATE или DELETE столько раз, сколько 
необходимо для обработки всех строк. Вот пример с использованием диалекта 
Transact-SQL СУБД SQL Server. 

    SET ROWCOJINJL 50000 
    /* Ограничивает до 50000 количество строк, обрабатываемых командой UPDATE одновременно. 
    */ 
    WHILE (EXISTS (SELECT * FROM ORDERS WHERE AmountoO)) 
    BEGIN 
    UPDATE  ORDERS   SET  Amount=0 
    WHERE AmountoO   /*  Предохраняет UPDATE  от  бесконечного цикла.   */ END 

    Поскольку измененные строки остаются в таблице, то их необходимо исключать 
на каждой итерации. Один из способов состоит в том, чтобы заставить оператор 
WHERE команды UPDATE игнорировать измененные строки. Другими словами, для поля 
Amount следует проверять условие <>0, поскольку при выполнении оператора 
UPDATE полю Amount присваивается значение 0. Таким образом, команда UPDATE 
помечает каждую строку при обработке. Тогда строка не сможет быть обработана дважды. 
    Вот пример использования команды DELETE. 

    SET ROWCOUNT 50000 
    /* Ограничивает действие команды DELETE 50000 строками. */ 
    WHILE (EXISTS (SELECT * FROM ORDERS WHERE Amount=0)) 
    BEGIN 
    DELETE FROM ORDERS 
    WHERE Amount=0 END 


    Резюме 

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

    Что дальше... 

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

    Глава 23. Совместный доступ к данным	447 
