Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
10
Добавлен:
05.06.2015
Размер:
60.42 Кб
Скачать

6

Из работ Тома Кайта

В СУБД Oracle используется многоверсионная, согласованная по чтению модель конкурентного доступа.

……сейчас же для нас важно то, что этот механизм используется в СУБД Oracle для обеспечения:

  • согласованных по чтению запросов – запросов, которые выдают согласованные результаты относительно момента времени;

  • неблокируемых запросов – запросы никогда не блокируются писателями данных, как это делается во многих других СУБД.

……………………………………………………………………………………………………….

Термин “многоверсионность” в основном связан с тем фактом, что СУБД Oracle способна поддерживать в базе данных множественные версии данных. Если вы понимаете, как работает многоверсионность, вы всегда будете понимать, как СУБД будет реагировать на ваши запросы. Перед немного более подробным изучением механизмов многоверсионности в СУБД Oracle рассмотрим самый простой способ демонстрации многоверсионности в СУБД Oracle:

tkyte@TKYTE816> create table t 2 as 3 select * from all_users; Table created. tkyte@TKYTE816> variable x refcursor tkyte@TKYTE816> begin 2 open :x for select * from t; 3 end; 4 / PL/SQL procedure successfully completed. tkyte@TKYTE816> delete from t; 18 rows deleted. tkyte@TKYTE816> commit; Commit complete. tkyte@TKYTE816> print x

USERNAME USER_ID CREATED

------------------------------ ---------- ---------

SYS 0 04-NOV-00

SYSTEM 5 04-NOV-00

DBSNMP 16 04-NOV-00

AURORA$ORB$UNAUTHENTICATED 24 04-NOV-00

ORDSYS 25 04-NOV-00

ORDPLUGINS 26 04-NOV-00

MDSYS 27 04-NOV-00

CTXSYS 30 04-NOV-00

...

DEMO 57 07-FEB-01

18 rows selected.

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

Мы не извлекаем данные из этого курсора, мы только открываем его. Имейте в виду, что СУБД Oracle не "отвечает" на запрос, никуда не копирует данные при открытии курсора (представьте, сколько времени потребовалось бы на открытие курсора при копировании миллиарда строк таблицы). Курсор открывается немедленно, и именно он отвечает на запрос. Другими словами, курсор будет читать данные из таблицы только тогда, когда вы будете извлекать их из курсора.

Затем в том же сеансе (или, возможно, в другом) мы удаляем из таблицы Т все данные. Мы даже пойдем так далеко, что зафиксируем транзакцию с этим удалением (COMMIT). Строки таблицы потеряны. Но в действительности они доступны через курсор. Дело в том, что результирующее множество, которое возвращается к нам, предопределено на момент времени открытия курсора (выполнение оператора OPEN). Во время открытия курсора мы не "прикасались" ни к одному блоку данных в таблице, но ответ уже был зафиксирован "в камне". У нас нет никаких способов узнать ответ до извлечения данных, но с точки зрения нашего курсора он будет неизменным. Не потому, что СУБД Oracle во время открытия курсора копирует все наши данные в какое-то другое место, на самом деле это делается во время выполнения оператора delete; наши данные сохраняются в области данных, которая называется сегментами отката (rollback segments).

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

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

Мы будем запрашивать простую таблицу accounts (счета). Она содержит балансы счетов (account_balance) в банке и имеет очень простую структуру:

create table accounts ( account_number number primary key, account_balance number );

В действительности эта таблица может содержать сотни тысяч строк, но для простоты мы будем рассматривать таблицу с четырьмя строками (более подробно мы рассмотрим этот пример в главе 3, "Блокирование и конкурентность"):

Строка

Номер счета (Account Number)

Баланс счета (Account Balance)

1

123

$500.00

2

234

$250.00

3

345

$400.00

4

456

$100.00

Подготовим отчет о балансе банка в конце банковского рабочего дня. Это очень простой запрос:

select sum(account_balance) from accounts;

