Помимо четырех уровней изоляции, определенных в стандарте SQL, сервер Oracle Database обеспечивает еще один уровень – READ ONLY (транзакции только для чтения). Уровень изоляции READ ONLY эквивалентен определенным в стандартах уровням изоляции транзакций REPEATABLE READ или SERIALIZABLE, если в них не выполняются никакие модификации данных. Такая транзакция "видит" только обновления, зафиксированные на момент начала ее выполнения, но в этом режиме не разрешена вставка, обновление и удаление данных (другие сеансы могут обновлять данные, но транзакция только для чтения – нет). Используя этот режим, можно достичь уровней REPEATABLE READ и SERIALIZABLE без фантомного чтения.
Теперь давайте рассмотрим, как многоверсионность и согласованность чтения соотносятся с показанной выше схемой изоляции и как другие СУБД, не поддерживающие многоверсионности, достигают тех же результатов. Эта информация будет полезна всем, кто работал с другими СУБД, и верит, что знает, как должны работать механизмы, обеспечивающие уровни изоляции. Также будет интересно посмотреть, как стандарт ANSI/ISO языка SQL, который, казалось бы, должен сглаживать различия между СУБД, фактически их допускает. Этот очень детальный стандарт может быть реализован множеством способов.
READ UNCOMMITTED. Уровень изоляции READ UNCOMMITTED разрешает грязное чтение. Сервер Oracle Database не использует грязного чтения и даже не допускает его. Основное назначение этого уровня изоляции – определение в стандарте возможности выполнения операций неблокирующего чтения. Как уже было сказано, сервер Oracle Database по умолчанию обеспечивает неблокирующее чтение. Надо создать специальные условия, чтобы оператор SELECT блокировал что-либо и ожидал в сервере базы данных (как отмечалось ранее, существует особый случай выполнения распределенной транзакции). Каждый отдельный запрос (будь это операторами SELECT, INSERT, UPDATE, MERGE или DELETE) выполняется в согласованном по чтению режиме. Может показаться странным называть оператор UPDATE запросом, но это так. Операторы UPDATE имеют два компонента: компонент чтения, определяемого предложением WHERE, и компонент записи, определяемой предложением SET. Операторы UPDATE читают и пишут в базу данных, также как и все DML-операторы. Единственным исключением является оператор INSERT, вставляющий одну строку, используя предложение VALUES, – у таких операторов нет компонентов чтения, есть только компоненты записи.
В главе 1 [книги Expert Oracle Database Architecture: 9i and 10g Programming Techniques and Solutions] я показывал, как сервер Oracle Database обеспечивает согласованность чтения, демонстрируя простой запрос к одной таблице, который извлекал строки, удаленные после открытия курсора. Теперь я собираюсь рассмотреть реальную ситуацию, чтобы показать, что происходит в сервере Oracle Database, использующим многоверсионность, а что – в любых других СУБД.
Я начну с той же таблицы и запроса:
create table accounts
( account_number number primary key,
account_balance number not null
);
select sum(account_balance)
from accounts;
Перед началом запроса имеются следующие данные:
Строка | Account_Number (номер счета) | Account_Balance (баланс счета) |
1 | 123 | $500.00 |
2 | 456 | $240.25 |
... | ... | ... |
342 023 | 987 | $100.00 |
Таблица 2. Таблица ACCOUNTS (банковские счета) перед началом модификации |
Теперь мой оператор SELECT начинает выполняться и читать строку 1, строку 2 и т. д. В какой-то момент выполнения запроса некая транзакция переводит $400 со счета 123 на счет 987. Эта транзакция выполняет эти два обновления, но не фиксирует их. Таблица ACCOUNTS теперь выглядит следующим образом:
Строка | Account_Number | Account_Balance | Заблокирована? |
1 | 123 | ($500.00) изменились на $100.00 | X |
2 | 456 | $240.25 | -- |
... | ... | ... | -- |
342,023 | 987 | ($100.00) изменились на $500.00 | X |
Таблица 3. Таблица ACCOUNTS во время модификации |
Как показывает таблица 3, обе обновленные строки заблокированы. Если кто-то попытается обновить их, он будет блокироваться. В этом отношении все СУБД работают примерно одинаково. Различия появятся, когда запрос доберется до заблокированных данных.
Добравшись до блока, содержащего заблокированную строку (строка 342 023), выполняемый запрос "поймет", что данные в ней изменились после начала выполнения запроса. Чтобы дать согласованный (правильный) ответ, сервер Oracle Database создаст копию этого блока, содержащего эту строку в том виде, в каком она была на момент начала выполнения запроса. То есть сервер читает значение, равное $100. Фактически сервер Oracle Database "обходит" модифицированные данные, реконструируя их из сегмента отката (rollback segment). Согласованный и правильный ответ получается без ожидания фиксации транзакции, обновляющей данные.
Однако СУБД, допускающая грязное чтение, просто вернет значение баланса счета 987 на момент его чтения, в данном случае $500. Этот запрос сосчитает перевод $400 дважды. Следовательно, он выдаст не только неправильный результат, но также возвратит сумму, которая никогда не фигурировала в таблице счетов. В многопользовательской системе базы данных грязное чтение может быть опасным, и лично я никогда не видел от него никакой пользы. Пусть вместо перевода денег с другого счета транзакция добавила бы $400 на счет 987. Грязное чтение сосчитало бы $400 и выдало "правильный" ответ, не так ли? А если будет выполнен откат незафиксированной транзакции? Нам только что предъявили $400, которых в базе данных фактически никогда не было.
Дело в том, что грязное чтение – это не особенность, а просто лишняя ответственность. В сервере Oracle Database брать ее на себя нет никакой необходимости. Мы получаем все преимущества грязного чтения (отсутствие блокирования) без каких-либо некорректных результатов.
READ COMMITTED. Уровень изоляции READ COMMITTED требует, чтобы транзакция читала только данные, зафиксированные в базе данных. Никаких грязных чтений (чтений незафиксированных данных). Невоспроизводимые чтения допускаются (то есть повторные чтения одной и той же строки в одной и той же транзакции могут давать разные результаты), как и фантомные чтения (то есть запросы могут возвращаться вновь вставленные строки, невидимые в транзакции ранее). Уровень READ COMMITTED, вероятно, – наиболее часто и повсеместно используемый в приложениях баз данных уровень изоляции транзакций; он по умолчанию используется в сервере Oracle Database. Другие уровни изоляции в системах баз данных Oracle используются редко.
Однако уровень READ COMMITTED не настолько безобиден, как можно судить по названию. Если взглянуть на таблицу 1, все кажется вполне очевидным. Естественно, с учетом указанных выше правил, запрос в любой системе базы данных, использующей уровень изоляции READ COMMITTED, должен выполняться одинаково, не так ли? Нет. Если запрос возвращает несколько строк, фактически в любой другой СУБД уровень изоляции READ COMMITTED может стать причиной появления некорректных данных в той же мере, что и грязное чтение (независимо от реализации).
В сервере Oracle Database, благодаря использованию многоверсионности и согласованных по чтению запросов, результат запроса к таблице ACCOUNTS при уровне изоляции READ COMMITTED будет таким же, как и в примере раздела READ UNCOMMITTED. Сервер Oracle Database реконструирует модифицированные данные в том виде, какой они имели на момент начала выполнения запроса, возвращая ответ, соответствующий состоянию базы данных на этот момент.
Теперь давайте рассмотрим, как представленный выше пример мог бы выполняться в режиме READ COMMITTED в других СУБД. Результат удивит вас. Я продолжу этот пример с момента, описанного в таблице 3:
мы находимся в середине таблицы, прочитав и просуммировав к этому моменту N первых строк;
другая транзакция перевела $400 со счета 123 на счет 987;
эта транзакция еще не зафиксирована, поэтому строки 123 и 987 заблокированы.
Мы знаем, как поведет себя сервер Oracle Database, добравшись до счета 987: он обойдет модифицированные данные, выяснив, что баланс должен быть равен $100), и завершит запрос. Давайте рассмотрим, как другая СУБД, работающая в таком же режиме по умолчанию READ COMMITTED, будет выдавать ответ:
Время | Запрос | Транзакция по переводу со счета на счет |
T1 | Читает строку 1. Пока sum = $500.00. | -- |
T2 | Читает строку 2. Пока sum = $740.25. | -- |
T3 | -- | Обновляет строку 1 и устанавливает на нее монопольную блокировку, предотвращающую другие обновления и чтения этой строки. В строке теперь значение $100.00. |
T4 | Читает строку N, sum = . . . | -- |
T5 | -- | Обновляет строку 342 023 и устанавливает на нее монопольную блокировку. В строке теперь значение $500.00. |
T6 | Пытается прочитать строку 342 023 и обнаруживает, что она заблокирована. Сеанс блокируется и ждет доступа к этой строке. Выполнение запроса приостанавливается. | -- |
T7 | -- | Транзакция фиксируется. |
T8 | Читает строку 342 023, видит в ней $500.00 и выдает окончательный результат, в котором дважды учитывается значение $400.00. | |
Таблица 4. Временная последовательность операций в СУБД других производителей, использующих режим READ COMMITTED |
Прежде всего в таблице 4 обратите внимание, что в другой СУБД мой запрос будет заблокирован, когда доберется до счета 987. Этому сеансу придется ждать освобождения соответствующей строки, когда зафиксируется транзакция, удерживающая монопольную блокировку. Это одна из причин появления у многих разработчиков плохой привычки фиксировать результаты после выполнения каждого оператора, вместо того чтобы выполнять правильно построенные транзакции, содержащие все операторы, которые необходимы для перевода базы данных из одного согласованного состояния в следующее. В большинстве других СУБД обновления мешают чтениям. Но самое печальное в этом то, что пользователь вынужден ожидать неверного результата. Как и в случае грязного чтения, получен результат, никогда не существовавший в базе данных, но в этом случае пришлось еще и ждать этого неправильного ответа.
Важный урок здесь в том, что разные СУБД, выполняющие запрос с одним и тем же, предположительно безопасным, уровнем изоляции транзакции, возвращают существенно отличающиеся результаты в абсолютно одинаковых обстоятельствах. Важно понимать, что в сервере Oracle Database неблокирующее чтения даются не ценой некорректности результатов. Иногда бывает так, что "один пирог можно съесть два раза".
REPEATABLE READ. Целью включения уровня REPEATABLE READ в стандарт языка SQL было обеспечение уровня изоляции транзакций, дающего согласованные, корректные результаты и предотвращающего потерю обновлений. Я покажу примеры, что вы должны делать в сервере Oracle Database для достижения этой цели, и рассмотрю, что происходит в других системах. Если вы используете уровень REPEATABLE READ, результаты запросов должны быть согласованными на определенный момент времени. Большинство СУБД (не Oracle) добиваются воспроизводимого чтения, используя разделяемые блокировки чтения на уровне строк. Эти блокировки не разрешают другим сеансам модифицировать прочитанные данные. Это, несомненно, уменьшает параллелизм выполнения операций. В сервере Oracle Database для получения согласованных по чтению результатов используется многоверсионная модель конкурентного доступа, обеспечивающая больший уровень параллелизма.
При использовании механизма многоверсионности в сервере Oracle Database получается результат, согласованный на момент начала выполнения запроса. В других СУБД, использующих разделяемые блокировки чтения, получается результат, согласованный на момент завершения запроса, то есть на момент, когда вообще можно получить результат (подробнее об этом см. ниже).
В системе, использующей для обеспечения воспроизводимого чтения разделяемые блокировки чтения, строки будут блокироваться по мере их обработки запросом. Поэтому, возвращаясь к рассмотренному выше примеру чтения таблицы ACCOUNTS, мой запрос будет устанавливать разделяемые блокировки чтения на каждой прочитанной строке:
Время | Запрос | Транзакция по переводу со счета на счет |
T1 | Читает строку 1. Пока sum = $500.00. На блоке 1 установлена разделяемая блокировка чтения. | -- |
T2 | Читает строку 2. Пока sum = $740.25. На блоке 2 установлена разделяемая блокировка чтения. | -- |
T3 | -- | Пытается обновить строку 1, но эта попытка блокируется. Транзакция приостанавливается до тех пор, пока не сможет установить монопольную блокировку. |
T4 | Читает строку N, sum = . . . | -- |
T5 | Читает строку 342 023, видит в ней $100.00 и выдает окончательный результат. | -- |
T6 | Фиксирует транзакцию. | -- |
T7 | -- | Обновляет строку 1, устанавливая на этот блок монопольную блокировку. В строке теперь значение $100.00. |
T8 | -- | Обновляет строку 342 023 устанавливая на этот блок монопольную блокировку. В строке теперь значение $500.00. Транзакция фиксируется. |
Таблица 5. Временная последовательность операций в СУБД других производителей, использующих режим READ REPEATABLE |
Таблица 5 показывает, что получен корректный результат, но благодаря физическому блокированию одной транзакции и выполнению двух транзакций последовательно. Один из побочных эффектов использования разделяемых блокировок чтения для получения согласованных результатов: читатели данных блокируют писателей данных. Кроме того, в этих системах писатели данных блокируют читателей данных.
Итак, понятно, как разделяемые блокировки чтения могут запрещать конкурентный доступ, но они также могут приводить к возникновению спорадических ошибок. В таблице 6 мы начнем с исходной таблицы, но в этот раз целью будет перевод $50 со счета 987 на счет 123.
Время | Запрос | Транзакция по переводу со счета на счет |
T1 | Читает строку 1. Пока sum = $500.00. На блоке 1 установлена разделяемая блокировка чтения. | -- |
T2 | Читает строку 2. Пока sum = $740.25. На блоке 2 установлена разделяемая блокировка чтения. | -- |
T3 | -- | Обновляет строку 342 023, устанавливает монопольную блокировку на содержащий эту строку блок (прим. пер. в предыдущем издании книги автор для простоты предположил, что в каждом блоке может храниться только одна строка), предотвращающую другие обновления и установку разделяемых блокировок. В этой строке теперь $50.00. |
T4 | Читает строку N, sum = … | -- |
T5 | -- | Пытается обновить строку 1, но она заблокирована. Транзакция приостанавливается до тех пор, пока не появится возможность установить монопольную блокировку. |
T6 | Пытается прочитать строку 342 023, но не может, поскольку на нее уже установлена монопольная блокировка. | -- |
Таблица 6. Вторая временная последовательность операций в СУБД других производителей, использующих режим READ REPEATABLE |
Только что мы получили классическую ситуацию возникновения взаимоблокировок. Мой запрос удерживает ресурс, необходимый моей транзакции, которая обновляет данные, и наоборот. Запрос и обновляющая транзакция взаимно заблокировали друг друга. Один из сеансов будет выбран в качестве жертвы, и будет выполнен откат его транзакции. Затрачено немало времени и ресурсов и все впустую: в конце работы произошло выполнение отката. Это – второй побочный эффект разделяемых блокировок чтения: читатели и писатели данных могут взаимно блокировать друг друга, часто так и происходит.
Как видите, в сервере Oracle Database обеспечивается согласованность чтения на уровне операторов без блокирования сеансов записи сеансами чтения и без взаимоблокировок. Сервер Oracle Database никогда не использует разделяемые блокировки чтения. Разработчики сервера Oracle выбрали более сложную в реализации, но обеспечивающую принципиально более высокую степень параллелизма схему многоверсионного доступа.
SERIALIZABLE. Этот уровень изоляции транзакции обычно считают наиболее ограничивающим, но он обеспечивает самую высокую степень изоляции. Транзакция с уровнем изоляции SERIALIZABLE работает в среде, где как бы нет других пользователей, модифицирующих данные в базе данных. Гарантируется, что любая прочитанная строка будет такой же при повторных чтениях, любой выполненный запрос будет возвращать одинаковые результаты в течение всего времени существования транзакции.
Например, если вы выполните:
select * from T;
begin dbms_lock. sleep( 60*60*24 ); end;
select * from T;
Результаты, возвращаемые из таблицы Т, будут одинаковыми и через 24 часа (или мы получим сообщение об ошибке ORA-1555, snapshot too old (моментальная копия устарела)). Этот уровень изоляции гарантирует, что эти два запроса всегда будут возвращать одинаковые результаты. Побочные эффекты или изменения, сделанные в других транзакциях, невидимы этому запросу, независимо от того, как долго он выполняется.
В сервере Oracle Database сериализуемые транзакции реализуется так, что согласованность чтения, обычно получаемая на уровне оператора, распространяется на всю транзакцию. (Как отмечалось ранее, в сервере Oracle есть также уровень изоляции, названный READ ONLY. Транзакции только для чтения очень похожи на транзакции с уровнем изоляции SERIALIZABLE, но в них не разрешается модификация данных. Обратите внимание, пользователь SYS (или пользователи, подключенные к системе базы данных как SYSDBA) не могут использовать уровни изоляции READ ONLY или SERIALIZABLE. В этом отношении пользователь SYS является особым пользователем.)
Результаты являются согласованными не по отношению к началу выполнения оператора, они предопределены в начале выполнения транзакции. Другими словами, сервер Oracle Database использует сегменты отката для реконструкции данных в том виде, в каком они были на момент начала транзакции, а не на момент начала выполнения оператора. Весьма глубокая мысль: серверу базы данных известен ответ на ваш любой возможный вопрос прежде, чем вы его зададите.
Этот уровень изоляции не дается даром: за него нужно платить возможным сообщением об ошибке:
ERROR at line 1:
ORA-08177: can't serialize access for this transaction
(сериализуемый доступ для этой транзакции невозможен)
Это сообщение будет получено при любой вашей попытке обновления строки, измененной после начала вашей транзакции. (Обратите внимание, сервер Oracle пытается выполнять эту проверку исключительно на уровне строк, но вы можете получить ошибку ORA-01877 даже тогда, когда строка, которую вы хотите обновить, не была модифицирована. Эта ошибка может возникнуть из-за того, что в блоке, содержащем вашу строку, были модифицированы какие-то другие строки.)
Сервер Oracle Database придерживается при обеспечении сериализуемости оптимистического подхода; он предполагает, что данные, которые потребуется обновлять вашей транзакции, не будут обновляться другими транзакциями. Обычно именно так и происходит, и подобное предположение вполне оправдано, особенно в быстрых транзакциях, OLTP-системах. Если во время транзакции ваши данные не изменяются другими сеансами, этот уровень изоляции, обычно снижающий степень параллелизма в других системах, обеспечит ту же степень параллелизма, что и при отсутствии транзакций с таким уровнем изоляции. Вопрос лишь в том, не получите ли вы сообщения об ошибке ORA-08177, если предположение не оправдается. Однако поразмыслив, можно найти такой риск оправданным. Если вы используете транзакции с уровнем изоляции SERIALIZABLE, вы не ожидаете изменения одной и той же информации другими транзакциями.
Если же это возможно, следует использовать оператор SELECT... FOR UPDATE, как было описано в главе 1 [книги Expert Oracle Database Architecture: 9i and 10g Programming Techniques and Solutions]. Он будет сериализовать доступ. Итак, уровень изоляции SERIALIZABLE дает хороший эффект, если:
высока вероятность того, что данные не модифицируют другие сеансы;
необходима согласованность чтения на уровне транзакций;
транзакции будут непродолжительными (чтобы первое условие стало более реальным).
Но (всегда есть "но") вы должны понимать эти различные уровни изоляции и их смысл. Помните, установив уровень изоляции SERIALIZABLE, вы не будете видеть никаких изменений, сделанных в базе данных после начала вашей транзакции, пока вы не зафиксируете её. Приложения, которые пытаются использовать свои собственные ограничения целостности данных, такие, как в планировщике ресурсов, описанном в главе 1 [книги Expert Oracle Database Architecture: 9i and 10g Programming Techniques and Solutions], должны дополнительно заботиться об этом. Проблема в главе 1 состояла в том, что вы не могли использовать ваше ограничение целостности в многопользовательской системе, так как не могли видеть обновления, сделанные другими сеансами с незафиксированными транзакциями. Используя уровень SERIALIZABLE, вы по-прежнему не будете видеть эти незафиксированные изменения, но вы не будете также видеть и зафиксированные изменения, сделанные после начала вашей транзакции.
И последний момент, уровень SERIALIZABLE не означает, что все пользовательские транзакции дают такой же результат, как и при последовательном выполнении одна за другой. На этом уровне не предполагается какого-то физического порядка выполнения транзакций, дающего одинаковый результат. Ранее описанные явления в соответствии со стандартами языка SQL не позволяют добиться этого. Этот момент часто понимают неправильно, и небольшой пример сейчас все прояснит. В следующей таблице представлены два сеанса, выполняющие определенную последовательность действий. Таблицы базы данных А и В первоначально пусты и созданы следующими операторами:
ops$tkyte@ORA10G> create table a ( x int );
Table created.
ops$tkyte@ORA10G> create table b ( x int );
Table created.
Теперь происходят следующие события:
Время | Сеанс 1 выполняет | Сеанс 2 выполняет |
T1 | Alter session set isolation_level=serializable; | -- |
T2 | -- | Alter session set isolation_level=serializable; |
T3 | Insert into a select count(*) from b; | -- |
T4 | -- | Insert into b select count(*) from a; |
T5 | Commit; | -- |
T6 | -- | Commit; |
Таблица 7. Пример транзакций с уровнем изоляции SERIALIZABLE |
Теперь, когда закончились действия, показанные в таблице 7, в каждой из таблиц А и В имеется по строке со значением 0. Если бы транзакции выполнялись последовательно одна за другой, мы не могли бы получить нулевые значения в обеих таблицах. Если бы сеанс 1 опережал сеанс 2, то в таблице В было бы значение 1. Если бы сеанс 2 выполнялся прежде сеанса 1, то в таблице А было бы значение I. Однако при выполнении приведенной выше последовательности действий в обеих таблицах будут нулевые значения, то есть транзакции выполняются так, будто других транзакций в базе данных в этот момент нет. Неважно, сколько раз сеанс 1 запрашивает таблицу В – будет выдано количество строк, зафиксированных в базе данных на момент времени Т1. Аналогично, независимо от количества запросов к таблице А, в сеансе 2 будет получено значение на момент времени Т2.
8. Обеспечение защиты информации в системах управления базами данных. Безопасность и секретность информации. Защита данных. Назначение привилегий. Аннулирование привилегий. Средства языка SQL для управления доступом к БД.
современных условиях любая деятельность сопряжена с оперированием большими объемами информации, которое производится широким кругом лиц. Защита данных от несанкционированного доступа является одной из приоритетных задач при проектировании любой информационной системы. Следствием возросшего в последнее время значения информации стали высокие требования к конфиденциальности данных. Системы управления базами данных, в особенности реляционные СУБД, стали доминирующим инструментом в этой области. Обеспечение информационной безопасности СУБД приобретает решающее значение при выборе конкретного средства обеспечения необходимого уровня безопасности организации в целом.
|
Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |


