Высокий взрыв адреналина? Это игра вулкан удачи - точно говорю!

8. Лекция: Средства языка SQL для обеспечения авторизации доступа к данным, управления транзакциями, сессиями и подключениями

В этой лекции обсуждаются основные средства SQL:1999, предназначенные для регулирования работы с базами данных. Сначала рассматривается механизм авторизации доступа к объектам SQL-ориентированной базы данных, основанный на понятиях идентификатора пользователя, имени роли и привилегии доступа. Затем описываются особенности SQL-транзакций и основные языковые средства, воздействующие на поведение транзакций. Наконец, обсуждаются зафиксированные в стандарте SQL средства управления подключением к серверу баз данных.

Введение

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

Данная лекция включает материал, в меньшей степени концептуально связанный, чем это было в предыдущих лекциях курса, посвященных языку SQL. В первом из основных разделов лекции мы обсудим базовые идеи авторизации доступа к данным, заложенные в основу языка SQL. Метод авторизации доступа, используемый в SQL, относится к мандатным (mandatory) видам защиты данных. При этом подходе с каждым зарегистрированным в системе пользователем (субъектом) и каждым защищаемым объектом системы связывается мандат, определяющий действия, которые может выполнять данный субъект над данным объектом. В отличие от такого подхода, при применении дискреционного (discretionary) метода ограничения доступа с каждым из объектов системы связывается одна или несколько категорий пользователей, каждой из которых позволяются или запрещаются некоторые действия над объектом.

Следующий раздел посвящен фундаментальному в области баз данных (и не только) понятию транзакции - последовательности операций над базой данных (в общем случае включающей операции обновления базы данных), которая воспринимается системой как одна неделимая операция. При классическом подходе к управлению транзакциями следуют принципу ACID (Atomicy, Consistency, Isolation, Durability). Этому принципу следовали и разработчики языка SQL. Однако понятие транзакции выходит далеко за пределы SQL; механизмы управления транзакциями составляют отдельную и большую исследовательскую область. В данной лекции мы не будем углубляться в технические детали управления транзакциями и ограничимся возможностями, заложенными в язык SQL.

Наконец, в последнем основном разделе лекции мы обсудим средства языка SQL, предназначенные для управления сессиями и подключениями пользовательских приложений. Подавляющее большинство реализаций языка SQL основывается на архитектурной модели клиент-сервер. Приложения обычно выполняются на клиентской аппаратуре, отделенной (по крайней мере, логически) от серверной аппаратуры, на которой работает собственно СУБД. Чтобы получить доступ к базе данных, приложение должно подключиться к серверу и образовать сессию в этом подключении. У приложения может одновременно существовать несколько подключений к разным серверам баз данных, но не более одной сессии в каждом подключении.

Поддержка авторизации доступа к данным в языке SQL

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

В языке SQL (SQL:1999) предусмотрены возможности контроля доступа к разным объектам базы данных, в том числе к следующим объектам:

  • таблицам;
  • столбцам таблиц;
  • представлениям;
  • доменам;
  • наборам символов1);
  • порядкам сортировки символов (collation);
  • преобразованиям (translation);
  • триггерам;
  • подпрограммам, вызываемым из SQL;
  • определенным пользователями типам.

В совокупности в SQL:1999 может поддерживаться девять видов защиты разных объектов в соответствии со следующими возможными действиями (см. табл.18.1).

При разработке средств контроля доступа к объектам баз данных создатели SQL придерживались принципа сокрытия информации об объектах, содержащихся в схеме базы данных, от пользователей, которые лишены доступа к этим объектам. Другими словами, если некоторый пользователь не обладает, например, привилегией на просмотр таблицы PRO, то при выполнении операции SELECT * FROM PRO он получит такое же диагностическое сообщение, как если бы таблица PRO не существовала. Если бы в случае отсутствия этой таблицы и в случае отсутствия привилегии доступа выдавались разные диагностические сообщения, то непривилегированный пользователь получил бы данные о том, что интересующая его таблица существует, но он лишен доступа к ней.

Таблица 18.1.
Вид защиты и соответствующее действиеНазвание привилегииПрименимо к следующим объектам
ПросмотрSELECTТаблицы, столбцы, подпрограммы, вызываемые из SQL
ВставкаINSERTТаблицы, столбцы
МодификацияUPDATEТаблицы, столбцы
УдалениеDELETEТаблицы
СсылкаREFERENCESТаблицы, столбцы
ИспользованиеUSAGEДомены, определенные пользователями типы, наборы символов, порядки сортировки символов, преобразования
ИнициированиеTRIGGERТаблицы
ВыполнениеEXECUTEПодпрограммы, вызываемые из SQL
ПодтипизацияUNDERСтруктурные типы

В лекции 12 мы бегло упоминали, что в SQL-ориентированной системе каждому зарегистрированному в системе пользователю соответствует его уникальный идентификатор (в стандарте используется термин идентификатор авторизации, authorization identifier - authID). Как мы отмечали, в стандарте SQL:1999 не зафиксированы точные правила представления идентификатора пользователя, хотя обычно в реализациях SQL ниладическая функция CURRENT USER выдает текстовую строку, содержащую регистрационное имя пользователя, как оно сохраняется в файлах соответствующей операционной системы (ОС). Привилегии доступа к объектам базы данных могут предоставляться пользователям, представляемым своими идентификаторами, а также ролям2) (см. следующий подраздел), выполнение которых, в свою очередь, может предоставляться пользователям. Кроме того, в SQL поддерживается концепция псевдоидентификатора (или идентификатора псевдо) пользователя PUBLIC, который соответствует любому приложению или пользователю, зарегистрированному в системе баз данных. "Пользователю" PUBLIC могут предоставляться привилегии доступа к объектам базы данных, как и любому другому пользователю.

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

Во многих реализациях поддерживаются привилегии уровня DBA (DataBase Administrator) для возможности выполнения операций DDL - Data Definition Language (CREATE, ALTER и DROP над объектами, входящими в схему базы данных). В стандарте SQL требуется лишь соблюдение следующих правил.

  • Любые пользователь или его роль могут выполнять любые операции DDL внутри схемы, которой владеют3).
  • Не допускается выполнение каких-либо операций DDL внутри схемы, которой не владеет пользователь или роль, пытающиеся выполнить соответствующую операцию.
  • Эти правила не допускают исключений.

Пользователи и роли

Как говорилось в начале этого раздела, любой пользователь характеризуется своим идентификатором авторизации. В стандарте ничего не говорится о том, что authID должен быть идентичен регистрационному имени пользователя в смысле операционной системы. Согласно стандарту SQL:1999 authID строится по тем же правилам, что и любой другой идентификатор, и может включать до 128 символов. Тем не менее во многих реализациях SQL, выполненных в среде ОС семейства UNIX, длина authID составляет не более восьми символов, как это свойственно ограничениям на длину регистрационного имени в этих ОС.

В стандарте языка SQL не специфицированы средства создания идентификаторов авторизации. Если говорить более точно, в стандарте не определяется какой-либо явный способ создания допустимых идентификаторов пользователей. Идентификатор авторизации может являться либо идентификатором пользователя, либо именем роли, а для создания ролей в SQL поддерживаются соответствующие средства (см. ниже). Но в соответствии с правилами стандарта SQL, все authID должны отслеживаться СУБД (имеются в виду все authID, для которых существует хотя бы одна привилегия). И в стандарте поддерживаются точные правила порождения и распространения привилегий. Привилегии по отношению к объекту базы данных предоставляются системой владельцу схемы при создании объекта в этой схеме, и привилегии могут явно передаваться от имени одного authID другому authID при наличии у первого authID привилегии на передачу привилегий.

