Согласованность чтения и неблокирующие чтения Oracle

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

Мы будем запрашивать простую таблицу ACCOUNTS , которая хранит балансы банковских счетов и имеет очень простую структуру:

create tаblе accounts
(account_number number primary key,
account balance number
);

В реальном приложении таблица ACCOUNTS должна была бы содержать сотни тысяч строк, но для простоты мы рассмотрим таблицу с четырьмя строками, как показано в табл. 1.1.

 Таблица 1. Содержимое таблицы Accounts

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

select sum(account_balance) from accounts;

Ответ в этом примере, конечно же, очевиден: $1250. Однако что произойдет, если мы успешно прочитали строку 1, а во время чтения строк 2 и 3 банкомат генерирует транзакции в отношении этой таблицы и переводит сумму $400 со счета 123 на счет 456? Запрос обнаружит $500 в строке 4 и возвратит сумму, равную $1650, не так ли? Естественно, подобного следует избегать, потому что ответ был бы ошибочным — ни в один из моментов времени в столбце баланса счетов такая денежная сумма не присутствует. В Orac1e описанные ситуации не возникают благодаря согласованности чтения. Методы, используемые в Oracle, отличаются от применяемых в большинстве остальных баз данных, и вы должны понимать эти отличия.

Во многих других базах данных для получения “согласованного” и “корректного” ответа на этот запрос придется либо заблокировать целую таблицу, пока сумма вычисляется, либо заблокировать строки при их чтении. Это препятствует изменению ответа во время его получения. Если вы заблокируете таблицу заранее, то ответ будет соответствовать состоянию базы данных на момент, когда запрос начал выполняться. Если вы будете блокировать данные по мере их считывания (метод, обычно называемый блокировкой разделяемого чтения, который предотвращает обновления, но не препятствует доступу к данным со стороны других процессов, выполняющих чтение), то получите ответ, соответствующий состоянию базы данных на момент завершения запроса. Оба метода в значительной степени снижают степень параллелизма. Блокировка таблицы препятствует любым обновлениям целой таблицы на протяжении выполнения запроса (для таблицы, состоящей из четырех строк, этот промежуток времени оказывается очень коротким, но для таблиц, которые содержат сотни тысяч строк, он мог бы занять несколько минут). Метод “блокировки по ходу” предотвращает обновления данных, которые бььти прочитаны и уже обработаны, и может приводить к возникновению взаимоблокировок между этим запросом и другими операциями обновления.

Как уже упоминалось ранее, без полного понимания концепции многоверсион- ности невозможно в полной мере воспользоваться преимуществами, предлагаемыми Oracle. Разберем одну из причин этого. Многоверсионность в Oracle применяется для получения ответа, соответствующего моменту начала запроса, и запрос будет выполнен без блокирования чего-либо. (Пока транзакция перевода денежных сумм с одного счета на другой обновляет строки 1 и 4, эти строки будут заблокированы

других процессов записи, но не процессов чтения данных, таких как запрос SELECT SUM ....) Фактически в Oracle отсутствует блокировка “разделяемого чтения” (распространенный тип блокировки в других базах данных), т.к. необходимость в ней отсутствует. Все, что снижает степень параллелизма, из Oracle было удалено.

Мне на практике встречались случаи, когда отчет, созданный разработчиком, который не понимал возможности многоверсионности Oracle, полностью блокировал всю систему. Причиной было то, что разработчик стремился получить согласованные по чтению (т.е. правильные) результаты запросов. В любой другой базе данных, с которой разработчик имел дело, это требовало блокирования таблиц либо использования оператора SELECT ... WITH HOLDLOCK (механизм SQL Server, предназначенный для блокировки строк в разделяемом режиме по мере их считывания). В итоге разработчик должен был либо блокировать таблицы перед тем, как приступить к запуску построения отчета, либо применять запрос SELECT ... FOR UPDATE (ближайший аналог WITH HOLDLOCK). Это приводило к практически полной остановке обработки транзакций — причем совершенно неоправданно.

Каким же образом СУБД Oracle получает правильный непротиворечивый ответ ($1250) во время чтения без блокировки каких-либо данных — другими словами, без снижения степени параллелизма? Секрет кроется в используемых Oracle транзакционных механизмах. При каждом изменении данных Oracle создает записи в двух разных местах (большинство других баз данных помещают обе записи в одно место; для них сегмент отката (undo) и журнальный сегмент (redo) — просто “транзакционные данные”). Одна запись поступает в журналы повторного выполнения, где Oracle хранит информацию для повторного выполнения (redo), или “наката” (roll fotward) транзакции. операции вставки это будет вставляемая строка. Для операции удаления концептуально это будет сообщение об удалении строки из файла Х, блока Y, строкового слота Z и т.д. Еще одной записью является запись отмены (undo), помещаемая в сегмент отмены. Если транзакция отказывает и должна быть отменена, то Oracle читает образ “до того” из сегмента отмены и восстанавливает данные. В дополнение к применению этих данных сегмента отмены для отмены транзакций, Oracle использует их отмены изменений в блоках данных и их восстановления в том состоянии, которое соответствовало моменту начала выполнения запроса.

Это позволяет производить чтение, минуя блокировку, и получать согласованные, правильные ответы, не блокируя какие-либо данные самостоятельно.

Итак, применительно к рассматриваемому примеру, Oracle приходит к правильному ответу, как описано в табл. 1.2.

 Таблица 2. Многоверсионность Oracle в действии

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

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

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

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

Результат запроса SELECT * FROM предопределен на момент начала его выполнения. Оператор SELECT не увидит никаких новых данных, сгенерированных оператором INSERT. Вообразите себе, что если бы он делал это - оператор мог бы превратиться в бесконечный цикл. Если бы оператор SELECT мог “видеть” новые строки, вставленные в таблицу т оператором INSERT, то приведенный выше код создавал бы неизвестное количество строк. Скажем, если бы в начале таблица т имела 10 строк, то в конце она могла бы содержать 20, 21, 23 или бесконечное число строк. Поведение было бы совершенно непредсказуемым. Согласованное чтение обеспечивается всех операторов, поэтому оператор INSERT вроде показанного ниже также является предсказуемым:

insert into t select * from t;

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

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

 

Вас заинтересует / Intresting for you:

Обновление до Oracle Database ...
Обновление до Oracle Database ... 5509 просмотров Илья Дергунов Tue, 21 Nov 2017, 13:18:05
Видеокурс по администрированию...
Видеокурс по администрированию... 10516 просмотров Илья Дергунов Mon, 14 May 2018, 05:08:47
Поддерживаемые Oracle типы дан...
Поддерживаемые Oracle типы дан... 5682 просмотров Валерий Павлюков Wed, 24 Oct 2018, 08:00:37
Многоверсионность в Oracle
Многоверсионность в Oracle 2152 просмотров Дэйзи ак-Макарова Tue, 21 Nov 2017, 13:28:01
Войдите чтобы комментировать

VladStr аватар
VladStr ответил в теме #8120 22 фев 2017 11:05
Очень глубокая и подробная статья, и главное - понятная! С согласованностью чтения в Oracle только начал разбираться. Теперь кое-что проясняется! Спасибо :-)