Конечно, в этом примере ответ очевиден: $1250. Но что случится, если мы прочитаем строку 1, а во время чтения строк 2 и 3 "автоматический кассир" (Automated Teller Machine) сгенерирует транзакцию, которая перечислит $400 со счета 123 на счет 456? Наш запрос подсчитает $500 в строке 4 и выдаст ответ: $1650, не так ли? Конечно, этого не должно быть – никогда такой суммы значений в столбце account_balance не было. Вы должны понимать, каким образом СУБД Oracle избегает таких ситуаций и как методы СУБД Oracle отличаются от других СУБД.

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

Ранее я сказал, что если вы не понимаете концепций многоверсионности в СУБД Oracle, вы не сможете воспользоваться всеми преимуществами СУБД Oracle. Здесь представлена одна из причин этого. СУБД Oracle использует многоверсионность для получения такого ответа на запрос, который существовал на момент времени начала запроса, причем запрос выполняется без блокирования данных (когда наша транзакция перечисления денег с одного счета на другой обновляла строки 1 и 4, эти строки блокировались для других писателей, но не блокировались для других читателей, выполнявших аналогичные запросы). В СУБД Oracle отсутствует блокировка “разделяемого чтения”, часто встречающаяся в других СУБД. В СУБД Oracle она не нужна. Все, что препятствует конкурентному доступу и может быть устранено, в СУБД Oracle устранено.

Итак, каким образом СУБД Oracle получает корректный, согласованный ответ ($1250), выполняя чтение без какого-либо блокирования данных, другими словами, без понижения уровня конкурентного доступа? Секрет содержится в транзакционных механизмах, используемых в СУБД Oracle. Всякий раз, когда вы модифицируете данные, СУБД Oracle создает записи в двух разных местах. Одна запись поступает в журнальные файлы (redo logs), в которых СУБД Oracle сохраняет достаточно информации для повторного выполнения транзакций (redo или ‘roll forward’ – прим. пер. иногда называются "откатом вперед" или "накатом" базы данных при ее восстановлении). Для операций вставки сохраняется вся вставляемая строка. Для операций удаления сохраняется сообщение об удалении строки в файле X, блоке Y, участке блока Z. И т.д. Другая запись (undo) поступает в сегмент отката (rollback segment). Если ваша транзакция завершается аварийно и ее требуется “откатить”, СУБД Oracle читает исходные данные из сегмента отката и восстанавливает их. Кроме использования сегментов отката для отката транзакций, СУБД Oracle использует их во время чтения для восстановления блоков на момент времени начала транзакций. Это позволяет правильно читать блоки, заблокированные и измененные другими транзакциями, и получать согласованные, корректные ответы без какого-либо блокирования данных читающим сеансом.

Так, в нашем примере СУБД Oracle выполняет следующие операции:

Время

Запрос

Транзакция перечисления денег

T1

Читает строку 1, сумма = $500.

 

T2

 

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

T3

Читает строку 2, сумма = $750.

 

T4

Читает строку 3, сумма = $1150.

 

T5

 

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

T6

Читает строку 4, обнаруживает, что строка 4 была модифицирована. Восстанавливает содержимое блока из сегмента отката, которое было во время T1. Из этого блока читает значение $100.

 

T7

 

Фиксирует транзакцию.

T8

Выдает ответ: $1250.

 

Во время T6 СУБД Oracle читает строку 4 несмотря на блокировку, наложенную нашей транзакцией. Так в СУБД Oracle реализовано неблокируемое чтение – СУБД проверяет только, изменены ли данные, не обращая внимания на текущую блокировку. Затем она просто извлекает старое значение из сегмента отката и переходит к следующему блоку данных.

Еще один пример многоверсионности: существует несколько версий одной и той же порции информации, соответствующих разным моментам времени. СУБД Oracle может использовать эти "моментальные копии" данных, сделанные в разные моменты времени, для выполнения согласованных по чтению запросов и неблокируемых запросов.

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

for x in (select * from t) loop insert into t values (x.username, x.user_id, x.created); end loop;

Результат выполнения оператора SELECT * FROM T был предопределен тогда, когда запрос начал выполняться. Оператор SELECT не будет видеть никакие новые данные, вставленные оператором INSERT. В противном случае выполнялся бы бесконечный цикл. Такое согласованное чтение обеспечивается для всех операторов, поэтому выполнение следующего оператора INSERT также предсказуемо:

insert into t select * from t;

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

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