Итак, authID может являться либо идентификатором пользователя, либо идентификатором роли. Попробуем разобраться в сути термина роль. При работе с большими базами данных в крупных организациях часто сотни служащих производят над базой данных одни и те же операции. Конечно, для этого каждый из служащих должен быть зарегистрированным пользователем соответствующей системы баз данных и тем самым обладать собственным authID. Используя базовые средства авторизации доступа (зафиксированные в стандарте SQL/92), можно предоставить каждому пользователю группы одни и те же привилегии доступа к требуемым объектам базы данных. Но схема авторизации доступа при этом становится очень сложной4). В некотором смысле имя роли идентифицирует динамически образуемую группу пользователей системы баз данных, каждый из которых обладает, во-первых, привилегией на исполнение данной роли и, во-вторых, всеми привилегиями данной роли для доступа к объектам базы данных. Другими словами, наличие ролей упрощает построение и администрирование системы авторизации доступа. Проиллюстрируем это на рис.18.1.

Каждая стрелка на рис.18.1 соответствует мандату доступа (паре <authID, набор_привилегий_доступа_к_объекту_БД>), который требуется сохранять в каталоге базы данных и проверять при попытке доступа от имени authID. Как видно, в случае (a) требуется сохранение и проверка n*m мандатов, где n - число пользователей в группе, а m - число объектов базы данных, для которых пользователи группы должны иметь одни и те же привилегии. В случае (b) число требуемых для корректной работы мандатов равно лишь n+m, и схема авторизации резко упрощается.

Группы пользователей, объединенных одной ролью, являются динамическими, поскольку в SQL поддерживаются возможности предоставления пользователю привилегии на исполнение данной роли и лишения пользователя этой привилегии (см. ниже в этом разделе). Более того, имеются возможности предоставления заданной роли 1 всех или части привилегий другой роли 1. Естественно, что при этом привилегии изменяются у всех пользователей, которые могут исполнять роль 1.

Привилегии, пользователи и роли
Рис. 18.1.  Привилегии, пользователи и роли

Более того, имеются возможности предоставления заданной роли A всех или части привилегий другой роли B. Естественно, что при этом привилегии изменяются у всех пользователей, которые могут исполнять роль A.

Применение идентификаторов пользователей и имен ролей

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

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

В стандарте не специфицированы все способы ассоциирования authID с SQL-сессией. Определено лишь то, что если сессия образуется с помощью оператора CONNECT (см. раздел "Подключения и сессии"), то authID указывается в качестве параметра соответствующей операции. Для реализаций SQL допускается, чтобы пользовательский идентификатор SQL-сессии совпадал с регистрационным именем пользователя с точки зрения операционной системы или являлся идентификатором, специально устанавливаемым специалистами организации, ответственными за обеспечение безопасности. Кроме того, допускается наличие в реализации оператора SET SESSION AUTHORIZATION, применение которого приводит к смене пользовательского идентификатора SQL-сессии. В начале SQL-сессии значение текущего идентификатора пользователя SQL-сессии совпадает со значением пользовательского идентификатора SQL-сессии, и такая ситуация сохраняется до тех пор, пока пользовательский идентификатор SQL-сессии не будет каким-либо образом изменен. Значение текущего пользовательского идентификатора SQL-сессии возвращается вызовом ниладической функции SESSION_USER (лекция 13).

Для каждой SQL-сессии существует также текущее имя роли (это имя можно получить путем вызова функции CURRENT_ROLE). Сразу после образования сессии текущему имени роли соответствует неопределенное значение, что трактуется как "роль для сессии не назначена". Имеется несколько способов подмены пользовательского идентификатора и/или имени роли SQL-сессии. При этом если задается идентификатор пользователя, то одновременно полагается, что неявно указывается имя роли, имеющее неопределенное значение. Если же задается имя роли, то, за несколькими исключениями, считается, что неявно указывается идентификатор пользователя, имеющий неопределенное значение. Более подробно мы обсудим подобные возможности ниже в этом разделе.

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

Как и везде в этом курсе, мы опустим детали, относящиеся к особенностям авторизации при использовании встраиваемого и динамического SQL.

Создание и ликвидация ролей

Для создания новой роли используется оператор CREATE ROLE, определяемый следующим синтаксическим правилом:

CREATE ROLE role_name
   [ WITH ADMIN { CURRENT_USER | CURRENT_ROLE } ]

Имя создаваемой роли должно отличаться от любого идентификатора авторизации, уже определенного и сохраненного в базе данных. В случае успешного создания роли некоторый authID получает привилегию на исполнение данной роли. Если в операторе CREATE ROLE не содержится раздел WITH ADMIN, то привилегию на исполнение роли получает текущий идентификатор пользователя SQL-сессии, если значение этого идентификатора отлично от NULL; иначе привилегия на исполнение роли дается текущему имени роли сессии.

Если в состав оператора включается раздел WITH ADMIN, то можно выбрать, будет ли являться владельцем роли authID, соответствующий текущему идентификатору пользователя SQL-сессии, или authID, соответствующий текущему имени роли (при условии, что соответствующие текущий идентификатор или текущее имя не содержат NULL). Кроме того, включение этого раздела означает, что authID-владелец роли получает право на передачу привилегии исполнения данной роли другим authID.

В соответствии со стандартом SQL:1999, привилегии, требуемые для выполнения оператора CREATE ROLE, определяются в реализациях SQL. Например, в некоторых реализациях выполнение этой операции разрешается только администратору базы данных.

Существующую роль можно ликвидировать с помощью оператора

DROP ROLE role_name

Для выполнения этой операции требуется, чтобы текущий authID SQL-сессии прямо или косвенно (через цепочку ролей) являлся владельцем ликвидируемой роли. При ликвидации роли, прежде всего, изымается привилегия на ее исполнение у всех authID, которым данная привилегия была ранее передана.

Передача привилегий и ролей

Для передачи привилегий и ролей от одних authID другим поддерживается оператор GRANT, который мы обсудим отдельно для случаев передачи привилегий и передачи ролей.

Передача привилегий

В случае передачи привилегий используется следующий синтаксис оператора GRANT:

GRANT { ALL PRIVILEGES | privilege_commalist }
   ON privilege_object
TO { PUBLIC | authID_commalist } [ WITH GRANT OPTION ]
   [ GRANTED BY { CURRENT_USER | CURRENT_ROLE } ]
privilege ::= SELECT [ column_name_commalist ]
           | DELETE
           | INSERT [ column_name_commalist ]
           | UPDATE [ column_name_commalist ]
           | REFERENCES [ column_name_commalist ]
           | USAGE
           | TRIGGER
           | EXECUTE
privilege_object ::= [ TABLE ] table_name
                     | DOMAIN domain_name
                     | CHARACTER SET character_set_name
                     | COLLATION collation_name
                     | TRANSLATION translation_name

Поскольку authID может являться идентификатором пользователя или именем роли, привилегии могут передаваться от пользователей пользователям, от пользователей ролям, от ролей ролям и от ролей пользователям.

В списке привилегий можно использовать SELECT, DELETE, INSERT, UPDATE, REFERENCES и TRIGGER только в том случае, когда в качестве объекта привилегий указывается таблица. Соответственно, список привилегий может состоять из единственной привилегии USAGE только в том случае, когда объектом является домен, набор символов, порядок сортировки или трансляция. Если в списке привилегий указывается более одной привилегии, то они все передаются указанным authID, но для этого текущий authID SQL-сессии должен обладать привилегией на передачу привилегий.

Использование ключевого слова ALL PRIVILEGES вместо явного задания списка привилегий означает, что передаются все привилегии доступа к соответствующему объекту базы данных, которыми обладает текущий authID SQL-сессии.

Как показывает синтаксис, один оператор GRANT позволяет передавать привилегии доступа только к одному объекту, но в том случае, когда объектом является таблица, разные привилегии могут передаваться по отношению к одному и тому же набору столбцов или к разным наборам. Если при указании привилегий SELECT, DELETE, UPDATE и REFERENCES список имен столбцов не задается, передаются привилегии по отношению ко всем столбцам таблицы. Заметим, что эти привилегии касаются всех существующих столбов данной таблицы, а также всех столбцов, которые когда-либо будут к ней добавлены.

Включение в оператор необязательного раздела WITH GRANT OPTION означает, что получателям передаваемых привилегий дается также привилегия на дальнейшую передачу полученных привилегий, включая привилегию на передачу привилегий. Включение в оператор раздела GRANTED BY позволяет явно указать, передаются ли привилегии от имени текущего идентификатора пользователя или же текущего имени роли.

При проверке возможности выполнения операции в SQL-сессии учитываются привилегии текущего authID SQL-сессии, а также привилегии всех ролей, которые переданы данному authID. Поскольку этим ролям могли быть переданы другие роли, обладающие собственными привилегиями, анализ возможности выполнения операции является рекурсивной процедурой.

Если одна и та же привилегия передается более одного раза одному и тому же authID2 от имени одного и того же authID1, то возникает ситуация, называемая избыточной дублирующей привилегией. Эта ситуация не вызывает дополнительных проблем, поскольку избыточная передача привилегии игнорируется. Для аннулирования данной привилегии у authID2 от имени authID2 требуется выполнение всего лишь одной операции REVOKE (см. ниже в этом разделе). Если привилегия была один раз передана authID2 от имени authID1 вместе с привилегией на передачу этой привилегии (WITH GRANT OPTION), а в другой раз - без этой опции (порядок действий не является существенным), то authID2 обладает данной привилегией и привилегией на ее передачу.

Если предпринимается попытка передачи нескольких привилегий, но соответствующий authID не обладает ни одной из них, то фиксируется ошибка. Аналогично, если производится попытка передачи нескольких привилегий с передачей привилегии на передачу привилегий, но соответствующий authID не обладает привилегией WITH GRANT OPTION ни для одной из передаваемых привилегий, то фиксируется ошибка. Наконец, если производится попытка передачи нескольких привилегий с передачей привилегии на передачу привилегий и соответствующий authID обладает привилегией на передачу только части этих привилегий, то в результате выполнения операции вырабатывается предупреждение, но соответствующая часть привилегий передается с привилегией WITH GRANT OPTION.

Привилегии и представления

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

Например, чтобы операция создания представления была выполнена успешно, текущий authID должен обладать привилегией SELECT по отношению ко всем базовым таблицам и представлениям, на которых основывается новое представление. Тогда текущий authID автоматически получит привилегию SELECT для нового представления. Но текущий authID сможет передавать эту привилегию другим authID только тогда, когда обладает соответствующей привилегией для всех базовых таблиц и представлений, на которых основывается новое представление. Аналогичным образом на представление распространяются привилегии DELETE, INSERT, UPDATE и REFERENCES. Поскольку триггеры над представлениями создавать не разрешается, привилегия TRIGGER представлениям не передается.

Наконец, посмотрим, что происходит при смене привилегий владельца представления по отношению к таблицам, на которых основано это представление. Для простоты предположим, что представление V основано на базовой таблице T. Если во время создания V текущий authID (будущий владелец представления) обладал по отношению к T привилегиями SELECT и INSERT, то он будет обладать этими привилегиями и по отношению к V6). Если впоследствии владелец представления получит по отношению к T дополнительные привилегии, то он (и все authID, которым передавались все привилегии - ALL PRIVILEGES для V) получит те же привилегии для V. Должно быть понятно, каким образом обобщается этот подход на случай, когда представление определяется над несколькими таблицами или представлениями.

Передача ролей

Для передачи ролей используется следующий вариант оператора GRANT:

GRANT role_name_commalist
TO { PUBLIC | authID_commalist } [ WITH ADMIN OPTION ]
   [ GRANTED BY { CURRENT_USER | CURRENT_ROLE } ]

Как показывает синтаксис, оператор позволяет передавать произвольное число ролей произвольному числу authID (которые могут представлять собой идентификаторы пользователей или имена ролей). Как и в случае передачи привилегий, от данного authID можно передавать только те роли, которые были получены этим authID с привилегией на дальнейшую передачу (WITH ADMIN OPTION). При включении в состав оператора GRANT раздела GRANTED BY можно явно указать, что роли передаются от имени текущего идентификатора пользователя или же текущего имени роли.

Изменение текущих идентификаторов пользователей и имен ролей

Как мы отмечали ранее в этом разделе, в SQL:1999 специфицированы некоторые операторы, позволяющие изменять текущий идентификатор пользователя и текущее имя роли SQL-сессии.

Оператор SET SESSION AUTHORIZATION

Для изменения текущего идентификатора пользователя SQL-сессии может использоваться оператор

SET SESSION AUTHORIZATION value_specification

Как указывалось в лекции 13, value_specification может быть либо литералом (в данном случае литералом типа символьных строк), либо вызовом ниладической функции, такой, как CURRENT_USER, SESSION_USER и т. д. Если указанная спецификация значения не соответствует требованиям, предъявляемым в реализации к представлению идентификатора пользователя, операция изменения текущего идентификатора пользователя аварийно завершается.

В стандарте также говорится, что если спецификация значения, заданная в операции, формально соответствует требованиям, предъявляемым к формату идентификатора пользователя конкретной системы, но в действительности не представляет известный системе идентификатор пользователя, то опять же фиксируется ошибка, и операция не выполняется. Допускается, чтобы в реализации принималось решение о смене идентификатора пользователя сессии одновременно с регистрацией нового идентификатора пользователя. Ограничения на регистрацию таким способом нового пользователя тоже определяются на уровне реализации. После успешного выполнения оператора SET SESSION AUTHORIZATION текущее имя роли соответствующей сессии принимает значение NULL, так что текущим authID этой сессии становится заданное значение идентификатора пользователя.

Опять по необходимости забегая вперед, заметим, что операцию смены текущего идентификатора пользователя SQL-сессии не разрешается выполнять внутри какой-либо транзакции этой сессии. Иначе терялся бы смысл привилегий доступа, которыми руководствуется система при выполнении операций внутри транзакции.

Оператор SET ROLE

Для смены текущего имени роли SQL-сессии можно использовать оператор

SET ROLE { value_specification | NONE }

Ограничения на выполнение операции SET ROLE почти совпадают с определенными в стандарте ограничениями на выполнение операции SET SESSION AUTHORIZATION. Наиболее важные отличия состоят в том, что эту операцию от имени текущего authID сессии всегда разрешается выполнять для ролей, которые переданы "пользователю" PUBLIC или данному текущему authID, а также в том, что всегда разрешается применение конструкции SET ROLE NONE. Выполнение последней конструкции приводит к тому, что значение текущего имени роли сессии становится неопределенным.

Заметим, что при смене текущего имени роли SQL-сессии значение текущего пользовательского идентификатора сессии не меняется, так что вполне вероятно, что после выполнения операции и текущий идентификатор, и текущее имя роли будут иметь значения, отличные от неопределенного значения. И конечно, операция SET ROLE NONE будет выполнена успешно только в том случае, когда значение текущего пользовательского идентификатора не является неопределенным7).

Аннулирование привилегий и ролей

Если от имени некоторого authID некоторые привилегии или роли были переданы одному или нескольким другим authID, то впоследствии первый authID (в сессии, где этот authID является текущим) можно изъять, или аннулировать, переданные привилегии или роли путем применения оператора REVOKE. Как и в случае передачи привилегий и ролей, способы аннулирования привилегий и ролей похожи, но между ними имеются важные отличия. Поэтому мы снова обсудим эти способы в отдельности.

Аннулирование привилегий

Для аннулирования привилегий используется оператор REVOKE, определяемый следующим синтаксическим правилом:

REVOKE [ GRANT OPTION FOR] privilege_commalist
   ON privilege_object
FROM { PUBLIC | authID_commalist }
   [ GRANTED BY { CURRENT_USER | CURRENT_ROLE } ]
   { RESTRICT | CASCADE }

Синтаксис конструкций privilege и privilege_object такой же, как для оператора GRANT. Общий смысл операции должен быть понятен из синтаксиса: у указанных authID аннулируются указанные привилегии доступа к указанному объекту базы данных.

Первой важной особенностью оператора аннулирования привилегий является обязательность указания одного из ключевых слов RESTRICT или CASCADE. Если в операторе содержится RESTRICT, то при выполнении операции система проверит, не передавалась ли какая-либо из указанных привилегий каким-либо authID от того authID, у которого привилегия должна быть аннулирована (это вполне возможно, если ранее привилегия была передана с правом передачи). Если это действительно так, операция не выполняется; в противном случае указанные привилегии у указанных authID аннулируются. Иначе говоря, при наличии ключевого слова RESTRICT не допускается, например, ситуация, показанная на рис.18.2.

Передача полученной привилегии
Рис. 18.2.  Передача полученной привилегии

На этом рисунке authID1 является владельцем объекта базы данных с именем object и, следовательно, обладает всеми привилегиями над этим объектом. Пунктирной стрелкой обозначена одна из подобных привилегий pr1. От имени authID1 привилегия pr1 была передана authID2 вместе с привилегией на ее дальнейшую передачу. Наконец, от имени authID2 привилегия pr1 была передана authID3. Тогда операция аннулирования этой привилегии от имени authID1 у authID2 при наличии ключевого слова RESTRICT не будет выполнена успешно.

В той же ситуации привилегия была бы аннулирована для authID2 (и для authID3), если бы в операторе GRANT присутствовало ключевое слово CASCADE. В общем случае если выполняется операция REVOKE ... CASCADE, то указанные привилегии аннулируются у всех authID, прямо или косвенно (через промежуточные authID) получивших привилегии от текущего authID SQL-сессии, в которой выполняется данная операция.

Если в операторе содержится раздел GRANT OPTION FOR, но имеется ключевое слово RESTRICT, то указанные привилегии для указанных authID не аннулируются, но у указанных authID аннулируется привилегия передачи данных привилегий (операция должна успешно выполняться только при соблюдении обсуждавшихся ранее условий). Однако если в операторе одновременно содержатся и GRANT OPTION FOR, и CASCADE, то указанные привилегии аннулируются у всех authID, которые прямо или косвенно (через промежуточные authID) получили привилегии от текущего authID SQL-сессии, в которой выполняется данная операция.

Задание в операторе необязательного раздела GRANTED BY позволяет явно указать, что должно использоваться в качестве текущего authID - текущий пользовательский идентификатор или текущее имя роли SQL-сессии. Если раздел GRANTED BY в операторе REVOKE не содержится, то действия производятся от имени текущего authID SQL-сессии (о том, как он определяется, см. выше).

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

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

Косвенная и прямая передача привилегий
Рис. 18.3.  Косвенная и прямая передача привилегий

Здесь привилегия pr1 передана от authID1 к authID2 вместе с правом на дальнейшую передачу этой привилегии. Далее, привилегия pr1 передается от authID2 к authID3. И затем выполняется прямая передача привилегии от authID1 к authID3 (на самом деле, порядок таких действий не является существенным). Теперь предположим, что от имени authID1 выполняется операция

REVOKE pr1 ON object FROM authID2 CASCADED

В соответствии с правилами SQL:1999 после выполнения этой операции authID3 будет продолжать владеть привилегией pr1 по отношению к объекту object, поскольку получил данную привилегию двумя разными способами. Грубо говоря, операция REVOKE, выполняемая от имени authID1, выполняется только по тем путям графа идентификаторов авторизации и объектов базы данных, которые начинаются с узлов, соответствующих authID, указанных в разделе FROM этой операции.

Далее, напомним, что если при передаче от authID1 к authID2 привилегии на выполнение некоторых действий над некоторой таблицей T (например, UPDATE) явно не указывается список имен столбцов этой таблицы, то привилегия распространяется на все столбцы этой таблицы (включая столбцы, которые, возможно, еще будут созданы). Если действительно использовался такой способ передачи привилегий, то в дальнейшем можно аннулировать привилегию authID2 на модификацию отдельных (уже определенных) столбцов таблицы T, оставив привилегию на модификацию всех остальных столбцов (включая те, которые еще не созданы).

И последнее замечание. Если некоторая привилегия была передана псевдоauthID PUBLIC, то, конечно, этой привилегией обладают все authID. Но нет возможности аннулировать такую привилегию у отдельно указываемого authID. Привилегия была передана всем, и аннулировать ее можно только сразу у всех.

Аннулирование ролей

Вариант оператора REVOKE, используемый для аннулирования ролей, выглядит следующим образом:

REVOKE [ ADMIN OPTION FOR ] role_name_commalist
FROM { PUBLIC | authID_commalist }
   [ GRANTED BY { CURRENT_USER | CURRENT_ROLE } ]
   { RESTRICT | CASCADE }

Действие операции аннулирования ролей очень похоже на действие операции аннулирования привилегий. Отличие состоит в том, что аннулируются не привилегии, а роли, а также в том, что для аннулирования привилегии на передачу роли используется раздел ADMIN OPTION FOR8).

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

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

ACID-транзакция

В SQL:1999 поддерживается классическое понимание транзакции, характеризуемое аббревиатурой ACID (от Atomicy, Consistency, Isolation и Durability). В соответствии с этим понятием под транзакцией разумеется последовательность операций (например, над базой данных), обладающая следующими свойствами.

  • Атомарность (Atomicy). Это свойство означает, что результаты всех операций, успешно выполненных в пределах транзакции, должны быть отражены в состоянии базы данных, либо в состоянии базы данных не должно быть отражено действие ни одной операции (конечно, здесь речь идет об операциях, изменяющих состояние базы данных). Свойство атомарности, которое часто называют свойством "все или ничего", позволяет относиться к транзакции, как к динамически образуемой составной операции над базой данных9).
  • Согласованность (Consistency). В классическом смысле это свойство означает, что транзакция может быть успешно завершена с фиксацией результатов своих операций только в том случае, когда действия операций не нарушают целостность базы данных, т. е. удовлетворяют набору ограничений целостности, определенных для этой базы данных. В стандарте SQL:1999 это свойство расширяется тем, что во время выполнения транзакции разрешается устанавливать точки согласованности (см. ниже про точки сохранения) и явным образом проверять ограничения целостности10).
  • Изоляция (Isolation). Требуется, чтобы две одновременно выполняемые транзакции11) никоим образом не действовали одна на другую. Другими словами, результаты выполнения операций транзакции T1 не должны быть видны никакой другой транзакции T2 до тех пор, пока транзакция T1 не завершится успешным образом.
  • Долговечность (Durability). После успешного завершения транзакции все изменения, которые были внесены в состояние базы данных операциями этой транзакции, должны гарантированно сохраняться, даже в случае сбоев аппаратуры или программного обеспечения.

Порождение транзакций в SQL:1999

В соответствии со стандартом языка SQL:1999 транзакции12) могут образовываться явным образом с использованием оператора START TRANSACTION, либо неявно, когда выполняется оператор, для которого требуется контекст транзакции, а этого контекста не существует. Например, операторы SELECT, UPDATE или CREATE TABLE могут выполняться только в контексте транзакции, а для выполнения оператора CONNECT (см. раздел "Подключения и сессии") такой контекст не требуется, и выполнение оператора CONNECT не приводит к неявному образованию транзакции. Для завершения транзакции должен быть явно использован один из двух операторов - COMMIT (требование завершить транзакцию с фиксацией ее результатов) или ROLLBACK (требование завершить транзакцию с удалением результатов всех выполненных операций из состояния базы данных13)).

Установка характеристик транзакции

У каждой выполняемой транзакции имеются три характеристики, значения которых существенно влияют на действия системы при управлении транзакцией, - уровень изоляции (isolation level), режим доступа (access mode) и размер области диагностики. При неявном образовании транзакции эти характеристики устанавливаются по умолчанию: транзакция получает максимальный уровень изоляции от одновременно выполняемых транзакций; режим доступа, позволяющий выполнять и операции выборки, и операции обновления базы данных; и назначаемый по умолчанию размер области диагностики.

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

SET TRANSACTION mode_commalist
mode ::= isolation_level
        | access_mode
        | diagnostics_size
isolation_level ::= READ UNCOMMITED
                   | READ COMITTED
                   | REPEATABLE READ
                   | SERIALIZABLE
access_mode ::= READ ONLY
             | READ WRITE
diagnostics_size ::= DIAGNOSTIC SIZE value_specification

Операцию установки характеристик транзакции нельзя выполнять в контексте какой-либо активной транзакции. Выполнение операции допустимо только до образования первой транзакции SQL-сессии или между последовательно выполняемыми транзакциями этой сессии. В одном операторе SET TRANSACTION можно задать только по одному значению каждой из трех характеристик, но допускается последовательное выполнение нескольких таких операций с разными операндами.

Как видно из синтаксических правил, у характеристики режим доступа может быть указано одно из двух значений - READ ONLY или READ WRITE. Если устанавливается режим READ ONLY, то в транзакции нельзя будет выполнять никакие операции, изменяющие базу данных, в том числе операции обновления таблиц и определения новых объектов базы данных. Если режим доступа явно не указывается, по умолчанию принимается характеристика READ WRITE, если только в качестве значения характеристики уровень изоляции не указывается READ UNCOMITTED (в этом случае устанавливается режим доступа READ ONLY).

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

Уровни изоляции будут подробно обсуждаться ниже, но здесь мы заметим, что если значение уровня изоляции явно не задано, то по умолчанию принимается уровень изоляции SERIALIZABLE. Кроме того, обратим внимание читателей, что одновременное задание уровня изоляции READ UNCOMITTED и режима доступа READ WRITE не допускается.

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

Явная инициация транзакции

Для явного образования транзакции поддерживается оператор START TRANSACTION, определяемый следующими синтаксическими правилами:

START TRANSACTION mode_commalist

Этот оператор очень похож на SET TRANSACTION. Единственное (хотя и очень существенное) отличие состоит в том, что выполнение оператора START TRANSACTION приводит не только к установке характеристик транзакции, но и к реальной инициации транзакции.

Уровни изоляции SQL-транзакции

В стандарте SQL:1999 уровни изоляции определяются на основе нескольких феноменов, которые могут возникать при выполнении транзакций15).

Феномен "грязного" чтения (dirty read)

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

Феномен "грязного" чтения
Рис. 18.4.  Феномен "грязного" чтения

На этом рисунке показано, что в момент времени t0 были образованы две транзакции T1 и T2. В момент времени t1 транзакция T1 успешно выполняет операцию модификации некоторого объекта базы данных O. В момент времени t2 (t2>t1) транзакция T2 читает объект O, после чего успешно завершается в момент времени t3. Транзакция же T1 завершается в момент времени t4 (t4>t3), причем в ней выполняется оператор ROLLBACK, что приводит к ликвидации в базе данных последствий изменения объекта O. В результате оказывается, что в транзакции T2 обрабатывались данные, которые реально не существуют в базе данных (отсюда и термин "грязные" данные).

В SQL феномен "грязного" чтения может наблюдаться у транзакций, выполняемых на уровне изоляции READ UNCOMMITTED. Рекомендуется использовать этот уровень изоляции только в тех транзакциях, для выполнения функций которых точные данные не обязательны (например, в транзакциях, производящих статистическую обработку).

Феномен неповторяемого чтения (unrepeatable read)

Этому феномену подвержены транзакции, читающие некоторые объекты базы данных и допускающие изменения уже прочитанных объектов другими транзакциями. Пример феномена неповторяемого чтения показан на рис.18.5.

Феномен неповторяемого чтения
Рис. 18.5.  Феномен неповторяемого чтения

На этом рисунке показано, что в момент времени t0 были образованы две транзакции T1 и T2. В момент времени t1 транзакция T2 выполняет операцию чтения некоторого объекта базы данных O (например, производит выборку строки из таблицы с указанием значения первичного ключа). В момент времени t2 (t2>t1) транзакция T1 изменяет объект O (модифицирует или даже удаляет). В момент времени t3 (t3>t2) транзакция T2 повторно считывает объект O и обнаруживает, что он изменился или вовсе отсутствует. Другими словами, в транзакции T2 повторное выполнение выборки объекта базы данных O дало результат, отличный от результата первого выполнения (отсюда и происходит термин "неповторяемое чтение").

В SQL феномен неповторяемого чтения может наблюдаться у транзакций, выполняемых на уровне изоляции READ COMMITTED (этот уровень изоляции, как показывает его название, гарантирует отсутствие феномена "грязного" чтения).

Феномен фантомов

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

Феномен фантомов
Рис. 18.6.  Феномен фантомов

На этом рисунке показано, что в момент времени t0 были образованы две транзакции, T1 и T2. В момент времени t1 транзакция T2 выполняет операцию выборки строк из таблицы R по условию c. В момент времени t2 (t2>t1) транзакция T1 выполняет над таблицей R операцию обновления (вставки или модификации строк), в результате которой в таблице R появляются дополнительные строки, удовлетворяющие условию c. В момент времени t3 (t3>t2) транзакция T2 повторно выполняет операцию выборки строк из таблицы R по условию c и обнаруживает наличие в результате дополнительных фантомных строк.

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

Наконец, для транзакций, выполняемых на уровне изоляции SERIALIZABLE, невозможно и проявление феномена фантомов. Термин serializable (сериализуемый) используется по той причине, что при работе на данном уровне изоляции суммарный эффект выполнения набора транзакций {T1, T2, ... , Tn} идентичен эффекту некоторого последовательного выполнения этих транзакций. Это означает предельную изолированность транзакций. Общая картина взаимосвязи уровней изоляции и феноменов транзакций показана в таблице 18.2.

Таблица 18.2. Уровни изоляции и феномены
Уровень"Грязное" чтениеНеповторяемое чтениеФантомы
READ UNCOMMITTEDВозможноВозможноВозможны
READ COMMITTEDНевозможноВозможноВозможны
REPEATABLE READНевозможноНевозможноВозможны
SERIALIZABLEНевозможноНевозможноНевозможны

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

Как мы отмечали в начале этого раздела, транзакции могут инициироваться как явным способом (с помощью оператора START TRANSACTION), так и неявно, при выполнении первого оператора, требующего наличия контекста транзакции. Для завершения транзакции всегда16) требуется выполнение одного из двух операторов COMMIT (фиксация транзакции) или ROLLBACK (откат транзакции), которые имеют следующий синтаксис:

COMMIT [ WORK ] [ AND [ NO ] CHAIN ]
ROLLBACK [ WORK ] [ AND [ NO ] CHAIN ]
[ TO SAVEPOINT savepoint_name ]

При желании завершить транзакцию таким образом, чтобы все произведенные ею изменения были навсегда сохранены в базе данных, следует завершать транзакцию оператором COMMIT (как видно из синтаксиса, допускается эквивалентный вид COMMIT WORK). Если требуется завершить транзакцию с аннулированием всех произведенных изменений, то нужно использовать оператор ROLLBACK (ROLLBACK WORK).

Заметим, что и операция фиксации транзакции, и операция отката являются достаточно сложными и выполняются не мгновенно. Поэтому в ходе выполнения этих операций, вообще говоря, может произойти аварийный отказ системы. Естественно (хотя в этом курсе мы не обсуждаем технические детали возможных реализаций), база данных будет восстановлена в свое последнее согласованное состояние, но ситуации прерванного выполнения операции фиксации и операции отката коренным образом различаются. Оператор COMMITсчитается безусловно выполненным только в том случае, когда сервер баз данных подтвердил это после выполнения всех действий, требуемых для фиксации транзакции. Аварийная ситуация во время выполнения операции ROLLBACK ничем не отличается от аварийной ситуации, возникшей в процессе выполнения транзакции. В этом случае (при восстановлении базы данных) прерванная транзакция считается незафиксированной (что так и есть), и все ее изменения автоматически удаляются из состояния базы данных. Поэтому окончательный результат выполнения операции фиксации транзакции, прерванной аварийным отказом системы, эквивалентен успешному выполнению операции отката транзакции17).

Синтаксис обоих операторов показывает, что в каждом из них может содержаться раздел AND [ NO ] CHAIN. Постараемся кратко пояснить смысл этого раздела (не вдаваясь в детали, поскольку стандарт SQL:1999 оставляет окончательное решение за реализацией).

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

Поэтому часто используется компромиссный вариант, при котором действия операторов COMMIT или ROLLBACK приводят не только к завершению текущей транзакции, но и к образованию новой транзакции18). Именно эту возможность поддерживает раздел AND [ NO ] CHAIN операторов COMMIT и ROLLBACK. Если такой раздел отсутствует в операторе завершения транзакции, то подразумевается наличие раздела AND NO CHAIN, и новая транзакция не образуется. Если же раздел AND CHAIN присутствует, то немедленно после завершения выполнения COMMIT или ROLLBACK текущей транзакции образуется новая транзакция, наследующая все характеристики завершенной транзакции.

Семантику раздела TO SAVEPOINT мы поясним немного позже.

Транзакции и ограничения целостности

Материал этого подраздела уже излагался в лекции 13, но там это делалось в контексте определений ограничений целостности. Для полноты картины мы воспроизведем часть этого материала в контексте управления транзакциями.

Итак, любое ограничение целостности обладает атрибутом, определяющим время проверки данного ограничения. Этот атрибут может иметь значения DEFERRABLE (отложенная проверка) или NOT DEFERRABLE (немедленная проверка). Чтобы данное ограничение целостности могло когда-либо обладать свойством отложенной проверки, нужно, чтобы в определении такого ограничения присутствовали ключевые слова INITIALLY DEFERRED или INITIALLY IMMEDIATE. В любом случае, в каждый момент времени выполнения транзакции любое ограничение целостности находится в одном из двух состояний - отложенная проверка или немедленная проверка. Если начальным состоянием ограничения является INITIALLY DEFERRED, то в начале любой транзакции его текущим состоянием будет отложенная проверка. Аналогично для ограничений с начальным состоянием INITIALLY IMMEDIATE.

Любое ограничение, находящееся в состоянии немедленной проверки, всегда проверяется в конце выполнения любого оператора SQL19). Немедленно проверяются и те ограничения, которые были определены как NOT DEFERRABLE, но для которых впоследствии был установлен режим немедленной проверки. Однако если текущим состоянием ограничения является отложенная проверка, оно будет проверяться только тогда, когда перейдет в состояние немедленной проверки. Это делается неявно при выполнении оператора COMMIT или явно при выполнении оператора SET CONSTRAINTS. Этот оператор имеет следующий синтаксис:

SET CONSTRAINTS { ALL | constraint_name_commalist}
   { DEFERRED | IMMEDIATE }

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

При попытке фиксации транзакции, для которой имеются одно или несколько ограничений целостности, текущим режимом которых является отложенная проверка, система (ненадолго, поскольку транзакция скоро тем или иным способом завершится) устанавливает для всех этих ограничений режим немедленной проверки и проверяет ограничения. Если какое-либо из ограничений нарушается, то операция COMMIT трактуется как операция ROLLBACK, и пользователю (или приложению) сообщается, что возникла ошибка. Избежать этой неприятной ситуации можно явным выполнением оператора SET CONSTRAINTS ALL IMMEDIATE до фиксации транзакции, для которой имеются DEFERRABLE ограничения, текущим режимом которых является отложенная проверка.

Точки сохранения

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

Частичное решение этой проблемы предоставляет механизм точек сохранения (savepoint) SQL:1999. Точка сохранения представляет собой своего рода пометку в последовательности операций транзакции, которую в дальнейшем можно использовать для частичного отката транзакции с сохранением жизнеспособности транзакции и результатов операций, выполненных в транзакции до точки сохранения. Пример использования точки сохранения показан на рис.18.7.

Пример транзакции с точкой сохранения
Рис. 18.7.  Пример транзакции с точкой сохранения

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

Допускается установка в одной транзакции нескольких последовательных точек сохранения. При установке каждой точки сохранения ей назначается некоторое (локальное в пределах транзакции) имя, которое в дальнейшем может использоваться в операции ROLLBACK для задания точки частичного отката транзакции (см. выше синтаксис оператора ROLLBACK). Если последовательно устанавливаются две точки сохранения SP1 и SP2 и затем выполняется операция ROLLBACK TO SAVEPOINT SP1, то восстановление производится до SP1 (через SP2), и точка сохранения SP2 "забывается"20).

Для установления точки сохранения используется оператор SAVEPOINT c очевидным синтаксисом

SAVEPOINT savepoint_name

Можно также отказаться от ранее установленной точки сохранения, удалив ее из контекста транзакции. Для этого предназначен оператор RELEASE, синтаксис которого также очевиден:

RELEASE SAVEPOINT savepoint_name

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

Подключения и сессии

Может показаться странным, что мы оставили на конец этой лекции материал, который, казалось бы, необходимо знать, чтобы иметь возможность приступить к работе с какой-либо из современных систем баз данных. Объяснение очень простое. Чем ниже уровень средств языка SQL, чем ближе эти средства соприкасаются с индивидуальными особенностями реализаций, тем менее точен и конкретен стандарт SQL. А в данном разделе речь идет о средствах, реализация которых в СУБД разных поставщиков обладает очень большой спецификой.

Сильно упрощая текущую ситуацию, можно сказать, что практически все современные продукты управления SQL-ориентированными базами данных основаны на архитектуре "клиент-сервер". Принципиальная схема клиент-серверной организации показана на рис.18.8.

Клиент-серверная архитектура СУБД
Рис. 18.8.  Клиент-серверная архитектура СУБД

Конечно, это рисунок весьма условен. Под термином пользователь здесь, конечно, понимается некоторое приложение, с которым реально работает конечный пользователь (например, в этом приложении может быть реализован монитор прямого SQL). Клиентская часть СУБД - это тот системный компонент, с которым непосредственно взаимодействует пользователь. Данный компонент скрывает специфику реальных взаимодействий с серверной частью СУБД (например, используемые сетевые протоколы, если клиентская и серверная части СУБД разнесены по разным компьютерам сети). Наконец, сервер баз данных представляет собой основную часть СУБД, где, собственно, и происходит выполнение операторов SQL и осуществляется доступ к базе данных.

Важно обратить внимание, что программные компоненты, представляющие пользователя и клиентскую часть СУБД, обычно выполняются на одном компьютере, а сервер баз данных работает на другом (серверном) компьютере. Но вполне может быть, что все три перечисленных программных компонента в действительности размещены на одном компьютере.

Установление соединений

Начиная со стандарта SQL/92, при разработке языковых средств стала приниматься во внимание клиент-серверная организация СУБД. Если говорить более точно, стал очевиден тот факт, что во всех существующих СУБД до начала работы приложения со средствами управления базой данных требуется выполнить некоторые предварительные инициирующие действия. В частности, необходимо создать контекст, в котором будет работать система баз данных. В некоторых реализациях этот контекст создается автоматически при запуске приложения, поскольку клиентская часть СУБД компонуется к приложению. В других случаях прикладная программа связывается с СУБД за счет наличия специализированных реализационно зависимых средств подключения к СУБД. Иногда контекст формируется на основе состояния системных переменных.

Очевидно, что для выработки языковых средств, которые не противоречили бы существующим реализациям, требовался компромисс. Этот компромисс выразился в том, что в SQL:1999 допускается установление связи приложения с СУБД по умолчанию, а также обеспечиваются средства явного управления соединениями. Общий подход состоит в следующем.

  • Почти все операторы SQL (с небольшим числом исключений) могут выполняться только при наличии подключения клиентской части СУБД к серверу базы данных.
  • Если соединение с сервером установлено и приложение пытается выполнить один из операторов SQL (для выполнения которых требуется соединение), то его выполняет та СУБД, с которой установлено соединение.
  • Если приложение пытается выполнить один из операторов SQL (для выполнения которых требуется соединение), а соединение не установлено, то, прежде всего, требуется установить соединение. В SQL:1999 указывается, что такое соединение является соединением с СУБД по умолчанию. Что собой представляет это умолчание, определяется в реализации. После установления соединения упомянутый оператор SQL выполняется той СУБД, с которой установлено соединение.
  • Если первым (до установки соединения) выполняемым оператором SQL является оператор CONNECT (это одно из исключений), то соединение по умолчанию не устанавливается, а происходит обращение к запрашиваемому серверу, и соединение устанавливается именно с ним.
  • Можно выполнять оператор CONNECT для установления соединений со вторым, третьим и т. д. серверами, не разрывая ранее установленные соединения. Каждое вновь установленное соединение называется текущим соединением (current connection), а все ранее установленные соединения - отложенными соединениями (dormant connection).
  • С каждым соединением ассоциирована сессия. Сессия, ассоциированная с текущим соединением, называется текущей сессией (current session), а сессии, ассоциированные с отложенными соединениями, называются отложенными сессиями (dormant session ).21)
  • Если у приложения имеется несколько соединений, можно переключать их с помощью оператора SET CONNECTION.
  • Для поддержания установленных соединений могут расходоваться значительные системные ресурсы. Поэтому может возникнуть потребность в ликвидации соединения. Это можно сделать с помощью оператора DISCONNECT. Все соединения, не ликвидированные явно до завершения работы приложения, ликвидируются системой автоматически. Попытка ликвидировать текущее соединение, в котором выполняется транзакция, расценивается как ошибка.
  • В реализации определяется, можно ли переключать соединения во время выполнения транзакции. Однако если реализация это допускает, то, в соответствии со стандартом, все операторы, выполняемые в одной транзакции, но в разных соединениях, являются частью одной общей транзакции.

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

Как отмечалось выше, в эту группу входят операторы CONNECT, SET CONNECTION и DISCONNECT.

Оператор CONNECT

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

CONNECT TO connection_target
connection_target ::= SQL_server_name
                        [ AS connection_name ]
                        [ USER connection_user_name ]
                   | DEFAULT

Здесь SQL_server_name - это литерально заданная символьная строка, идентифицирующая сервер, к которому требуется подключиться. Смысл (и формат) этого имени определяется в реализации.

В необязательном разделе AS указываемое имя (connection_name) выступает в роли временного имени соединения, которое впоследствии может быть использовано в операторах SET CONNECTION и DISCONNECT. Если в операторе CONNECT раздел AS не содержится, то по умолчанию connection_name совпадает с SQL_server_name.

В необязательном разделе USER указываемое имя (connection_user_name) идентифицирует пользователя, от имени которого устанавливается соединение. При отсутствии раздела USER в качестве connection_user_name по умолчанию принимается текущий authID. В стандарте допускается, что реализация может ограничить возможные значения connection_user_name (например, потребовать, чтобы это имя всегда совпадало с текущим authID).

Эффект использования оператора в форме CONNECT TO DEFAULT почти не отличается от результата действия системы при отсутствии какого-либо явного требования соединения. (Напомним, что соединение по умолчанию неявно устанавливается при попытке выполнения первого оператора SQL, требующего соединения.) Однако имеется одно важное отличие. Если соединение по умолчанию устанавливается неявно, а затем вдруг прерывается из-за какой-то ошибки, то оно автоматически переустанавливается при выполнении следующего оператора SQL. Если же соединение по умолчанию устанавливается явным образом, то автоматическое повторное установление соединения после его разрыва не производится.

Оператор SET CONNECTION

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

SET CONNECTION connection_object
connection_object::= { connection_name | DEFAULT }

Условием успешного выполнения операции является наличие отложенного установленного соединения с именем connection_name или отложенного установленного соединения по умолчанию. В этом случае текущее соединение становится отложенным, а указанное отложенное соединение - текущим.

Оператор DISCONNECT

Оператор имеет следующий синтаксис:

DISCONNECT { connection_object | ALL | CURRENT }

Необходимым условием для возможности ликвидации соединения является отсутствие активной транзакции в этом соединении.

Если в операторе указывается connection_object, то соответствующее имя должно соответствовать установленному (текущему или отложенному) соединению. Если указывается CURRENT, то должно существовать текущее соединение.

Если оператор применяется к текущему соединению, то это соединение ликвидируется, и ни одно соединение не является текущим. В таком случае для продолжения работы необходимо установить текущее соединение при помощи операторов CONNECT или SET CONNECTION.

Если в операторе указывается ALL, то ликвидируются все соединения, включая текущее.

Заключение

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

Как легко видеть, при распространении привилегий и ролей могут возникать произвольно сложные ориентированные графы связей между объектами базы данных, владельцами привилегий, привилегиями и ролями. Если изображать сплошными стрелками передачу привилегий, прерывистыми - передачу ролей, пунктирными - владение привилегиями, а точечными - владение ролями, то даже по отношению к одной привилегии pr для одного объекта o может появиться следующий граф связей (userID означает authID, отличный от имени роли), показанному на рис.18.8.

Простейший граф идентификаторов пользователя, имен ролей, объектов и привилегий
Рис. 18.9.  Простейший граф идентификаторов пользователя, имен ролей, объектов и привилегий

Как мог появиться такой граф? Пользователь с authID, равным userID1 (это мы предположили для упрощения, а вообще-то это могло быть и именем роли), создает объект o, становится его владельцем и тем самым обладателем привилегии pr по отношению к этому объекту. Пользователь userID1 предоставляет полномочие pr роли role1 (с правом передачи). Затем пользователю userID1 предоставляется роль role1 (с правом передачи), и он получает право исполнять эту роль. От имени роли role1 полномочие pr передается пользователю userID2 (с правом передачи), и этот же пользователь получает право исполнять роль role1 (с правом передачи). Пользователь userID2 передает роли role2 роль role1 и полномочие pr (с правом передачи). Наконец, от имени роли role2 полномочие pr и сама роль role2 передаются пользователю userID1.

Попробуйте теперь проследить, как будет выполняться операция

REVOKE pr ON o FROM role1 CASCADED

Какие узлы и дуги останутся в графе? Задача не очень сложная, но, очевидно, нетривиальная. И такого рода задачи приходится ежедневно решать администраторам больших и динамических SQL-ориентированных баз данных.

Теперь немного поговорим об управлении транзакциями. В стандарте SQL:1999 ничего не говорится о возможной реализации различных уровней изоляции. Конечно, это правильно, поскольку спецификация языка не должна накладывать какие-либо ограничения на реализации. Но, к сожалению, при использовании SQL-ориентированной СУБД некоторые знания о реализации механизма транзакций необходимы. Например, предположим, что имеются две транзакции T1 и T2, выполняемые в режиме изоляции SERIALIZABLE. Предположим, что они должны работать по "симметричному" плану, показанному на рис.18.9.

Взаимные "фантомы"
Рис. 18.10.  Взаимные "фантомы"

Транзакции работают в наивысшем режиме изолированности. Эффект их выполнения должен быть эквивалентен эффекту некоторого последовательного выполнения транзакций T1 и T2. Но попробуйте придумать какой-либо корректный способ одновременного выполнения этих транзакций, который привел бы к эффекту их последовательного выполнения. Другими словами, для грамотного использования механизма транзакций на уровне языка SQL необходимо знать, каким образом данный механизм реализован в используемой СУБД.

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

  1)   Напомним, что в этом курсе мы не касаемся вопросов интернационализации и локализации языка SQL.
  2)   Как будет показано в следующем подразделе, термин роль в языке SQL полностью соответствует своему житейскому смыслу. И в мире баз данных люди большей частью играют чью-то роль, а не представляют себя лично.
  3)   В соответствии со стандартом любые зарегистрированные в системе пользователь или роль автоматически являются владельцами части схемы базы данных, имена объектов которой начинаются с соответствующего идентификатора, за которым следует символ <.>.
  4)   Для каждого объекта базы данных и для каждого пользователя, обладающего какими-либо привилегиями доступа к этому объекту, требуется хранить список его привилегий. Если учесть еще и возможность передачи привилегий от одного пользователя к другому, то образуется произвольно сложный граф, за которым трудно следить администраторам базы данных.
  5)   Чтобы хотя бы немного облегчить чтение данного подраздела, забегая вперед, заметим, что понятия сессии и подключения относятся к сеансу работы клиентского приложения с некоторым сервером SQL-ориентированной базы данных.
  6)   Кстати, это один из тех случаев, когда иметь право не означает автоматически иметь возможность реализации своего права. SQL допускает, например, наличие привилегии INSERT для представления, к которому операция INSERT не применима.
  7)   Кстати, стандарт полностью отдает на волю реализации способ того, каким образом сделать неопределенным значение текущего пользовательского идентификатора SQL-сессии.
  8)   В действительности, как видно из приведенных описаний, варианты операторов GRANT и REVOKE для привилегий и ролей настолько близки, что непонятно их синтаксическое разделение, которое, очевидно, усложняет реализацию. Как кажется, это разделение не обосновано в стандарте SQL:1999.
  9)   В общем случае состав и порядок выполнения операций, выполняемых внутри транзакции, становится известным только на стадии выполнения.
  10)   Читателей может смутить параллельное использование терминов согласованность и целостность. С точки зрения автора этого курса, в контексте баз данных эти два термина эквивалентны. Единственным критерием согласованности данных является их удовлетворение ограничениям целостности, т. е. база данных находится в согласованном состоянии тогда и только тогда, когда она находится в целостном состоянии.
  11)   Здесь мы опять сталкиваемся с терминологической трудностью, существующей уже много лет. В англоязычной терминологии имеется замечательный термин concurrent, который соответствует как реально параллельному, так и квазипараллельному выполнению транзакций (или процессов). Русский эквивалент одновременный не совсем точно соответствует смыслу оригинала, но лучшего варианта пока нет.
  12)   Правильнее было бы говорить SQL-транзакции, но в этом курсе мы не обсуждаем другие модели транзакций и поэтому будем использовать термин "транзакция" в смысле SQL-транзакция.
  13)   В русской терминологии для краткой характеристики этого действия часто используется не очень элегантный, но точно отражающий суть происходящего термин откат транзакции.
  14)   В этом курсе мы не будем более подробно обсуждать способы получения и обработки диагностических сообщений, поскольку это потребовало бы привлечения слишком большого числа технических деталей, не слишком существенных для общего понимания языка.
  15)   В действительности, этот подход был введен еще в проекте System R.
  16)   Правильнее было бы сказать почти всегда, поскольку в SQL предусматривается особый способ терминации транзакций, инициированных программными агентами. Но в данном курсе мы этого не касаемся.
  17)   Возможно, некоторым читателям эти рассуждения покажутся несколько расплывчатыми, но в действительности за ними стоит развитая техника журнализации и восстановления, применяемая во всех развитых SQL-ориентированных СУБД.
  18)   При этом экономятся хотя бы ресурсы, требуемые для создания транзакций. Иногда такие цепочки транзакций поэтически называют сагами: если вы когда-нибудь пробовали писать саги, то должны были почувствовать, что это проще, чем писать отдельные сказания.
  19)   Естественно, на практике проверяются только те ограничения, которые могут быть потенциально нарушены в результате выполнения соответствующего оператора.
  20)   Обратите внимание, что оператор ROLLBACK TO SAVEPOINT savepoint_name, хотя и синтаксически схож с "обычным" оператором ROLLBACK, принципиально отличается по своей семантике. Откат транзакции до указанной точки сохранения не означает завершения транзакции. Видимо, из соображений здравого смысла, следовало бы ввести в язык SQL операцию ABORT TRANSACTION для аварийного завершения транзакции с ликвидацией всех последствий ее операций в базы данных и, отдельно, операцию ROLLBACK TO, в которой всегда явно указывалось бы, до какого уровня требуется откат. Кстати, заметим, что комбинация ROLLBACK AND CHAIN TO SAVEPOINT savepoint_name является недопустимой (поскольку текущая транзакция не завершается).
  21)   Каждому соединению соответствует одна и только одна сессия. В сообществе SQL эти термины часто используются попеременно для обозначения одного и того же. Более строгие блюстители терминологии утверждают, что термин подключение относится к сетевому пути между клиентом и сервером, а ессия - это контекст, в котором работает SQL-сервер.