Стабилизация плана оптимизатора в Oracle 8i/9i
Узнайте, как с помощью «хранимых шаблонов» (stored outlines) можно повысить производительность приложения, даже если нельзя менять его исходный код, систему индексации и параметры конфигурации системы.
Инструментальные средства : Для упрощения экспериментов, в статье рассматривается только простой SQL- и PL/SQL-код, выполняемый в сеансе SQL*Plus. Читателю необходимы будут привилегии, которые типичным конечным пользователям обычно не предоставляют, но, в остальном, понадобится только знание основ языка SQL. Статья начинается с описания возможностей версии Oracle 8i, но затем автор переходит к Oracle 9i, в котором появилось ряд дополнительных возможностей генерации хранимых шаблонов и работы с ними.
Черный ход в черный ящик
Если вы — АБД, отвечающий за работу приложения стороннего производителя на базе СУБД Oracle, то, наверняка, испытывали разочарование, обнаружив в библиотечном кэше пару крайне медленно работающих и пожирающих массу ресурсов SQL-операторов, которые очень легко можно настроить, — если бы только можно было добавить пару подсказок оптимизатору в исходный код.
Начиная с Oracle 8.1, вам больше не надо переписывать SQL-операторы, чтобы добавить подсказки — можно передать оптимизатору подсказки, не меняя код. Эта возможность известна как использование хранимых шаблонов (Stored Outlines) или стабилизация плана оптимизатора (Plan Stability), причем, сонвная ее идея весьма проста: вы сохраняете в базе данных информацию типа: «если встречается SQL-оператор типа XXX, то перед его выполнением надо вставить вот такие подсказки в следующих местах. «
Эта возможность дает вам три потенциальных преимущества. Прежде всего, можно оптимизировать ту самую пару пожирающих ресурсы операторов. Далее, если есть друшгие операторы, которые сервер Oracle дольше оптимизирует, чем выполняет, можно сэкономить время и уменьшить количество конфликтов на стадии оптимизации. Наконец, можно использовать новый параметр конфигурации cursor_sharing , не рискуя потерять оптимальные пути выполнения операторов.
Есть несколько проблем в Oracle 8i, которые при этом придется как-то обходить (они практически решены в Oracle 9i), но, в общем случае, использовать хранимые шаблоны очень легко. В этой статье описаны некоторые возможности, которые при этом открываются.
Обзор основных возможностей
Чтобы продемонстрировать, как лучше всего использовать хранимые шаблоны, мы начнем с хранимой процедуры, исходный код которой нельзя изменить, выполняющей (теоретически) ряд крайне неэффективных SQL-операторов.
Мы увидим, как перехватить выполняемые SQL-операторы и особенности текущего пути их выполнения в базе данных, подобрать подсказки, повышающие производительность этих SQL-операторов, а затем заставить сервер Oracle использовать эти подсказки при дальнейшем выполнении этих SQL-операторов.
По ходу демонстрации мы создадим пользователя, таблицу в схеме этого пользователя и процедуру, обращающуюся к этой таблице, — но, ради развлечения, мы применим к процедуре утилиту wrap , чтобы ее код нельзя было увидеть. Затем мы поставим себе задачу настройки производительности SQL-операторов, выполняемых этой процедурой.
При демонстрации мы предполагаем, что все необходимое для использования хранимых шаблонов было установлено автоматически при создании базы данных.
Создаем пользователя с привилегиями: create session , create table , create procedure , create any outline и alter session . Подключаемся от имени этого пользователя и выполняем следующие операторы для создания таблицы:
create table so_demo ( n1 number, n2 number, v1 varchar2(10) ); insert into so_demo values (1,1,'One'); create index sd_i1 on so_demo(n1); create index sd_i2 on so_demo(n2); analyze table so_demo compute statistics;
Теперь необходимо создать процедуру, обращающуюся к этой таблице. Создаем сценарий c_proc.sql , содержащий следующий код:
create or replace procedure get_value ( i_n1 in number, i_n2 in number, io_v1 out varchar2 ) as begin select v1 into io_v1 from so_demo where n1 = i_n1 and n2 = i_n2; end; /
Можно, конечно, просто выполнить этот сценарий для построения процедуры, но, для большей эффектности, давайте выполним следующую команду в окне командной строки операционной системы:
wrap iname=c_proc.sql
В ответ вы должны получить:
Processing c_proc.sql to c_proc.plb
Вместо выполнения сценария c_proc.sql для генерации процедуры, выполните неочевидный сценарий c_proc.plb , и вы обнаружите, что никаких следов использованного SQL-оператора в представлении user_source нет.
Что хочет сделать приложение?
Теперь, когда исследуемое приложение готово, можно запустить его, включив, пожалуй, предварительно sql_trace , чтобы понять, что происходит. Неудивительно, если окажется, что SQL-оператор выполняет полный просмотр таблицы для получения необходимых данных.
В этом небольшом тесте полный просмотр, скорее всего, является самым эффективным способом выполнения запроса, но, предположим, мы доказали, что более высокая производительность достигается, когда сервер Oracle использует план на базе одностолбцовых индексов и опции and-equal . Как заставить сервер выполнять запрос именно так, не добавляя в код подсказки оптимизатору?
После появления хранимых шаблонов ответ простой. На самом деле, есть есть несколько способов добиться этого, так что не рассматривайте данный пример как единственно возможную стратегию. С каждой новой версией возможности сервера Oracle расширяются, чтобы упростить жизнь пользователям, и описанный далее механизм, наверняка, не понадобится в следующих версиях.
Что, по вашему, должно делать приложение?
Чтобы заставить сервер Oracle делать то, что нужно нам, необходимо пройти три этапа:
- Начинаем новый сеанс и повторно выполняем процедуру, потребовав предварительно от сервера Oracle перехватывать каждый поступающий SQL-оператор вместе с информацией о плане его выполнения. Эти «планы» станут нашим первым примером хранимых шаблонов.
- Создаем более подходящие шаблоны для всех проблемных SQL-операторов и заменяем «плохие» хранимые шаблоны хорошими.
- Начинаем новый сеанс и требуем от сервера Oracle начать использовать новые хранимые шаблоны вместо обычных методов оптимизации при обработке соответствующих SQL-ператоров. Затем снова выполняем процедуру.
Надо завершать текущий и начинать новый сеанс, чтобы гарантировать, что существующие курсоры не остались открытыми в кэше pl/sql. Хранимые шаблоны генерируются и/или применяются только при анализе операторов, так что надо гарантировать, что уже существующие курсоры для аналогичных операторов закрыты.
Итак, начнем сеанс и выполним следующую команду:
alter session set create_stored_outlines = demo;
Затем выполним небольшой анонимный блок, вызывающий процедуру, например:
declare m_value varchar2(10); begin get_value(1, 1, m_value); end; /
Теперь прекращаем сбор планов выполнения (иначе несколько следующих SQL-операторов тоже окажутся в таблицах хранимых шаблонов, что усложнит выполнение дальнейших действий).
alter session set create_stored_outlines = false;
Для просмотра результатов выполненных действий можно сделать запросы к представлениям, позволяющим получить детальную информацию о шаблонах, автоматически созданных и сохраненных сервером Oracle:
select name, category, used, sql_text from user_outines where category = 'DEMO'; NAME CATEGORY USED ------------------------------ ------------------------------ ------- SQL_TEXT --------------------------------------------------------------------- SYS_OUTLINE_020503165427311 DEMO UNUSED SELECT V1 FROM SO_DEMO WHERE N1 = :b1 AND N2 = :b2 select name, stage, hint from user_outline_hints where name = ' SYS_OUTLINE_020503165427311'; NAME STAGE HINT ------------------------------ ---------- ------------------------------ SYS_OUTLINE_020503165427311 3 NO_EXPAND SYS_OUTLINE_020503165427311 3 ORDERED SYS_OUTLINE_020503165427311 3 NO_FACT(SO_DEMO) SYS_OUTLINE_020503165427311 3 FULL(SO_DEMO) SYS_OUTLINE_020503165427311 2 NOREWRITE SYS_OUTLINE_020503165427311 1 NOREWRITE
Как видите, есть категория, demo , включающая всего лишь один хранимый шаблон, а посмотрев на sql_text для этого шаблона можно увидеть нечто похожее на SQL-оператор в исходном PL/SQL-коде, но не точно совпадающее с ним. Это существенно, поскольку сервер Oracle будет рассматривать возможность использования хранимого шаблона только если сохраненное значение sql_text очень близко к тексту SQL-оператора, который требуется выполнить. Фактически, в Oracle 8i тексты должны совпадать буквально, и первоначально это было большой проблемой при экспериментах с хранимыми шаблонами.
По листингу видно, что хранимые шаблоны представляют собой набор подсказок, описывающих действия сервера Oracle, который он выполнил (или должен выполнить) при выполнении соответствующего SQL-оператора. Данный план использует полный просмотр таблицы. Не правда ли, сервер Oracle используем немало подсказок, чтобы гарантировать выполнение настолько простого действия, как полный просмотр таблицы.
Обратите внимание, что хранимый шаблон всегда относится к определенной категории, в данном случае, к категории demo , которую мы задали в исходной команде alter session . Если в исходной команде просто указать true вместо demo , хранимые шаблоны окажутся в категории по имени default .
Хранимые шаблоны тоже имеют имена, и эти имена должны быть уникальными во всей базе данных. Имя шаблона не может совпадать с именем другого шаблона, даже сгенерированного другим пользователем. Фактически, у шаблонов нет владельцев, — есть только создатели. Если кто-то создал хранимый шаблон, соответствующий выполняемому мной в дальнейшем SQL-оператору, сервер Oracle применит соответствующий набор подсказок к моему тексту, даже если эти подсказки лишены смысла в контексте моей схемы. (Это дает нам несколько абсолютно новых возможностей для формирования хранимых шаблонов, и заслуживает отдельной статьи). Можно заметить, что когда сервер Oracle автоматически генерирует хранимые шаблоны, имена имеют простой формат и включают временную отметку (время создания) с точностью до миллисекунды.
Продолжая процесс «настройки» нашего проблематичного SQL-оператора, мы решаем, что если добавить подстказку /*+ and_equal(so_demo, sd_i1, sd_i2) */ , сервер Oracle будет использовать необходимый нам план выполнения, так что, теперь мы явно создает хранимый шаблон следующим образом:
create or replace outline so_fix for category demo on select /*+ and_equal(so_demo, sd_i1, sd_i2) */ v1 from so_demo where n1 = 1 and n2 = 2;
Этот оператор создает хоранимый шаблон с явно заданным именем so_fix в категории demo . Вид хранимого шаблона можно получить, повторив запросы к user_outlines и user_outline_hints с добавлением условия name = ‘SO_FIX’ .
NAME CATEGORY USED ------------------------------ --------------------- --------- SQL_TEXT --------------------------------------------------------------- SO_FIX DEMO UNUSED select /*+ and_equal(so_demo, sd_i1, sd_i2) */ v1 from so_demo where n1 = 1 and n2 = 2 NAME STAGE HINT ------------------------------ ---------- -------------------------------- SO_FIX 3 NO_EXPAND SO_FIX 3 ORDERED SO_FIX 3 NO_FACT(SO_DEMO) SO_FIX 3 AND_EQUAL(SO_DEMO SD_I1 SD_I2) SO_FIX 2 NOREWRITE SO_FIX 1 NOREWRITE
Обратите внимание, в частности, что строка FULL(SO_DEMO) заменена строкой AND_EQUAL(SO_DEMO SD_I1 SD_I2) , что и требовалось.
Теперь надо «поменять местами» эти два хранимых шаблона. Мы хотим, чтобы сервер Oracle использовал наш новый список подсказок при выполнении оператора с исходным текстом; и для этого придется прибегнуть к трюку. Представления user_outlines и user_outline_hints созданы на основе двух таблиц ( ol$ и ol$hints , соответственно), принадлежащих схеме outln , и мы собираемся изменять эти таблицы непосредственно; для этого подключаемся от имени пользователя outln или пользователя, имеющего привилегию изменения этих таблиц.
К счастью, таблицы outln не имеют включенных декларативных ограничений целостности ссылок. Нам на руку то, что взаимосвязь между таблицами ol$ (шаблоны) и ol$hints (подсказки) задается по имени шаблона (которое хранится в столбце ol_name ). Поэтому, особо внимательно проверяя имена, мы можем поменять подсказки в хранимых шаблонах, меняя местами имена в таблице ol$hints следующим образом:
update outln.ol$hints set ol_name = decode( ol_name, 'SO_FIX','SYS_OUTLINE_020503165427311', 'SYS_OUTLINE_020503165427311','SO_FIX' ) where ol_name in ('SYS_OUTLINE_020503165427311','SO_FIX');
Вас может смущать непосредственное изменение данных, настолько близких к ядру сервера Oracle, особенно с учетом комментариев в руководствах — но такое изменение, фактически, санкционировано документом Metalink Note: 92202.1 от 5 июня 2000 года. Однако в этом документе не сказано, что может понадобиться и другой оператор update , гарантирующий согласованность количества подсказок в каждом из хранимых шаблонов с самими подсказками. Если его не обеспечить, может оказаться, что некоторые из хранимых шаблонов повреждены или уничтожены в ходе экспорта/импорта.
update outln.ol$ ol1 set hintcount = ( select hintcount from ol$ ol2 where ol2.ol_name in ('SYS_OUTLINE_020503165427311',' SO_FIX') and ol2.ol_name != ol1.ol_name ) where ol1.ol_name in ('SYS_OUTLINE_020503165427311','SO_FIX');
После замены можно подключиться в новом сеансе, потребовать использовать хранимые шаблоны, повторно выполнить процедуру и завершить сеанс. С помощью sql_trace снова можно будет узнать, как же сервер Oracle фактически обрабатывал SQL-операторы. Чтобы потребовать от сервера Oracle использовать (измененный) шаблон, выполните команду:
alter session set use_stored_outline = demo;
Просмотрев файл трассировки, вы должны обнаружить, что для выполнения SQL-оператора теперь используется план с and_equal . (Если вы используете утилиту tkprof для обработки и изучения файла трассировки, то можете обнаружить в результатах два противоречащих друг другу плана. Первый, правильный, план должен показывать, что используется and_equal , а второй, скорее всего, будет показывать полный просмотр таблицы, поскольку хранимый шаблон мог и не использоваться когда утилита tkprof выполняла explain plan для протрассированного SQL-оператора).
От разработки — к внедрению
Теперь, когда нам удалось создать нужный шаблон, необходимо перенести его в производственную среду. Есть множество небольших полезных особенностей хранимых шаблонов, которые при этом пригодятся. Например, можно переименовать хранимый шаблон, экспортировать его с сервера разработчика, импортировать в производственной системе, проверить, что и там он работает правильно, поместив его в категорию ‘test’ , а затем перевести в производственную категорию. При этом пригодятся команды:
alter outline SYS_OUTLINE_020503165427311 rename to AND_EQUAL_SAMPLE; alter outline AND_EQUAL_SAMPLE change category to PROD_CAT;
Для экспортирования шаблона из среды разработки в производственную систему можно воспользоваться возможностью добавлять конструкцию where в файле параметров экспорта, что позволяет создать следующий файл:
userid=outln/outln tables=(ol$, ol$hints, ol$nodes) # ol$nodes существует только в версии 9 file=so.dmp consistent=y # очень важно rows=yes query='where ol_name = ''AND_EQUAL_SAMPLE'''
Дополнительные возможности Oracle 9
Есть множество других деталей, которые необходимо учитывать при работе с хранимыми шаблонами. Хранимые шаблоны в версии Oracle 8i имеют ряд неприятных и ограничивающих возможности особенностей. К счастью, многие проблемы решены в Oracle 9.
Самой тривиальной и очевидной проблемой при использовании хранимых шаблонов в Oracle 8 было то, что они могли использоваться только если сохраненный текст в точности совпадал с поступившим. В Oracle 9 применяется «нормализация», ослабляющая это требование совпадения; тексты операторов перед сравнением преобразуются в верхний регистр и все лишние пробелы убираются. Это повышает вероятность, что незначительно отличающиеся SQL-операторы смогут использовать тот же хранимый шаблон.
Есть также ряд проблем с более сложными планами выполнения, включающих несколько блоков запросов — корпорация Oracle решила их в Oracle 9, добавив третью таблицу в схему outln , ol$nodes . Она помогает серверу Oracle разбивать на части список подсказок в таблице ol$hints и учитывать их в соответствующих фрагментах обрабатываемого SQL-оператора. Это, конечно, хорошо, но может повлиять на стратегию замены подсказок одного хранимого шаблона на подсказки другого, поскольку в таблице ol$hints появились дополнительные детали о длине и смещении фрагментов текста. При переходе на Oracle 9 придется использовать другие методы создания хранимых шаблонов, такие как отдельные схемы со специально подобранными наборами данных или отсутствующими индексами, или представлениями со встроенными подсказками, имена которых совпадают с именами таблиц в тексте настраиваемых операторов.
Также в Oracle 9 появились дополнительные возможности создания требуемых хранимых шаблонов, в том числе, предварительная версия пакета, позволяющего непосредственно редактировать хранимые шаблоны. Важнее, однако, что появилась возможность более безопасной работы с планами, хранящимися в производственной системе. Хотя в производственной среде экспериментировать не любит никто, иногда производственная система — единственное место, где можно получить реальные распределения и объемы данных, позволяющие найти оптимальный план выполнения проблемного SQL-оператора. В Oracle 9 можно создать собственную копию таблиц outln и выбрать в них «общедоступные» хранимые шаблоны для «частных» экспериментов, не рискуя сделать один из экспериментальных хранимых шаблонов видимым для кода конечного пользователя. Лично я выполнял бы такую настройку только в крайнем случае, но вполне могу представить себе ситуацию, когда она может понадобиться. Менее опасно будет экспериментировать в отдельной тестовой системе или в системе разработчика, а там эта возможность позволит выполнять независимое тестирование.
Эта статья предоставляет достаточно информации для начала экспериментов с хранимыми шаблонами, но есть ряд нюансов, о которых надо знать, прежде чем начать применять шаблоны в производственной системе.
Во-первых, в Oracle 8i стандартный пароль для outln (схемы, которой принадлежат таблицы, используемые для размещения хранимых шаблонов) широко известен, а сама учетная запись имеет очень опасные привилегии. Обязательно поменяйте пароль этой учетной записи. В Oracle 9i вы обнаружите, что эта учетная запись заблокирована.
Во-вторых, таблицы, используемые для размещения хранимых шаблонов, создаются в табличном пространстве system . В производственной системе окажется, что при создании хранимых шаблонов вы используете очень много пространства в табличном пространстве system . Имеет смысл перенести эти таблицы в другое табличное пространство, предпочтительно, — специально для них созданное. К сожалению, одна из таблиц содержит столбец типа long , поэтому для переноса в новое табличное пространство, вероятно, придется использовать exp / imp .
В-третьих, хотя хранимые шаблоны и весьма полезны для решения критических проблем производительности, их использование не дается даром. При активации хранимых шаблонов сервер Oracle проверяет, нет ли подходящего шаблона при анализе каждого нового оператора. Если для множества операторов хранимых шаблонов нет, надо сопоставлять расходы на это дополнительное действие с преимуществами, которые получаются от использования хранимых шаблонов для нескольких избранных операторов. Правда, это, скорее всего, придется учитывать только в системе, имеющей другие, более существенные проблемы производительности.
Использование хранимых шаблонов может дать потрясающие результаты. Если нельзя менять исходный код или стратегию индексирования, хранимый шаблон может оказаться единственным способом заставить приложение стороннего производителя работать эффективно.
Доводя идею до крайности, если перед вами все еще стоит проблема перевода системы с оптимизатора, основанного на правилах, на использование стоимостного оптимизатора, использование хранимых шаблонов может оказаться наиболее выгодным и надежным вариантом ее решения.
Если необходимо получить от хранимых шаблонов максималоную отдачу, используйте версии Oracle 9, в которых имеется ряд расширений, позволяющих покрыть дополнительные классы SQL-операторов, сократить накладные расходы, а также обеспечивающих больше возможностей при тестировании, управлении и установке хранимых шаблонов.
Работа с отслеживанием изменений (SQL Server)
Приложения, в которых используется отслеживание изменений, должны иметь возможность получать отслеженные изменения, применять эти изменения к другому хранилищу данных и обновлять базу данных-источник. В этой статье описывается выполнение этих задач, а также отслеживание изменений ролей играет при отработки отказа, а база данных должна быть восстановлена из резервной копии.
Получение изменений с помощью функций отслеживания изменений
Описывает, как использовать функции отслеживания изменений, чтобы получить изменения и сведения об изменениях, произведенных в базе данных.
Сведения о функциях отслеживания изменений
Чтобы получить изменения из базы данных и сведения об этих изменениях, приложения могут использовать следующие функции.
Функция CHANGETABLE(CHANGES . )
Эта функция, возвращающая набор строк, используется в запросе данных отслеживания изменений. Функция запрашивает данные, хранящиеся во внутренних таблицах отслеживания изменений. Она возвращает результирующий набор, содержащий первичные ключи измененных строк, а также другие сведения: операцию, число обновленных столбцов и версии строк.
Функция CHANGETABLE(CHANGES . ) получает в качестве аргумента последнюю версию синхронизации. Версию последней синхронизации можно получить с помощью переменной @last_synchronization_version . Семантика последней версии синхронизации выглядит следующим образом.
- Вызывающий клиент получает сведения обо всех изменениях, происшедших до момента последней синхронизированной версии включительно.
- Функция CHANGETABLE(CHANGES . ) поэтому возвращает сведения обо всех изменениях, произошедших после последней версии синхронизации. Ниже показано, как функция CHANGETABLE(CHANGES . ) используется для получения изменений. В этом примере клиент A последний синхронизирован в 9:30, а клиент B последний синхронизирован в 10:30. В 10:00 и снова в 11:00 были внесены некоторые изменения в данные. Эти отслеживаемые изменения приведены ниже. CHANGETABLE(CHANGE. ) Выходные данные — 11:30Клиент A последний синхронизирован в 9:30 утра.
Код продукта Операция Столбцы 139 Изменить Имя, цена 140 Удаление — 141 Insert — Клиент B последний синхронизирован в 10:30.
Код продукта Операция Столбцы 139 Изменить Цена 140 Удаление — 141 Изменить Цена
Функция CHANGE_TRACKING_CURRENT_VERSION()
Используется для получения текущей версии, которая будет использоваться при следующем запросе изменений. Эта версия представляет версию последней зафиксированной транзакции.
Функция CHANGE_TRACKING_MIN_VALID_VERSION()
Используется для получения минимальной допустимой версии, которую может иметь клиент и по-прежнему получать допустимые результаты из CHANGETABLE(). Клиент должен сравнить последнюю версию синхронизации со значением, возвращенным этой функцией. Если номер последней версии синхронизации меньше, чем возвращает эта функция, то он не сможет получить достоверные результаты из функции CHANGETABLE() и должен повторно инициализировать данные.
Получение исходных данных
Прежде чем приложение в первый раз получит изменения, оно должно выполнить запрос для получения первоначальных данных и версии синхронизации. Приложение должно получить соответствующие данные напрямую из таблицы, а затем вызвать функцию CHANGE_TRACKING_CURRENT_VERSION() для получения первоначальной версии. Эта версия передается функции CHANGETABLE(CHANGES . ) при первом получении изменений.
В следующем примере показано получение первоначальной версии синхронизации и первоначального набора данных.
declare @synchronization_version bigint; -- Obtain the current synchronization version. This will be used next time that changes are obtained. SET @synchronization_version = CHANGE_TRACKING_CURRENT_VERSION(); -- Obtain initial data set. SELECT P.ProductID, P.Name, P.ListPrice FROM SalesLT.Product AS P;
Использование функций отслеживания изменений для получения изменений
Чтобы получить измененные строки для таблицы и сведения об изменениях, используйте CHANGETABLE(CHANGES. ). Например, следующий запрос получает изменения для SalesLT.Product таблицы.
declare @last_synchronization_version bigint; SELECT CT.ProductID, CT.SYS_CHANGE_OPERATION, CT.SYS_CHANGE_COLUMNS, CT.SYS_CHANGE_CONTEXT FROM CHANGETABLE(CHANGES SalesLT.Product, @last_synchronization_version) AS CT;
Как правило, клиенту нужно получить последние данные строки, а не просто первичные ключи. Поэтому приложение соединяет результаты запроса функции CHANGETABLE(CHANGES . ) с данными в пользовательской таблице. Например, следующий запрос соединяется с таблицей SalesLT.Product , чтобы получить значения столбцов Name и ListPrice . Обратите внимание на использование OUTER JOIN . Это требуется, чтобы убедиться, что возвращаются сведения об изменениях в строках, удаленных из пользовательской таблицы.
SELECT CT.ProductID, P.Name, P.ListPrice, CT.SYS_CHANGE_OPERATION, CT.SYS_CHANGE_COLUMNS, CT.SYS_CHANGE_CONTEXT FROM SalesLT.Product AS P RIGHT OUTER JOIN CHANGETABLE(CHANGES SalesLT.Product, @last_synchronization_version) AS CT ON P.ProductID = CT.ProductID;
Чтобы получить версию, которая будет использоваться в следующем перечислении изменений, используется функция CHANGE_TRACKING_CURRENT_VERSION(), как показано в следующем примере.
SET @synchronization_version = CHANGE_TRACKING_CURRENT_VERSION();
Когда приложение получает изменения, оно должно использовать как функцию CHANGETABLE(CHANGES. ), так и CHANGE_TRACKING_CURRENT_VERSION(), как показано в следующем примере.
-- Obtain the current synchronization version. This will be used the next time CHANGETABLE(CHANGES. ) is called. SET @synchronization_version = CHANGE_TRACKING_CURRENT_VERSION(); -- Obtain incremental changes by using the synchronization version obtained the last time the data was synchronized. SELECT CT.ProductID, P.Name, P.ListPrice, CT.SYS_CHANGE_OPERATION, CT.SYS_CHANGE_COLUMNS, CT.SYS_CHANGE_CONTEXT FROM SalesLT.Product AS P RIGHT OUTER JOIN CHANGETABLE(CHANGES SalesLT.Product, @last_synchronization_version) AS CT ON P.ProductID = CT.ProductID;
Номера версий
База данных, для которой включено отслеживание изменений, содержит счетчик версий, который увеличивается при каждом изменении отслеживаемых таблиц. Каждой изменяемой строке присваивается собственный номер версии. Когда в приложение отправляется запрос изменений, вызывается функция, возвращающая номер версии. Эта функция возвращает сведения обо всех изменениях, которые были сделаны после этой версии. В некотором роде версия отслеживания изменений напоминает тип данных rowversion .
Проверка последней синхронизированной версии
Сведения об изменениях хранятся ограниченное время. Длительность этого времени управляется параметром CHANGE_RETENTION, который указывается в инструкции ALTER DATABASE.
Время, указанное для CHANGE_RETENTION, определяет, насколько часто все приложения должны запрашивать изменения из базы данных. Если приложение имеет значение для last_synchronization_version , которое старше минимальной допустимой версии синхронизации для таблицы, это приложение не может выполнить допустимое перечисление изменений. Это объясняется тем, что некоторые данные изменений могли быть очищены. Прежде чем приложение получает изменения с помощью CHANGETABLE(CHANGES . ), приложение должно проверить значение last_synchronization_version , что оно планирует передать в CHANGETABLE(CHANGES . ). Если значение last_synchronization_version недопустимо, это приложение должно повторно инициализировать все данные.
В следующем примере показано, как проверять достоверность значения last_synchronization_version для каждой таблицы.
-- Check individual table. IF (@last_synchronization_version < CHANGE_TRACKING_MIN_VALID_VERSION( OBJECT_ID('SalesLT.Product'))) BEGIN -- Handle invalid version and do not enumerate changes. -- Client must be reinitialized. END;
Как показано в следующем примере, достоверность значения last_synchronization_version можно проверить для всех таблиц в базе данных.
-- Check all tables with change tracking enabled IF EXISTS ( SELECT 1 FROM sys.change_tracking_tables WHERE min_valid_version > @last_synchronization_version ) BEGIN -- Handle invalid version & do not enumerate changes -- Client must be reinitialized END;
Использование отслеживания столбцов
Отслеживание столбцов позволяет приложениям получать изменившиеся данные не всей строки, а только одного столбца. Например, рассмотрим ситуацию, когда в таблице имеются один или два больших, но редко изменяющихся столбца и другие столбцы, значения которых часто меняются. Не применяя отслеживание столбцов, приложение может только определить изменение строки и синхронизировать все данные, включая данные больших столбцов. Но с помощью отслеживания столбцов приложение может определить, в каких столбцах изменились значения, и синхронизировать только изменившиеся данные.
Сведения об отслеживании столбцов отображаются в столбце SYS_CHANGE_COLUMNS, который возвращается функцией CHANGETABLE(CHANGES . ).
Отслеживание столбцов можно использовать, чтобы значение NULL возвращалось для столбца, который не изменился. Если значение NULL недопустимо для того или иного столбца, можно возвратить отдельный столбец, который укажет, изменялись ли данные этого столбца.
В следующем примере столбец будет иметь NULL значение, CT_ThumbnailPhoto если этот столбец не изменился. Этот столбец может также принимать значение NULL , потому что он был изменен на NULL , поэтому приложение может использовать столбец CT_ThumbNailPhoto_Changed , чтобы определить, происходили ли изменения.
DECLARE @PhotoColumnId int = COLUMNPROPERTY( OBJECT_ID('SalesLT.Product'),'ThumbNailPhoto', 'ColumnId'); SELECT CT.ProductID, P.Name, P.ListPrice, -- Always obtain values. CASE WHEN CHANGE_TRACKING_IS_COLUMN_IN_MASK( @PhotoColumnId, CT.SYS_CHANGE_COLUMNS) = 1 THEN ThumbNailPhoto ELSE NULL END AS CT_ThumbNailPhoto, CHANGE_TRACKING_IS_COLUMN_IN_MASK( @PhotoColumnId, CT.SYS_CHANGE_COLUMNS) AS CT_ThumbNailPhoto_Changed, CT.SYS_CHANGE_OPERATION, CT.SYS_CHANGE_COLUMNS, CT.SYS_CHANGE_CONTEXT FROM SalesLT.Product AS P INNER JOIN CHANGETABLE(CHANGES SalesLT.Product, @last_synchronization_version) AS CT ON P.ProductID = CT.ProductID AND CT.SYS_CHANGE_OPERATION = 'U';
Получение согласованных и правильных результатов
Для получения измененных данных для таблицы требуется несколько шагов. Несогласованные или неправильные результаты могут быть возвращены, если некоторые проблемы не рассматриваются и обрабатываются.
Например, чтобы получить изменения, внесенные в таблицу Sales и SalesOrders таблицу, приложение выполнит следующие действия:
- Проверить последнюю синхронизированную версию с помощью функции CHANGE_TRACKING_MIN_VALID_VERSION().
- Получить версию, которая будет использована для получения изменений в следующий раз, с помощью функции CHANGE_TRACKING_CURRENT_VERSION().
- Получите изменения для Sales таблицы с помощью CHANGETABLE(CHANGES . ).
- Получите изменения для SalesOrders таблицы с помощью CHANGETABLE(CHANGES . ).
В базе данных выполняются следующие два процесса, которые могут повлиять на результаты, возвращаемые на предыдущих шагах.
- Процесс очистки выполняется в фоновом режиме и удаляет данные отслеживания изменений старше указанного срока хранения. Процесс очистки является отдельным фоновым процессом и руководствуется сроком хранения, заданным при настройке отслеживания изменений в базе данных. Проблема заключается в том, что он может выполняться в промежуток между проверкой последней версии синхронизации и вызовом функции CHANGETABLE(CHANGES. ). Последняя версия синхронизации, которая была действительна, может быть недействительна к моменту получения изменений. Поэтому функция может возвратить неверные результаты.
- Текущие операции DML выполняются в продажах и SalesOrders таблицах, таких как следующие операции:
- В таблицах могут происходить изменения после того, как с помощью функции CHANGE_TRACKING_CURRENT_VERSION() была получена последняя версия синхронизации. Следовательно, функция может вернуть больше изменений, чем ожидалось.
- Транзакция может зафиксировать время между вызовом получения изменений из Sales таблицы и вызовом получения изменений из SalesOrders таблицы. Таким образом, результаты таблицы SalesOrder могут иметь значение внешнего ключа, которое не существует в Sales таблице.
Для преодоления приведенных выше проблем рекомендуется применять изоляцию моментального снимка. Это позволит обеспечить согласованность сведений об изменениях и избежать соперничества при выполнении задач фоновой очистки. Если вы не используете транзакции моментальных снимков, разработка приложения, использующего отслеживание изменений, может потребовать значительно больше усилий.
Использование изоляции моментальных снимков
Отслеживание изменений было разработано для слаженной работы вместе с изоляцией моментального снимка. В базе данных необходимо активировать изоляцию моментального снимка. Все шаги, необходимые для получения изменений, должны включаться в транзакцию моментального снимка. Это гарантирует, что все изменения, внесенные в данные при получении изменений, не будут видны запросам внутри транзакции моментального снимка.
Чтобы получить данные в транзакции моментального снимка, выполните следующие действия.
- Установите уровень изоляции транзакции на уровень моментальных снимков и запустите транзакцию.
- Проверьте последнюю версию синхронизации с помощью функции CHANGE_TRACKING_MIN_VALID_VERSION().
- Получите версию, которая будет использоваться в следующий раз, с помощью функции CHANGE_TRACKING_CURRENT_VERSION().
- Получение изменений для Sales таблицы с помощью CHANGETABLE(CHANGES . )
- Получение изменений для SalesOrders таблицы с помощью CHANGETABLE(CHANGES . )
- Зафиксируйте транзакцию.
Необходимо помнить, что все действия для получения изменений находятся внутри транзакции моментальных снимков.
- Если очистка происходит после последней версии синхронизации, результаты из CHANGETABLE(CHANGES . ) по-прежнему будут допустимы, так как операции удаления, выполняемые очисткой, не будут отображаться внутри транзакции.
- Любые изменения, внесенные в Sales таблицу или SalesOrders таблицу после получения следующей версии синхронизации, не будут видны, и вызовы CHANGETABLE(CHANGES . ) никогда не будут возвращать изменения с версией позже, чем возвращенная CHANGE_TRACKING_CURRENT_VERSION(). Согласованность между Sales таблицей и SalesOrders таблицей также будет поддерживаться, так как транзакции, зафиксированные во время между вызовами CHANGETABLE(CHANGES . ), не будут видны.
В следующем примере показано включение изоляции моментального снимка в базе данных.
-- The database must be configured to enable snapshot isolation. ALTER DATABASE AdventureWorksLT SET ALLOW_SNAPSHOT_ISOLATION ON;Транзакция моментального снимка используется следующим образом.
SET TRANSACTION ISOLATION LEVEL SNAPSHOT; BEGIN TRAN -- Verify that version of the previous synchronization is valid. -- Obtain the version to use next time. -- Obtain changes. COMMIT TRANДополнительные сведения о транзакциях моментальных снимков см. в разделе SET TRANSACTION ISOLATION LEVEL (Transact-SQL).
Очистка и изоляция моментальных снимков
Включение изоляции моментальных снимков и отслеживания изменений в одной базе данных или в двух разных базах данных в одном экземпляре может привести к удалению строк sys.syscommittab с истекшим сроком действия при наличии открытой транзакции в базе данных с изоляцией моментального снимка. Это может произойти, поскольку процесс очистки с отслеживанием изменений учитывает нижнюю конечную точку в масштабе всего экземпляра (которая является безопасной версией очистки) при выполнении очистки. Это делается, чтобы процесс автоматической очистки отслеживания изменений не удалял строки, которые могут потребоваться открытой транзакцией в базе данных с включенной изоляцией моментальных снимков. Сохраняйте фиксацию моментальных снимков чтения и транзакции изоляции моментальных снимков как можно короче, чтобы обеспечить своевременное очистку просроченных строк sys.syscommittab .
Альтернативные варианты изоляции моментальных снимков
Существуют альтернативы использованию изоляции моментального снимка, но они требуют больше усилий, чтобы обеспечить требования всех приложений. Чтобы убедиться, что last_synchronization_version является допустимым и данные не удаляются процессом очистки перед получением изменений, выполните следующие действия.
- Проверьте параметр last_synchronization_version после вызовов функции CHANGETABLE().
- Проверяйте параметр last_synchronization_version в составе каждого запроса, чтобы получить изменения с помощью функции CHANGETABLE().
Изменения могут происходить после получения версии синхронизации для следующего перечисления. Для обхода этой ситуации существует два способа. Используемый вариант зависит от приложения и обработки побочных эффектов каждого подхода.
- Пропускайте изменения с версиями больше, чем новая версия синхронизации. Побочный эффект этого подхода заключается в том, что новая или обновленная строка будет пропущена, если она была создана или обновлена перед версией новой синхронизации, а затем обновлена после нее. Если есть новая строка, проблема целостности ссылок может возникнуть, если в другой таблице существовала строка, которая ссылалась на пропущенную строку. Если есть обновленная строка, строка будет пропущена и не синхронизирована до следующего раза.
- Включайте все изменения, даже с версиями больше, чем версия новой синхронизации. Строки с версиями больше, чем версия новой синхронизации, будут опять получены во время следующей синхронизации. Приложение должно предполагать такую ситуацию и обрабатывать ее.
Кроме двух предыдущих вариантов можно разработать подход, сочетающий обе возможности в зависимости от операции. Например, может потребоваться, чтобы приложение, для которого лучше игнорировать изменения, более новые, чем следующая версия синхронизации, в которой была создана или удалена строка, но обновления не игнорируются.
Выбор оптимального подхода для приложения, работающего с отслеживанием изменений, или любого пользовательского средства отслеживания изменений требует тщательного анализа. Поэтому намного проще использовать изоляцию моментального снимка.
Как отслеживание изменений обрабатывает изменения в базе данных
Некоторые приложения, использующие отслеживание изменений, выполняют двустороннюю синхронизацию с другими хранилищами данных. То есть изменения, внесенные в базу данных SQL Server, обновляются в другом хранилище данных, а изменения, внесенные в другое хранилище, обновляются в базе данных SQL Server.
Если приложение обновляет локальную базу данных изменениями, сделанными в другом хранилище данных, оно должно выполнить следующие операции.
- Проверить наличие конфликтов. Конфликт возникает, когда одни и те же данные одновременно изменяются в обоих хранилищах данных. Приложение должно иметь возможность проверить наличие конфликта и получить достаточно информации для его разрешения.
- Сохранить контекстные сведения приложения. Приложение хранит данные, с которыми связана информация отслеживания изменений. Эта информация должна быть доступна вместе с другими данными отслеживания изменений, если изменения были получены из локальной базы данных. Типичный пример таких контекстных сведений – идентификатор хранилища данных, которое было источником изменений.
Для выполнения вышеуказанных операций приложение синхронизации может использовать следующие функции:
- CHANGETABLE(VERSION. ) Когда приложение выполняет изменения, оно может использовать эту функцию для проверки конфликтов. Функция получает последние данные отслеживания изменений в заданной строке в таблицу отслеживания изменений. Эти данные содержат версию последней измененной строки. Это позволяет приложению определить, изменилась ли строка с момента последней синхронизации приложения.
- WITH CHANGE_TRACKING_CONTEXT Приложение может использовать это предложение для хранения контекстных данных.
Проверка наличия конфликтов
В двустороннем сценарии синхронизации клиентское приложение должно определить, не была ли строка обновлена с момента последнего получения изменений приложением.
В следующем примере показано использование функции CHANGETABLE(VERSION . ) для проверки конфликтов самым эффективным способом — без отдельного запроса. В примере CHANGETABLE(VERSION . ) определяет SYS_CHANGE_VERSION для строки, заданной аргументом @product id . CHANGETABLE(CHANGES . ) может получить те же сведения, но это будет менее эффективным. Если значение SYS_CHANGE_VERSION строки больше значения @last_sync_version , возникает конфликт. Если возникает конфликт, строка не будет обновлена. Проверка ISNULL() необходима, поскольку для строки может не иметься информации об изменениях. Если строка не была обновлена, так как отслеживание изменений было включено или после очистки информации об изменении не было.
-- Assumption: @last_sync_version has been validated. UPDATE SalesLT.Product SET ListPrice = @new_listprice FROM SalesLT.Product AS P WHERE ProductID = @product_id AND @last_sync_version >= ISNULL ( SELECT CT.SYS_CHANGE_VERSION FROM CHANGETABLE(VERSION SalesLT.Product, (ProductID), (P.ProductID)) AS CT), 0);Следующий код проверяет обновленное число строк и может предоставить более подробные сведения о конфликте.
-- If the change cannot be made, find out more information. IF (@@ROWCOUNT = 0) BEGIN -- Obtain the complete change information for the row. SELECT CT.SYS_CHANGE_VERSION, CT.SYS_CHANGE_CREATION_VERSION, CT.SYS_CHANGE_OPERATION, CT.SYS_CHANGE_COLUMNS FROM CHANGETABLE(CHANGES SalesLT.Product, @last_sync_version) AS CT WHERE CT.ProductID = @product_id; -- Check CT.SYS_CHANGE_VERSION to verify that it really was a conflict. -- Check CT.SYS_CHANGE_OPERATION to determine the type of conflict: -- update-update or update-delete. -- The row that is specified by @product_id might no longer exist -- if it has been deleted. ENDНастройка сведений о контексте
С помощью предложения WITH CHANGE_TRACKING_CONTEXT приложение может хранить контекстные данные вместе с информацией об изменениях. Затем эти данные можно получить в столбце SYS_CHANGE_CONTEXT, который возвращает функция CHANGETABLE(CHANGES . ).
Контекстные данные обычно используются для определения источника изменений. Если источник изменений можно определить, эти данные могут использоваться хранилищем данных, чтобы не получать изменения при повторной синхронизации.
-- Try to update the row and check for a conflict. WITH CHANGE_TRACKING_CONTEXT (@source_id) UPDATE SalesLT.Product SET ListPrice = @new_listprice FROM SalesLT.Product AS P WHERE ProductID = @product_id AND @last_sync_version >= ISNULL ( (SELECT CT.SYS_CHANGE_VERSION FROM CHANGETABLE(VERSION SalesLT.Product, (ProductID), (P.ProductID)) AS CT), 0);Обеспечение согласованности и правильности результатов
Приложение должно учитывать процесс очистки при проверке значения параметра @last_sync_version. Это объясняется тем, что данные могли быть удалены после вызова функции CHANGE_TRACKING_MIN_VALID_VERSION(), но перед тем, как было выполнено обновление.
Следует использовать изоляцию моментальных снимков и вносить изменения в транзакцию моментального снимка.
-- Prerequisite is to ensure ALLOW_SNAPSHOT_ISOLATION is ON for the database. SET TRANSACTION ISOLATION LEVEL SNAPSHOT; BEGIN TRAN -- Verify that last_sync_version is valid. IF (@last_sync_version < CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID('SalesLT.Product'))) BEGIN RAISERROR (N'Last_sync_version too old', 16, -1); END ELSE BEGIN -- Try to update the row. -- Check @@ROWCOUNT and check for a conflict. END; COMMIT TRAN;Существует вероятность того, что обновляемая в рамках транзакции моментальных снимков строка уже была обновлена в другой транзакции после начала транзакции моментальных снимков. В этом случае произойдет конфликт обновления при изоляции моментальных снимков, который вызовет прекращение транзакции. В этом случае повторите попытку обновления. В дальнейшем это приведет к обнаружению конфликта отслеживания изменений и ни одна из строк обновлена не будет.
Отслеживание изменений и восстановление данных
Приложения, для которых необходима синхронизация, должны учитывать возможность восстановления базы данных, для которой включено отслеживание изменений, в предыдущее состояние. Это может произойти после восстановления базы данных из резервной копии, при отработке отказа в асинхронное зеркальное отображение базы данных или при использовании доставки журналов. Эту проблему иллюстрирует следующий сценарий.
- Таблица T1 отслеживается, а минимальная допустимая версия таблицы — 50.
- Клиентское приложение синхронизирует данные версии 100 и получает все данные отслеживания изменений, произведенных с версии 50 до версии 100.
- После версии 100 в таблицу Т1 были внесены дополнительные изменения.
- В версии 120 возникает сбой, а администратор базы данных восстанавливает базу данных с потерей данных. После завершения восстановления таблица содержит данные до версии 70, а минимальная синхронизированная версия по-прежнему равна 50. Это означает, что синхронизированное хранилище данных содержит данные, которых уже нет в первичном хранилище.
- Таблица Т1 обновлялась многократно. Текущий номер версии для нее — 130.
- Клиентское приложение синхронизируется вновь и получает номер последней синхронизации 100. Проверка этого номера клиентом происходит успешно, так как 100 больше 50. Клиент получает изменения между версиями 100 и 130. На этом этапе клиент не знает, что изменения в диапазоне от 70 до 100 не совпадают с ранее. Данные на клиенте и сервере не синхронизируются.
Если база данных была восстановлена до точки после версии 100, не будет проблем с синхронизацией. Клиент и сервер должны правильно синхронизировать данные во время следующего интервала синхронизации.
Отслеживание изменений не обеспечивает поддержку восстановления после потери данных. Однако существует две возможности обнаружения проблем синхронизации такого рода.
- Сохраните идентификатор версии базы данных на сервере и обновляйте это значение при каждом восстановлении данных или их потере, вызванной любыми другими причинами. Этот идентификатор должно хранить и проверять во время синхронизации данных каждое клиентское приложение. Если происходит потеря данных, идентификаторы не будут совпадать, и клиенты будут повторно инициализировать. Один из недостатков заключается в том, что потеря данных не пересекла последнюю синхронизированную границу, клиент может сделать ненужную повторную инициализацию.
- Когда клиент делает запрос об изменениях, сохраните номер последней версии синхронизации каждого клиента сервера. Если возникла проблема с данными, последние синхронизированные номера версий не совпадают. Это будет означать, что необходима повторная инициализация.
См. также
- Отслеживание изменений данных (SQL Server)
- Сведения об отслеживании изменений (SQL Server)
- Управление отслеживанием изменений (SQL Server)
- Включение и отключение отслеживания изменений (SQL Server)
- CHANGETABLE (Transact-SQL)
- CHANGE_TRACKING_MIN_VALID_VERSION (Transact-SQL)
- CHANGE_TRACKING_CURRENT_VERSION (Transact-SQL)
- WITH CHANGE_TRACKING_CONTEXT (Transact-SQL)
Анализ технологий репликации данных и методы повышения эффективности разрешения конфликтов репликации Текст научной статьи по специальности «Компьютерные и информационные науки»
Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Гришмановский Павел Валерьевич, Базилевский Евгений Викторович
Различные фирмы и корпорации предлагают конечным пользователям и промышленным предприятиям СУБД различной функциональности. Давно признана перспективность распределенных баз данных , но соответствующие технологии либо слишком ограниченны, либо чересчур сложны. В данной статье представлен анализ и сравнение технологий репликации, применяемых в продуктах ведущих компаний, разрабатывающих СУБД, включая методы разрешения конфликтов репликации. Несмотря на наличие достаточно развитых средств репликации, их практическое применение не всегда удовлетворяет существующим потребностям предприятий. Это вынуждает искать альтернативные решения. Представленные в данной статье методы разрешения конфликтов репликации отличаются от предлагаемых производителем СУБД большей производительностью и эффективностью, а их внедрение дает предприятию экономическую выгоду.
i Надоели баннеры? Вы всегда можете отключить рекламу.
Похожие темы научных работ по компьютерным и информационным наукам , автор научной работы — Гришмановский Павел Валерьевич, Базилевский Евгений Викторович
Временной анализ обработки конфликтных транзакций в Oracle Enterprise Manager и ErrManager
Анализ методов репликации баз данных при разработке онлайн-сервиса
Алгоритм динамического масштабирования реляционных баз данных в облачных средах
Репликация данных в иерархических информационных системах с непостоянной связностью узлов
Метрики для динамического масштабирования баз данных в облачных средах
i Не можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.THE ANALYSIS OF EXISTING TECHNOLOGIES OF DATA REPLICATION AND METHODS OF EFFICIENCY IMPROVING AT REPLICATION CONFLICTS' RESOLVING SPHERE
A lot of different companies and corporations offer DBMS with variable functionality to end-users and industries today. The prospects of distributed databases have long been recognized, but current technologies are either too limited or too complicated. Analysis and comparison of replication technologies, used in products of leading companies DBMS developers, including methods of replication conflicts' resolving are presented in this article. Despite of the presence of a sufficiently advanced replication means, their practical usage does not always satisfy the existing needs of enterprises. This forces the search of alternative solutions. Methods of replication conflicts ' resolving, presented in this article, have greater productivity and efficiency then suggested by database developers ones, and their implementation provides economic benefits to the enterprise.
Текст научной работы на тему «Анализ технологий репликации данных и методы повышения эффективности разрешения конфликтов репликации»
УДК 004.042+004.658.6 ББК 32.973.26-018.2
Гришмановский П.В., Базилевский Е.В.
АНАЛИЗ ТЕХНОЛОГИЙ РЕПЛИКАЦИИ ДАННЫХ И МЕТОДЫ ПОВЫШЕНИЯ ЭФФЕКТИВНОСТИ РАЗРЕШЕНИЯ КОНФЛИКТОВ
GrishmanovskyP. V., BazilevskiyE. V.
THE ANALYSIS OF EXISTING TECHNOLOGIES OF DATA REPLICATION AND METHODS OF EFFICIENCY IMPROVING AT REPLICATION CONFLICTS’ RESOLVING SPHERE
Ключевые слова: база данных, репликация данных, конфликты репликации, Microsoft SQL Server, Sybase SQL Server System, Oracle.
Keywords: database, data replication, replication conflicts, Microsoft SQL Server, Sybase SQL Server System, Oracle
Различные фирмы и корпорации предлагают конечным пользователям и промышленным предприятиям СУБД различной функциональности. Давно признана перспективность распределенных баз данных, но соответствующие технологии либо слишком ограниченны, либо чересчур сложны. В данной статье представлен анализ и сравнение технологий репликации, применяемых в продуктах ведущих компаний, разрабатывающих СУБД, включая методы разрешения конфликтов репликации. Несмотря на наличие достаточно развитых средств репликации, их практическое применение не всегда удовлетворяет существующим потребностям предприятий. Это вынуждает искать альтернативные решения. Представленные в данной статье методы разрешения конфликтов репликации отличаются от предлагаемых производителем СУБД большей производительностью и эффективностью, а их внедрение дает предприятию экономическую выгоду.
A lot of different companies and corporations offer DBMS with variable functionality to end-users and industries today. The prospects of distributed databases have long been recognized, but current technologies are either too limited or too complicated. Analysis and comparison of replication technologies, used in products of leading companies - DBMS developers, including methods of replication conflicts' resolving are presented in this article. Despite of the presence of a sufficiently advanced replication means, their practical usage does not always satisfy the existing needs of enterprises. This forces the search of alternative solutions. Methods of replication conflicts' resolving, presented in this article, have greater productivity and efficiency then suggested by database developers ones, and their implementation provides economic benefits to the enterprise.
В настоящее время огромные объемы информации, обрабатываемой в различных сферах человеческой деятельности, большое количество и территориальная удаленность конечных пользователей вынуждают искать решения оперативного взаимодействия с массивами данных. Распределенные базы данных, в отличие от централизованных, призваны сократить объемы информации, передаваемые по телекоммуникационным линиям, снизить стоимость телекоммуникационного оборудования и повысить оперативность доступа к актуальным данным.
Хотя перспективность распределенных баз данных уже давно признана, до недавнего времени соответствующая технология была либо слишком ограниченной, либо чересчур сложной. В настоящее время у каждой из компаний-лидеров в области разработки СУБД имеется решение относительно реплицирования баз данных, а многие компании, для которых разработка СУБД не является основным направлением деятельности, предлагают альтернативные методы репликации. Так как эта задача особенно актуальна для крупных корпораций, то и наиболее адекватную реализацию распределенных баз данных следует ожидать от производителей СУБД, ориентированных на корпоративные решения.
Стоит отметить, что рынок систем управления базами данных остается прибыльным и весьма перспективным, так как невозможно предоставлять клиентам комплексные решения без использования СУБД. В результате, все крупные софтверные компании имеют интересы на рынке СУБД. Сегодня в этой области наиболее активными крупными игроками являются корпорации IBM, Microsoft, Oracle, а также Sybase, Cache и др. По данным за 2010 г. лидером стала Oracle, владеющая долей 69,9%, второе место занимает Microsoft (8,5%), а третье - корпорация IBM (7,2%). Четвертое место аналитики присудили фирме Sybase, доля рынка которой составляет 0,7% [5].
Актуальность задач репликации в распределённых базах данных
Наиболее распространенными сценариями репликации являются: простая
репликация с разрешением только для чтения реплик; репликация к мобильному клиенту и от него; разрешение множественных обновлений.
В первом случае используется концепция трехзвенной серверной архитектуры, которая включает центральный сервер и два или более серверов рабочих групп, а третьим звеном является клиент. При репликации только по чтению данные вводятся на каждом сервере рабочих групп и хранятся там же, а затем реплицируются на центральный сервер (консолидируются). Таким образом, на центральном сервере будет содержаться только читаемая копия всех данных со всех серверов рабочих групп. Вариантами этого сценария, не меняющими его сути, являются следующие:
- данные вводятся только на центральный сервер, а серверы рабочих групп хранят реплики, доступные только для чтения;
- данные вводятся на один из серверов рабочих групп и копируются на один или более серверов рабочих групп и центральный с доступом к копиям только по чтению.
В последние годы мобильные компьютеры стали гораздо более доступными, а телекоммуникационные каналы обеспечивают высокую пропускную способность, поэтому все чаще люди работают удаленно, вне офиса. Одним из методов обеспечения мобильного доступа к данным является репликация. В этом случае данные для мобильного клиента сбрасываются по требованию с локального сервера рабочих групп, то есть осуществляется, по сути, кэширование актуальных данных на стороне клиента, а обновления данных на серверах рабочих групп или центральном сервере выполняются аналогично рассмотренному выше.
В отличие от первых двух сценариев, реализация множественных обновлений представляет собой довольно сложный и трудоёмкий процесс при разработке систем с репликацией, так как это предполагает возможность изменения одних и тех же данных на разных узлах распределенной базы данных с последующей репликацией изменений на другие узлы. Традиционное решение состоит в назначении одного сервера-источника, что приводит к архитектуре, близкой к первому сценарию, и практически не дает преимуществ перед ним. Более сложная технология репликации допускает наличие архитектуры с множественными обновлениями между несколькими равноправными узлами, что потенциально влечет конфликты одновременного изменения данных. При этом администратор системы определяет, какие данные следует считать корректными в
случае конфликта. Алгоритмы разрешения конфликтов репликации обычно основаны на одном из следующих методов:
- задание уникальных приоритетов серверов: серверы с более высоким
приоритетом «выигрывают» у серверов с более низким приоритетом;
- использование временных меток: корректной считается наиболее старая или наиболее молодая транзакция, участвующая к конфликте, в зависимости от определенной администратором стратегии;
- разделение данных: гарантируется, что каждой записью манипулирует только один сервер, блокируя её, но это фактически приводит архитектуру к первому сценарию.
На количество конфликтов репликации также существенно влияет порядок автоматической генерации ключей. Как правило, в базах данных используются автоматически сгенерированные суррогатные ключи, которые уникально идентифицируют строки данных. Когда данные для одной таблицы создаются на нескольких серверах, необходимо гарантировать уникальность реплицированных строк одним из способов:
- установить разные диапазоны генераторов ключей для каждого сервера;
- добавить идентификатор сервера в качестве составной части к первичному
- читать все данные, реплицированные в разные таблицы, через представление с объединением, а для работы с ключами-дубликатами использовать псевдостолбец, идентифицирующий базу данных-источник.
Следует отметить, что именно разрешение множественных обновлений, при всей сложности реализации, позволяет наиболее полно использовать потенциал распределённых баз данных.
Решения в области репликации данных от ведущих производителей СУБД
В СУБД Microsoft SQLServer используется механизм публикации и подписки. После того, как данные одной из баз данных (публикатора) сделаны доступными всем, другая база данных (подписчик) получает реплицированные данные (публикации) при их изменении. Узел, который выполняет передачу данных от публикатора к подписчику, называется распространителем. Таким образом, в архитектуру должны быть включены реплицируемые данные (публикации), база данных-источник (публикатор), распространитель и база данных-приемник (подписчик), а также определен способ работы с данными подписчика (чтение или обновление) и то, как часто требуется репликация.
С использованием развитого инструментария Microsoft SQL Enterprise Manager реплицированная база данных создается достаточно просто, но эту базу данных может быть очень трудно поддерживать. Каждый сервер может быть одновременно публикатором, подписчиком и распространителем, одна и та же таблица может одновременно содержать как реплицированные, так и исходные данные. Это может приводить к сложным взаимным зависимостям и, как показывает опыт, к неустойчивой работе вследствие архитектурных просчетов администратора при проектировании и настройке базы данных.
Для управления репликацией используется компонент Microsoft SQL Executive Task Manager. Задачи, отвечающие за репликацию, запускаются и планируются на сервере распространения и выполняют репликацию, читая журналы публикатора, передавая их каждому подписчику и реализуя все изменения, произведенные публикатором, путем генерации соответствующих команд SQL. При наличии нескольких публикаторов и подписчиков синхронизировать эти задачи достаточно трудно.
Первый сценарий (репликация только для чтения) реализуется и управляется очень просто. На каждом из серверов рабочих групп публикуется таблица, содержащая уникальные для него данные, а центральный сервер подписывается ко всем этим серверам
и содержит в виде одной таблицы консолидированные данные со всех серверов рабочих групп.
Для второго сценария (мобильные данные) Microsoft имеет незаконченное решение. В качестве мобильной базы данных можно использовать ядро Jet (посредством Visual Basic forApplications или выполнением запросов из Access), но через ODBCвозможна лишь ограниченная репликация на Jet. В SQL Server и Jet используются разные версии SQL, и у Jet меньше возможностей, так что мобильные приложения должны отличаться от стационарных, и если однонаправленная репликация небольших объемов данных не вызывает особых затруднений, то более сложная распределенная база данных требует включать в мобильные приложения специально написанные подпрограммы репликации.
Третий сценарий может быть реализован следующим образом. SQL Server допускает наличие нескольких подписчиков к одной таблице, а одна и та же таблица может быть использована как подписанная и как опубликованная. Единственным механизмом разрешения конфликтов являются триггеры на целевой таблице. Механизм очень прост, но недостаточно безопасен, является жестким и усложняет сопровождение базы данных[2].
В СУБД Sybase SQL Server System для управления репликацией используется продукт Replication Server - отдельный набор процессов с собственным командным языком, поддерживающий также обращения к нему через интерпретатор ISQL. Как и в Microsoft SQLServer, используется схема публикации и подписки, но Sybase реализовала репликацию раньше, чем Microsoft и Oracle. Кроме того, возможна репликация не только данных, но и хранимых процедур, что даёт потенциальный выигрыш в производительности, поскольку одна хранимая процедура может выполняться на нескольких узлах, не требуя передачи между ними больших объёмов данных.
Механизм, реализованный в Replication Server, использует процессы операционной системы, а в дополнение к процессу Replication Server для каждого публикатора требуется Replication Agent, который читает журнал файлов и передает соответствующие записи об изменении данных в Replication Server, транслирующий их подписчикам. Replication Agent основан на использовании Open Connectivity, что позволяет использовать базы данных, управляемые не Sybase SQL Server. Replication Server поддерживает множественные обновления, предоставляя полный контроль над разрешением конфликтов: можно выбрать встроенный алгоритм или реализовать свой собственный [4].
Для организации репликации только по чтению нужно опубликовать таблицу на каждом сервере рабочих групп и подписать на каждую реплику центральный сервер. Однако, при наличии большого числа рабочих групп этим процессом сложно управлять, поэтому целесообразно использовать несколько Replication Server, каждый из которых управляет небольшим числом рабочих групп, однако это усложняет архитектуру системы в целом.
Компания Sybase является единственной, предлагающей законченное решение для построения мобильных приложений - SQL Anywhere (ранее система Watcom), которое полностью совместимо с SQL Server, включая Transact SQL. Это позволяет запускать на мобильных компьютерах приложения без изменений. Для поддержки репликации данных используется SQL Remote, но этот продукт не настолько полон и гибок как Replication Server и обеспечивает только базовый уровень репликации для поддержки мобильных приложений.
Корпорация Oracle предлагает развитые механизмы репликации, включая прозрачную двухфазную фиксацию и разные формы мгновенных снимков. Двухфазная фиксация используется для синхронной (в реальном времени) репликации и гарантирует, что транзакция, обновляющая две базы данных, либо завершилась успешно, либо полностью отменена. Однако, сложность проектирования транзакций реального времени,
обращающихся к нескольким базам данных, побудила разработать другие методы репликации, выполняемой в пакетном режиме с использованием двухфазной фиксации.
В основе механизма репликации компании Oracle изначально лежали мгновенные снимки, которые теперь представляют собой развитый механизм. Мгновенный снимок может быть «быстрым» (реплицируются только журнализованные изменения) или «полным» (копируется вся таблица), может быть только читаемым или обновляемым. Последний вариант позволяет вносить в удаленном узле временные изменения, которые действуют до проверки в центральном узле. Однако, использование мгновенных снимков ограничено количеством возможных попыток передачи, после чего требуется вмешательство администратора, а также тем, что быстрый снимок должен базироваться на одной таблице и требует определения журнала мгновенного снимка на стороне сервера и мгновенного снимка на стороне целевого компьютера. Существуют также ограничения на количество одновременно обрабатываемых мгновенных снимков.
В Enterprise Server доступна новейшая технология репликации компании Oracle. Механизм развитой репликации основан на использовании хранимых процедур системного уровня (выполняемых как «задания»), триггеров, запросов и связей с базами данных. Это позволяет точно определить, что нужно реплицировать (полные таблицы или только изменения) и реализовать свой собственный алгоритм разрешения конфликтов. Репликация может быть синхронной и асинхронной, причем этот режим можно менять. Механизм встроен в сервер баз данных и управляется интерпретатором SQL или инструментами администратора, поскольку все команды - это операторы SQL и вызовы хранимых процедур. Однако лучшим средством управления развитой репликацией является компонент Oracle Enterprise Manager, позволяющий создавать группы репликации, что делает большую сложную базу данных гораздо более управляемой [3].
При использовании в Oracleмультимастер репликации появляется возможность для обнаружения и разрешения трех типов конфликтов: конфликтов изменения, конфликтов уникальности и конфликтов удаления.
Конфликт изменения (Update Conflicts) возникает, когда репликация измененной записи конфликтует с другим изменением той же записи из-за ее обновления на двух узлах в примерно одинаковое время. Конфликтов изменения полностью избежать нельзя, но их можно минимизировать в той или иной степени. Oracle позволяет использовать один из методов разрешения конфликтов изменения: additive and average (добавление разницы и замена на среднее значение), minimum and maximum (замена на минимальное или максимальное значение), earliest and latest timestamp (приоритет первого или последнего изменения), overwrite and discard (перезапись или отмена записи), priority groups and site priority (приоритет группы или приоритет сайта), однако вOracleEnterpriseManager можно использовать только стандартный метод LatestTimestamp (приоритет по последнему времени изменения). Время изменения определяется по указанному полю, значение в которое заносится триггером перед добавлением или изменением записи в таблице.
Конфликт уникальности (Uniqueness Conflicts) возникает, когда репликация записи пытается нарушить сущностную целостность (ограничение primary key или unique) -каждая из двух конкурирующих транзакций добавляет запись в таблицу-реплику с одинаковым значением первичного ключа. Возможность возникновения конфликтов уникальности предупредить достаточно легко, например, путем генерации на каждом узле последовательностей, имеющих, не пересекающиеся диапазоны значений. Возможный вариант - генерация чисел, частью которых является номер сайта (первичный ключ в каждой таблице будет составным). В OracleEnterpriseManager используется генерация первичных ключей таблиц для каждого узла в разных диапазонах.
Конфликт удаления (Delete Conflicts) возникает, когда одна транзакция удаляет запись, которую другая транзакция изменяет. Для их устранения предлагается при удалении записи не удалять ее физически, а только помечать как удаленную. Таким образом, конфликты удаления преобразовываются в конфликты изменения и разрешаются
одним из стандартных способов или способом, предусмотренным разработчиком базы данных. Конфликты удаления могут разрешаться различными методами:
- административными - изменения в справочники вносит только администратор системы по заявкам других пользователей;
- разграничением прав на рабочие таблицы и справочники - создаются роли на изменение рабочих таблиц пользователями и на изменение справочников ограниченным кругом лиц, которые согласовывают между собой время правок справочников;
- эмулированием разнесения ввода по времени - для каждого узла либо для отдельного пользователя задается смещение времени изменения записи, имитируя их разнесение по приоритетам, что позволяет использовать стандартный метод LatestTimestamp для разрешения не только конфликтов изменения, но и удаления [1].
Механизм разрешения конфликтов репликации в базах данных Oracle
Механизм разрешения конфликтов репликации, реализованный в предлагаемом администратору продукте OracleEnterpriseManager (OEM), базируется на использовании пакета dbms_defer_query. Объекты пакета позволяют извлечь из системной таблицы system.def$_aqerror данные, которые в неё помещает СУБД Oracle при возникновении конфликтной транзакции. Основными полями, необходимыми для разбора конфликтных транзакций являются:
- ENQ_TID - порядковый номер конфликтной транзакции;
- STEP_NO - номер вызова в транзакции;
- USER_DATA - массив байт (используется тип BLOB), в котором определены тип аргумента, над которым пользователем были произведены изменения, размер аргумента в байтах и собственно исходное и модифицированное значения аргумента.
Каждая транзакция содержит в себе определённое количество вызовов, которые, в свою очередь, содержат наборы аргументов. Таким образом, алгоритм разбора конфликта сводится к последовательному разбору массива байт, хранящегося в поле USER_DATA по каждому вызову в конфликтной транзакции.
Для разбора вызовов в конфликтных транзакциях OracleEnterpriseManager использует пакет dbms_defer_query, который, кроме прочих, содержит следующие функции:
- функция get_arg_form возвращает набор символов для указанного аргумента в вызове конфликтной транзакции в виде отложенного вызова параметров;
- функция get_arg_type определяет тип аргумента в вызове;
- функция get_datatype_arg определяет значение аргумента в вызове в соответствии его типом, который может являться одним следующих: NUMBER, VARCHAR2, CHAR, DATE, RAW, ROWID, BLOB, CLOB, NCLOB, NCHAR, NVARCHAR2, TIMESTAMP.
Текущее значение изменяемого аргумента определяется посредством SQL-запроса к базе данных по каждому аргументу в вызове конфликтной транзакции.
При возникновении конфликтной транзакции СУБД Oracle помещает в системную таблицу system.def$_aqerror информацию о содержании конфликта. Затем, OracleEnterpriseManager обеспечивает поиск конфликтной транзакции и следующий алгоритм её обработки:
- для каждого вызова конфликтной транзакции определяются типы и используемые наборы символов аргументов вызова при помощи функций
- для каждого из аргументов вызова определяется значение при помощи функции get_datatype_arg;
- при помощи SQL-запроса определяется текущее значение аргумента;
- аналогично обрабатываются аргументы каждого объекта в вызове при помощи функций get_arg_type, get_arg_form и get_datatype_arg;
- найденные значения аргументов вызовов и объектов конфликтной транзакции выводятся для принятия администратором решения о необходимых действиях по её разрешению.
Языком разработки OEM является Java. В отличие от многих языков программирования, Java - это еще и программная платформа, включающая большой объём кода мощной библиотеки и среду для выполнения программ (Java Virtual Machine), которая обеспечивает безопасность, независимость от операционной системы и автоматическую «сборку мусора», а также независимость от архитектуры компьютера, что является несомненным достоинством. Однако, независимость от архитектуры компьютера является в то же время и слабой стороной Java, приводящей к снижению производительности программ, разработанных на этой платформе. Тот факт, что OEM разработан на Java, является одним из факторов, снижающих производительность этого ПО - его слабой стороной является время реакции интерфейса на действия администратора, время формирования соответствующих SQL-запросов, время преобразования полученного результата из БД и предоставления обработанных данных непосредственно администратору. Кроме того, в силу большого объема данных, полученных из БД, и ограниченности ресурсов рабочего места администратора, их оперативная обработка в OEM посредством JVM и отображение результата в OEM иногда оказываются невозможными (приложение «зависает»). По причине постоянного роста промышленных БД, подобные ситуации довольно часты, но являются крайне нежелательными, особенно при разборе конфликтных транзакций и синхронизации данных между различными узлами распределённой БД, так как приводят к блокировке объектов базы данных и невозможности полноценного функционирования информационных систем предприятия.
Таким образом, корпорация Oracle предоставляет наиболее полное и функциональное решение для создания и эксплуатации распределенных баз данных. Однако, предлагаемое средство для разрешения конфликтов репликации не всегда позволяет обеспечить требуемую оперативность синхронизации данных между узлами распределенной базы данных и надежность работы. Это привело к необходимости разработки альтернативного инструментария администратора для анализа и разрешения конфликтных транзакций.
Альтернативные методы разрешения конфликтов репликации
Анализ выше описанного алгоритма разбора вызовов в конфликтных транзакциях показывает, что время разбора можно значительно сократить за счёт разбора массива байт поля USER_DATA из таблицы system.def$_aqerror путём считывания и преобразования без использования вызовов системного пакета dbms_defer_query. Такой подход реализован в разработанном приложении ErrManager и является более эффективным по сравнению с OracleEnterpriseManager, так как требует использования существенно меньших ресурсов сервера базы данных. Локальный разбор конфликтных вызовов требует значительно меньше времени по сравнению с многочисленными вызовами функций системного пакета, что обеспечивает оперативность разрешения конфликтных транзакций и сокращает время блокировки объектов базы данных. Кроме этого, разработанное приложение ErrManager обеспечивает большую функциональность, по сравнению с OEM.
Повышение эффективности обусловлено лишь однократным обращением к системной таблице system.def$_aqerror и получения значения поля типа BLOB целиком в виде массива байт. Его декодирование и дальнейшая обработка осуществляется полностью на строне клиента без обращения к серверу базы данных, то есть используя только ресурсы рабочей станции администратора. При этом также не используется JVM. После обработки данных о конфликтной транзакции, их отображения и принятия решения администратором, выполняется SQL-скрипт для внесения соответствующих изменений.
Обоснование актуальности внедрения приложения ErrManager выполнено путем оценки экономической эффективности, которая определена следующим образом:
- экономический эффект эксплуатации в первый год составляет разницу между затратами до и после внедрения за год за вычетом затрат на разработку программного продукта;
- экономический эффект в последующие годы составляет разницу между затратами до и после внедрения за год.
В качестве исходных данных для расчетов использованы следующие: в среднем 1 день работы администратора БД стоит 6 101 р. Соответственно, затраты на одну операцию равны времени на операцию (в днях), умноженной на 6 101 р.
Оценка временных характеристик выполнена посредством следующего эксперимента. На двух узлах распределённой БД, между которыми настроено реплицирование данных, одновременно сформировано 20 транзакций по 20 вызовов в каждой. Один из вызовов в каждой транзакции - конфликтный. Операции произведены над объектом БД «таблица», имеющей 7 полей. На разрешение описанных конфликтных транзакций и синхронизацию данных между различными узлами распределённой БД необходимо затратить 1 ч. 51 мин. 33 сек. при использовании ОЕМ - продукта корпорации Огасіе. При использовании разработанного ПО ErrManager время, затраченное на решение аналогичной задачи сокращается до 12 секунд в случае автоматической синхронизации данных и 20 секунд в случае ручной синхронизации. Для дальнейшего расчёта экономического эффекта примем среднее время, равное 16 секундам.
Анализ журналов базы данных предприятия показал, что в течение дня в среднем возникают 4 аналогичные ситуации, соответственно для их решения средствами ОЕМ требуется 7 ч. 24 мин., а средствами ErrManager - 1 мин. 4 сек. Таким образом, для оплаты одного дня работы администратора необходимо:
- при использовании ОЕМ - 5 643,42 р.;
- при использовании ErrManager - 813,47 р.
На разработку ПО ErrManager потребовалось 60 рабочих дней специалиста с тем же размером оплаты труда. Весь период разработки делится на следующие этапы (сроки ориентировочные):
- исследование предметной области (15 дней);
- разработка и программная реализация алгоритма разрешения конфликтных транзакций (20 дней);
- разработка интерфейса ПО ErrManager (15 дней);
- тестирование (эксперимент, опытная эксплуатация) ПО ErrManager (10 дней).
Таким образом, затраты на разработку составили 366 060 р. В результате получены
следующие оценки экономического эффекта от внедрения ПО ErrManager: в первый год -1 396 871,75 р., в последующие годы - 1 762 931,75 р.
В настоящее время многие компании, занимающиеся разработкой систем управления базами данных, предлагают различные, в том числе достаточно развитые и функциональные решения в области репликации в распределенных базах данных. Однако, предлагаемые решения не всегда способны удовлетворить существующие потребности предприятий в силу тех или иных недостатков, что ограничивает их практическое применение.
Представленные в данной статье методы разрешения конфликтов репликации и реализованное с их использованием программное обеспечение в виде приложения администратора ErrManager, отличаются от предлагаемых производителем СУБД большей производительностью и эффективностью. Внедрение этого решения на предприятии, информационная система которого реализована с использованием распределенной базы данных Oracle, дает предприятию как прямую экономическую
выгоду за счет снижения стоимости обслуживания корпоративной базы данных, так и сокращение затрат, вызванных невозможностью выполнения производственных операций из-за блокировки объектов базы данных на время разрешения конфликтов репликации.
i Не можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
1. Гаршин И.К. Практика работы с Oracle: генерация, администрирование, репликация. - М.: Издательство «ПОЛТЕКС», 2000, - 250 с.: ил.
2. Microsofr SQL Server 2008 [Электронный ресурс] / SQL Server 2008 - Режим доступа :http://technet.microsoft.com/ru-ru/library/bb418470 (SQL.10).aspx - Загл. с экрана (дата обращения: 29.02.2012).
3. Oracle [Электронный ресурс] /Oracle Replication :http://www.oracle.com/ global/ru/ pdfs/tech/tg_oracle_replication.pdf - Загл. с экрана (дата обращения: 29.02.2012).
4. SyBaseanSapCompany [Электронный ресурс] / ReplicationServer - Режим доступа :http://www.sybase.ru/products/replication_server - Загл. с экрана (дата обращения: 29.02.2012).
5. Tadvaiser [Электронный ресурс] / Статья: СУБД (рынок России) - Режим
доступа: http://www.tadviser.ru/index.php (http://www.tadviser.ru/index.php/%D0%A1
%D 1%82%D0%B0%D 1 %82%D 1%8C%D 1 %8F :%D0%A 1%D0%A3%D0%91 %D0%94_(%D 1%80%D 1 %8B%D0%BD%D0%BE%D0%BA_%D0%A0%D0%BE%D 1%81%D 1%81 %
D0%B8%D0 %B8)) - Загл. с экрана (дата обращения: 29.02.2012).
Как устроены базы данных
Нельзя сказать, что в этой статье вас ждут отборные потроха баз данных, но скорее рассказ про базы данных от самого начала, плюс небольшое углубление в некоторые подробности, которые Илье Космодемьянскому (@hydrobiont) кажутся важными. И есть все основания полагать, что так оно и есть.
Эта статья родилась не от хорошей жизни. Часто даже не то что начинающие разработчики, но и вполне продвинутые, не знают каких-то базовых вещей — может быть, давно учились в университете и с тех пор забыли, или им не приходилось углубляться в теорию, поскольку и так работалось нормально.
Тем не менее, теоретические знания иногда полезно освежить. Этим мы, в том числе, и займемся.

О спикере: Илья Космодемьянский CEO и консультант в компании Data Egret, специалист по базам данных PostgreSQL, Oracle, DB2. А кроме того, отвечает за продвижение Postgres-технологий, выступает на конференциях и рассказывает людям, как с ними работать.
Ниже материал по докладу Ильи на РИТ++ 2017, который не был связан с какой-то конкретной базой данных, но охватывал многие основные аспекты.
Для чeго это нужно знать?
Хранение и обработка данных — mission-critical задача любой компьютерной системы.
Даже если у вас уже 30 лет есть блог в интернете на текстовых файлах, как у некоторых создателей баз данных бывает, все равно на самом деле этот текстовый файлик — это база данных, только очень простенькая.
Все пытаются изобрести базу данных. Один из докладчиков на конференции сказал: «20 лет назад я написал свою базу данных, только не знал, что это она!» Этот тренд в мире очень развит. Все стараются так делать.
Для работы с данными база данных — это очень удобная штука. Многие базы данных — это очень старые технологии. Они разрабатываются последние полвека, в 70-х годах уже были базы данных, которые работали по схожим принципам, что и сейчас.
Эти базы очень хорошо и продуманно написаны, поэтому теперь мы можем выбрать язык программирования и использовать общий удобный интерфейс обработки данных. Таким образом можно стандартизованно обрабатывать данные, не боясь, что они будут обработаны как-то по-другому.
При этом полезно помнить, что языки программирования меняются: вчера был Python 2, сегодня Python 3, завтра все побежали писать на Go, послезавтра еще на чем-то. У вас может быть кусок кода, который эмулирует работу по манипуляции с данными, которую, по идее, должна делать база данных, а вы не будете знать, что дальше с этим делать.
В большинстве баз данных интерфейс очень консервативный. Если взять PostgreSQL или Oracle, то с некоторым бубном можно работать даже с очень старыми версиями из новых языков программирования — хорошо и здорово.
Но задача на самом деле не самая простая. Если мы начнем закапываться в глубины того, как нам не «побить» данные, как быстро, производительно и, главное так, чтобы потом можно было доверять результату, обрабатывать их, то окажется, что сложное это дело.
Если вы попробуете написать свое простенькое персистентное хранилище, все просто будет только первые 15 минут. Потом начнутся блокировки и прочие вещи, и в какой-то момент вы поймете: «Ой, зачем я все это делаю?»
Об этом и поговорим.
Уровни работы с данными
Итак, есть различные уровни работы с данными:

- Слой доступа к данным, который удобно использовать из языков программирования;
- Слой хранения. Это отдельный слой, потому что обычно хранить данные удобно другими способами, чем использовать: эффективно по памяти, выравнивать, складывать на диск. Это к вопросу о schemaless: схема, которая удобна для хранения, не удобна для доступа.
- «Железо» — слой, где лежат данные, причем там они организованы еще третьим способом, потому что дисками управляет операционная система, и общаются они только через драйвер. В этот уровень мы не будем сильно вникать.
Для слоя доступа к данным есть требования, в выполнении которых мы заинтересованы, чтобы было удобно работать:
- Универсальность, чтобы возможно было с помощью любой технологии запрашивать данные.
- Оптимальность этого запроса. Метод доступа должен быть такой, чтобы хорошо и удобно доставать данные из базы.
- Параллелизм, потому что сейчас все масштабируются, разные серверы одновременно обращаются к базу за одними и теми же данными. Надо сделать так, чтобы максимально использовать преимущества параллелизма и быстрее обрабатывать данные таким способом.
В то же время они должны быть надежно сохранены и надежно воспроизведены. То есть, если мы что-то записали в базу данных, мы должны быть уверены, что мы это получим обратно.
Если вы работали со старыми базами данных, например, FoxPro, то знаете, что там часто появляются битые данные. В новых базах данных, типа MongoDB, Cassandra и прочих, такие проблемы тоже случаются. Может быть, просто их не всегда замечают, потому что данных уж очень много и заметить сложнее.
Для «железа» на самом деле важна надежность. Это как бы допущение, поскольку мы все-таки будем говорить о теоретических вещах. В нашей модели, если что-то попало на диск, то мы считаем, что там все хорошо. Как заменить вовремя диск в RAID — это сегодня для нас забота админов. Мы не будем глубоко погружаться в этот вопрос, и практически не будем касаться того, насколько эффективно хранилище организовано физически.
Чтобы решать эти проблемы, есть некоторые подходы, которые очень похожи у разных хранилищ данных — и новых, и классических.

Прежде всего для того, чтобы обеспечить универсальный и оптимальный доступ к данным, есть язык запросов. В большинстве случаев это SQL (почему именно он, мы подискутируем дальше), но сейчас просто хочу обратить внимание на тенденцию. Сначала достаточно долгое время был SQL — конечно, были времена и до него, но, тем не менее, SQL господствовал долго. Потом стали появляться всякие Key-value-storage, которые, дескать, работают без SQL и гораздо лучше.
Многие Key-value-storage в основном делались для того, чтобы из любимого языка программирования было проще ходить за данными, а SQL не очень хорошо вяжется с любимым языком программирования. Он высокоуровневый, декларативный, а нам хочется объектов, поэтому появилась идея, что SQL не нужен.
Но большинство этих технологий сейчас на самом деле придумывают какой-то свой язык запросов. В Hibernate очень развит свой собственный язык запросов, кто-то использует Lua. Даже те, кто раньше использовал Lua, делают свои реализации SQL. То есть сейчас тенденция такая: SQL опять возвращается, потому что удобный человеко-читаемый язык работы со множествами все равно нужен.
Плюс к тому по-прежнему удобно табличное представление. В той или иной степени во многих базах данных по-прежнему имеются таблички, и это далеко не случайно — таким образом легче оптимизировать запросы. Вся математика оптимизации завязана вокруг реляционной алгебры, и когда есть SQL и таблички, работать гораздо проще.
В слое хранения возникает такое понятие, как сериализация. Когда есть параллелизм и конкурентный доступ, нам нужно обеспечить, чтобы на процессор, на диск это приезжало в более-менее предсказуемом порядке. Для этого нужны алгоритмы сериализации, которые реализуются в слое хранения.
Опять же, если что-то пошло не так и база данных упала, нам нужно ее быстро поднять.
Как вы считаете, можно ли написать 100% надежное отказоустойчивое хранилище? Наверное, вы знаете, что база данных надежно работает только тогда, когда есть механизм, чтобы ее быстро поднять, если она упала.
Для этого нужно восстановление, потому что, что ни делай, где-нибудь будет слабое звено и очень большие накладные расходы на синхронизацию. Мы можем сделать сотню копий на сотню серверов, а в результате сгорит питание или какой-то коммутатор, и будет плохо и больно.
Для «железа» на самом деле важно, чтобы база данных была хорошо интегрирована с ОС, работала производительно, вызывала правильные syscalls и поддерживала все фишки ядра по быстрой работе с данными.
Слой хранения
Начнем со слоя хранения. Понимание того, как он устроен, хорошо помогает понять, что происходит на более высоких слоях.
Слой хранения обеспечивает:
✓ Параллелизм и эффективность.
Другими словами, это конкурентный доступ. То есть, когда мы пытаемся получить пользу от параллелизма, неизбежно возникает проблема конкурентного доступа. Мы одновременно ходим за одним ресурсом, который может записаться не так, побиться при записи и, черт знает что еще, может при этом получиться.
✓ Надежность: восстановление после сбоев.
Вторая проблема — это внезапный сбой. Когда обеспечивается надежность, это означает, что мы не только максимально обеспечили катастрофоустойчивое решение, но важно и то, что мы умеем быстро восстановиться в случае чего.
Конкурентный доступ
Когда я говорю о целостности, внешних ключах и прочем, все как-то хмыкают и говорят, что все это они проверяют на уровне кода. Но как только предложишь: «А давайте пример на вашей зарплате! Вам переводят зарплату, а она не пришла», — почему-то сразу понятней становится. Не знаю почему, но сразу появляется блеск в глазах и интерес к теме внешних ключей, констрейнтов.
Ниже код на несуществующем языке программирования.
account_a < balance = 1000, curr = ’RUR’ >send_money(account_a, account_b, 100); send_money(account_a, account_c, 200); account_a->balance = .Допустим, у нас есть банковский счет с балансом в 1 000 рублей, и есть 2 функции. Как они устроены внутри, нам сейчас не важно, эти функции переводят с аккаунта a на другие банковские счета 100 и 200 рублей.
Внимание, вопрос: сколько денег окажется в результате на балансе счета a? Скорее всего, вы ответите, что 700.
Проблемы
Здесь начинаются проблемы с конкурентным доступом к данным, потому что язык у меня выдуманный, совершенно не понятно, как он реализован, одновременно ли исполняются эти функции и как они устроены внутри.
Мы, наверное, считаем, что операция send_money() — это не элементарное действие. Надо проверить баланс и куда переводится, выполнить контроль 1 и 2. Это не элементарные операции, которые занимают какое-то время. поэтому нам важен порядок выполнения элементарных операций внутри них.
В последовательности «прочитали значение на балансе», «записали на другой баланс», важен вопрос — когда мы читали этот баланс? Если мы это делаем одновременно, возникнет конфликт. Обе функции выполняются примерно параллельно: прочитали одно и то же значение баланса, перевели деньги, записали каждая свое.
Может возникнуть целое семейство конфликтов, в результате которых на балансе может оказаться 800 рублей, 700 рублей, как должно быть, или что-то побьется, и на балансе окажется null. Такое, к сожалению, бывает, если не относиться к этому с должным вниманием. Как с этим бороться, мы и поговорим.
В теории все просто — мы можем выполнить их одну за другой и все будет хорошо. На практике этих операций может быть очень много и делать их строго последовательно может быть проблематично.
Если помните, несколько лет назад была история, когда у Сбербанка упал Oracle и процессинг карточек остановился. Они тогда просили совета у общественности и примерно обозначили, сколько логов писала база данных писала. Это огромные количества и конкурентные проблемы.
Выполнять операции строго последовательно не очень хорошая идея еще и по той простой причине, что операций много, а мы ничего не выиграем от параллелизма. Можно, конечно, разбивать операции по группам, которые не будут конфликтовать друг с другом. Такие подходы тоже есть, но они не очень классические для современных баз данных.

В немецких правилах дорожного движения есть одна интересная история. Если дорога сужается, то правила предписывают доехать до конца, и только после этого перестраиваться по одному, а соседняя машина должна пропускать. Все перестраиваются строго друг за другом — такой знак об этом говорит.
Это живой пример возможной сериализации, когда общественность очень долго приучали к тому, что правила нужно соблюдать. Думаю, что все, кто ездит на машине по Москве понимают, насколько утопична эта картина.
В принципе, нам нужно то же самое сделать с данными, которые мы пишем на диск.
Как улучшить ситуацию?
● Операции должны быть независимы друг от друга — изолированость.
Чисто теоретически контролируемым образом операция должна знать, что происходит снаружи. Не может быть такого, что как только одна операция что-то изменила, результат сразу стал виден другой. Должны быть какие-то правила.
Это называется изолированностью транзакций. В самом простейшем случае транзакции вообще не знают, что происходит с соседними. Это действия сами в себе, в пределах одной функции никакого взаимодействия наружу нет, пока она не закончилась.
● Операция происходит по принципу «все или ничего» — атомарность.
То есть либо вся операция прошла, и тогда ее результат записался, либо, если что-то пошло не так, мы должны уметь вернуть статус-кво. Такая операция должна быть восстановима, а если она восстановима и изолирована, то она атомарна. Это элементарная операция, которая, как и результат, не делима. Она не может пройти наполовину, а только целиком проходит или целиком не проходит.
● Нужен механизм как проверить что все произошло правильно — консистентность.
Я спрашивал, сколько денег получилось на балансе в нашем примере, и вы почему-то сказали, что 700. Мы все знаем, что есть арифметика, здравый смысл и Уголовный Кодекс, который следит за банками и бухгалтерами, чтобы они не сделали чего-то противозаконного. Уголовный Кодекс — это одна из частных версий консистентности. Если мы говорим, о базах данных, там их гораздо больше: внешние ключи, констрейнты, все дела.
ACID-транзакция
Действия с данными, которые обладают свойствами атомарности, консистентности, изолированности и Durability — это определение ACID транзакции.
D — Durability — это та самая модель, про которую я говорил: если данные уже записали на диск, то мы считаем, что они там лежат, записались надежно и никуда не денутся. На самом деле это не так, например, данные нужно бэкапить, но для нашей модели это не важно.
Как ни печально, обеспечить эти свойства можно только с помощью блокировок. Есть 3 основных подхода к шедулингу транзакций:
- Пессимистические шедулеры;
- Оптимистичные шедулеры;
- Гибридные шедулеры и основанные на упорядочивании TimeStamp происходящей транзакции.
Про упорядочивание TimeStamp все знают: мы смотрим время одной транзакции, время другой транзакции, кто первый встал, того и тапки. На самом деле для большинства серьезных систем этот подход имеет кучу проблем, потому что, для начала, время на сервере может идти назад, может скакать или идти неправильно — и мы приедем.
Есть разные методы усовершенствовать это, но как один-единственный метод синхронизации транзакций, он не работает. Есть еще векторные часы, Лэмпортовы часы — наверняка слышали такие термины — но у них тоже есть свои ограничения.
Оптимистические подходы подразумевают, что у нас не будет конфликтов типа того, что я описал с банковским счетом. Но в реальной жизни они не очень успешно работают, хотя есть реализации, которые помогают проводить какие-то операции с помощью оптимистичных вариантов.
Как люди, работающие с базами данных, мы на самом деле всегда пессимисты. Мы ожидаем, что программисты напишут плохой код, поставщик поставит плохое железо, Марь Иванна выдернет из розетки сервер, когда будет мыть пол — чего только может не быть!
Поэтому мы любим пессимистичный шедулинг транзакций, а именно с помощью блокировок. Это единственный гарантированный способ обеспечить целостность баз данных. Есть соответствующие теоремы, которые можно доказать и продемонстрировать это.
Нужны эффективные алгоритмы взятия и снятия блокировок, потому что, если просто блокировать все, что нам нужно, скорее всего, мы придем к очень тупой версии, когда мы выполняем все операции строго последовательно. Как мы уже знаем, это не эффективно с точки зрения утилизации параллелелизма, современных ЦПУ, количества серверов и т.д.
Семантика Эрбрана
Небольшое лирическое отступление, которое поможет понять, что будет происходить дальше. Жак Эрбран — французский математик первой половины XX века, который, кстати, изобрел рекурсию. Он придумал еще в докомпьютерные времена обозначать транзакции следующим способом:

Здесь S — от слова schedule (расписание). Расписание транзакции включает в себя операцию — r (read — чтение) или w (write — запись). Еще бывает b (begin), с (commit) и т.п.
Что удобно — у нас есть 2 транзакции (цифры 1 и 2). Одна транзакция просто читает данные из какого-то ресурса (x), а вторая транзакция его тоже читает, делает какую-то математику на основе этих двух чтений x и записывает что-то в y.
Очень удобно — транзакции состоят из элементарных действий «чтение — запись, чтение — запись». Мы можем составить итоговое расписание, проверить его, есть ли в нем конфликты, с помощью всякой хитрой математики, и таким образом гарантировать, что все будет хорошо и целостно.
Для чего все это нужно?
Two Phase Locking
Один из основополагающих алгоритмов в современных базах данных — это так называемое двухфазное блокирование или 2PL (Two Phase Locking).
Двухфазное оно, потому что было подмечено, что для оптимизации взятия и снятия блокировок в базе данных удобно сделать это в 2 присеста:
- Сначала выставляем блокировки на все ресурсы, которые нужно прочитать или записать тому скопу транзакций, которые в данный момент есть в базе данных.
- Ни одна блокировка не снимется, пока не выставлены все необходимые блокировки.

На рисунке 3 линейки обозначают транзакции и время их исполнения. Видно, что операция записи в первой транзакции ресурса x имеет ненулевое время, поскольку запись занимает какое-то время — пока диск повернется, пока странички туда уйдут и т.д.
Когда она начинается, в этой модели нет никаких других транзакций, поэтому запись началась и пишется. Но вторая транзакция тоже должна прочитать x. Эта транзакция не может взять блокировку на чтение x по той простой причине, что x в этот момент пишется другой транзакцией. Линия становится пунктирной — это означает, что транзакция ждет блокировки, которую выставила транзакция t1.
Как только транзакция t2 взяла все блокировки, которые ей были нужны для того, чтобы она выполнилась — ей еще нужна блокировка на y и блокировка на z — только тогда она может начать их отпускать. В этот момент разблокируется следующая транзакция, которая тоже выполняется до конца.
Эта идея повышает эффективность транзакций и позволяет одни и те же операции выстраивать в параллель только так, чтобы элементарные операции блокировались и ждали, только если они конфликтуют.
Рекомендую книгу «Transactional Information Systems» (Gerhard Weikum, Gottfried Vossen) — это фундаментальный учебник по теории транзакций.
Что плохо в двухфазном блокировании?
Почему нельзя так просто решить всю проблему со всеми базами данных с помощью одного простого волшебного алгоритма?
✓ Во-первых, с такими блокировками неизбежно возникают дедлоки, когда непонятно, курица или яйцо.
Одной транзакции нужен ресурс x, другой y, они его блокируют, а дальше им нужны крест-накрест те же самые ресурсы, и непонятно, кто должен первый отпускать блокировку. Для этого в базах данных имеются специальные системы контроля дедлоков и так называемого отстрела дедлоков. Дедлок нельзя разрешить мирным способом, а только откатом одной из транзакций.
Обычно математика внутри детекции дедлоков — это граф дедлоков, где на вершинах обозначены transaction ID, а направленные ребра обозначают, какая ждет блокировки от какой. На этом графе выделяются небольшие подграфы от одной из этих вершин, смотрится, например, если одну транзакцию ждет очень большое количество транзакций, то эту транзакцию прибивают.
Но есть и другие красивые математические подходы, искать которые можно по теме deadlock-detection.
✓ Второй момент, это медленно — никто не хочет ждать блокировки.
Есть такие транзакции, которые занимают ресурс надолго, например, какой-то отчет считает, заняла ресурс, и все остальные вынуждены ждать. Чтобы этого не было, придумали некоторые усовершенствования, о которых расскажу чуть позже.
✓ Зато таким образом обеспечена сериализация.
Без двухфазного блокирования никакой сериализации нет. То есть надо придумывать, как улучшать двухфазное блокирование, чтобы сократить время на ожидание.
В любой современной базе данных двухфазное блокирование — это главный способ обеспечения целостности и сериализации, даже если мы говорим о версионных базах данных.
На самом деле бывают конфликты, которые 2PL разрулить не может в принципе и тогда одна из конфликтующих транзакций откатывается. Обычно в базах данных реализован механизм, когда база данных ждет некоторое время и понимает, что некоторая транзакция ждет блокировки слишком долго, и что нельзя никак разрешить конфликт, база данных просто убивает такую транзакцию. Это достаточно редкая ситуация и следующий алгоритм позволяет решить некоторые из этих конфликтов.
MVCC — MultiVersion Concurrency Control
Версионирование данных нужно не только для того, чтобы ускорить, но еще и для того, чтобы решить некоторые разновидности конфликтов, которые могут возникнуть.
✓ Интуитивно все понятно — чтобы не ждать блокировку, берем предыдущую версию.
Если какой-то ресурс заблокирован, мы можем посмотреть его старую версию и начать с ней работать. Если, например, блокировка была такая, что та транзакция, которая блокировала, не изменила ничего на этом ресурсе, то мы можем продолжить исполнение транзакции. Если было изменение и появилась новая, более свежая версия данных, наша транзакция будет вынуждена их еще раз перечитать.
В любом случае это обычно быстрее, чем долго ждать блокировку. Если помните старый MS SQL Server и старые версии DB2, страшное дело, то там, если там пошло много блокировок, дальше началась их эскалация — все работало плохо и жить с этим было тяжело.
✓ Все современные DBMS в той или иной степени «версионники»
Oracle, PostgreSQL, MySQL — все «версионники» в честном виде. DB2 немножко оригинальнее на эту тему, там есть свой механизм — хранят только одну предыдущую версию.

Это расписание, которое я рисовал раньше, но несколько более сложное. Здесь больше транзакций (3 штуки), больше ресурсов (есть еще z) и 2 коммита. То есть обе транзакции заканчиваются коммитом.
Как говорят в таких случаях математики: «Легко заметить. » — я это очень люблю, особенно когда на половину доски формула. Действительно, тут легко заметить одну штуку. В качестве домашнего задания попробуйте понять, почему это легко заметить.
Я вам подскажу. Это расписание никогда не сериализуется по той простой причине, что операция r1(y) вызовет конфликт, возможно, даже дедлок, если не будет доступна предыдущая версия y.
То есть, если здесь будет доступна предыдущая версия y, то транзакция нормально завершится, никаких проблем не будет. Если этой версии y не будет, то операции будут конфликтовать.
Как это работает?

Диаграмма более-менее такая же, как с двухфазным блокированием. Это разновидность версионного шедулинга транзакций, то есть это все равно алгоритм двухфазного блокирования, только мультиверсионный.
Добавляется еще такая фишка, как нижний индекс — 0, 1, 2 — это номер версии.
- Когда мы пошли исполнять транзакцию t1, имеется чтение x0, т.е. самой изначальной версии.
- Дальше в t2 мы начинаем записывать y другой версии, потому что он был изменен.
- В транзакции t1, которая началась раньше, чем мы начали записывать y, до сих пор видно предыдущую версию y0, поскольку t2 еще не завершилась, и мы и спокойно начать с ней работать.
- Поскольку транзакция t1 заканчивается раньше, чем w2(y2), то произойдет перечитываниеy,и после этого в транзакции t2 выполнится нормальная работа, а другая транзакция просто нормально завершится.
В этом большой плюс MVCC. Мультиверсионность на самом деле быстрее, чем блокировка, это не просто маркетинговая фича.
А что, если в момент транзакции, которая явно имеет ненулевую длительность, произойдет сбой, например, развалится жесткий диск под базой данных или выдернут провода из сервера?
На самом деле мы к этому готовы, потому что транзакции выполняются так. Рассмотрим абстрактную базу данных:

Есть некий объем памяти, который обобществлен между разными процессами или тредами, в которых обрабатываются клиентские подключения. У треда есть свой объем памяти, куда приходит SQL-запрос. В этом объеме памяти SQL запрос (или запрос на другом языке) прекомпилируется, интерпретируется, перестраивается каким-то образом.
Дальше он идет за данными, которые ему нужно прочитать и изменить. Эти данные на диске лежат специальным образом. Если заглянуть глубже в хранение, они лежат фиксированными кусками (страничками) в PostgreSQL это 8Кб, в Oracle можно разного размера использовать. В разных базах данных по-разному.
Эта страничка очень удобна тем, что в ней лежит куча разных данных (фактически в ней лежат tuple (кортежи) То есть есть табличка, а в ней строчки, эти строчки упакованы в большие странички.
Если запросу нужны данные с одной из страничек, он просто поднимает эту страничку себе в память и все воркеры, треды и процессы базы данных будут иметь к ней доступ. Если нужно много, то он поднимет несколько. Они будут закэшированы — это удобно, производительно — память быстрее, чем диски, все дела.
Если нужно поменять хотя бы одну запись хотя бы на одной страничке, вся страничка будет помечена, как так называемая «грязная». Это делается потому, что так удобнее. Мы рисовали на схеме ресурсы x и y — здесь это странички.
На самом деле база данных умеет блокировать и на более гранулярном уровне, в том числе единичную запись. Но сейчас мы рассуждаем о более теоретических вещах, а не о тонкостях глубокой реализации.
Соответственно, страничка помечена как «грязная», и у нас возникает проблема, которая заключается в том, что теперь слепок в памяти отличается от того, который на диске. Если мы сейчас упадем, память не персистентна, мы потеряем информацию о «грязных» страничках.
Поэтому нужно сделать следующую операцию: где-то на бумажке записать, какие изменения мы проделали, чтобы, когда поднимемся, прочитать эту бумажку и, используя информацию из нее, восстановить страничку до того состояния, в которое мы ее привели этим апдейтом.
Поэтому прежде, чем ответ от транзакции вернется снова к клиенту, происходит запись в так называемые Write Ahead Log. Это та самая бумажка, которая позволяет записывать быстро — запись в WAL последовательная, нам не надо искать, куда вставить в огромный data-файл это дело.
Мы записали в лог информацию о страничке и дальше вернули управление — все хорошо. Если в какой-то момент упали, то читаем назад Write Ahead Log и используя информацию об этих изменениях, можем чистые странички докатить до уровня «грязных». База данных у нас снова новая.
Это позволяет произвести то самое восстановление, которое нам нужно было обеспечить, исходя из проблем хранения данных, и позволяет восстановиться на самую последнюю транзакцию, на самое последнее действие, которое произошло перед тем, как Марь Иванна вытащила сервер из розетки.
Этот алгоритм называется ARIES и в современном виде сделан достаточно давно. Фундаментальная статья по его устройству и способе восстановления в реляционных базах данных была опубликована Моханом в 1992 году.
С тех пор теории особо не добавилось — Write Ahead Log с тех пор остался Write Ahead Log. Они все используют концепцию страничек и концепцию записей изменений в лог. Лог может по-разному называться и в разных местах располагаться:
- В MySQL он внутри InnoDB,
- В PostgreSQL это отдельная директория, которая наконец в версии 10 стала называться WAL вместо PGX-Log;
- В Oracle это называется Redo Log;
- В DB2 — WAL.
Важный момент состоит в том, что все это было бы очень непроизводительно, если бы мы просто от начала времен писали WAL. Он бы рос и рос, а мы потом очень бы долго накатывали эти изменения в базу данных.
Checkpoint
Поэтому в последней реинкарнации этого алгоритма имеются идея так называемых Checkpoint — периодически база данных выполняет синхронизацию «грязных» страниц на диск. Когда мы будем восстанавливаться, можно просто дойти только до предыдущего Checkpoint. Все остальное уже синхронизировано, мы, так сказать, замечаем до какого момента мы восстановили.
Это как в компьютерной игре — люди, когда ее проходят, сохраняются периодически. Или как в Word сохраняют периодически свои файлы, чтобы текст не пропал никуда.
База данных это все умеет делать внутри себя. Она так устроена. Помимо всего прочего, это немножко ускоряет процесс, потому что рано или поздно эти странички должны же попасть на диск.
Доступ к данным
Эти самые странички, конечно, хорошие, но нам нужно в каком-то более человеко-читаемом виде эти данные получить. Страничная модель удобна для хранения и транзакционных алгоритмов, но не удобна для доступа. Странички читать неудобно — по битам, поэтому нужен более удобный человеку эффективный метод доступа к данным в страницах.

Есть странички на диске, относящиеся к таблице A и B. Данные на диске ничего не знают, к какой табличке они относятся. Про это знает наш оптимизатор, то есть тот engine в базе данных, который исполняет наш запрос, написанный на нашем языке запросов.
Например, если рассмотреть традиционный SQL, то обычно такая штука будет называться планом запроса. С помощью последовательного sequential scan мы будем брать странички из таблицы A и из таблицы B — иногда синхронно, иногда по очереди — в зависимости от реализации.
Дальше будем накладывать на них, например, JOIN, а с результатом делать что-нибудь еще, и потом вернем ответ клиенту.
Чем это удобно? Представьте себе альтернативу: вы из какого-нибудь Python читаете все это к себе в приложение, а эти таблички могут быть на самом деле огромными, а условие JOIN может исключать 90% этих данных. Вытаскиваете в память — там соответственно ходите по ним циклами, сортируете, возвращаете. На самом деле на каждом из этих этапов планировщик может решить, как сделать выгоднее. Например, он может выбрать метод JOIN, который может или целиком состоять из циклов, или может закэшировать одну таблицу и присоединить к ней другую и т.д. В зависимости от метода, например, можно не делать full sequential scan, то есть не читать всю табличку, а из приложения, скорее всего, вам придется прочитать данные целиком.
Здесь все придумано до вас и реализовано эффективно.
Выводы
SQL и реляционная модель удобны для оптимизации и для человеко-читаемого представления, поэтому без языка запросов никуда. Смерть SQL как технологии предрекают долгие годы, но дедушка живее всех живых. Это во многом потому, что на реляционные модели и с помощью SQL проще всего производить оптимизации и выбирать алгоритмы.
Хранить данные удобней страницами. Есть разные базы данных: объектные, графовые, документо-ориентированные. В основном это все нишевые, неуниверсальные продукты, которые используются для своих целей. Просто поставить на такую базу огромный объем транзакций, чтобы они работали в универсальном виде, не особо получается. Шедулинг транзакций, которые мы рассматривали здесь, на объектах выглядит гораздо сложнее.
Фактически каждая транзакция представляет собой путь по графу. Граф может быть очень большим. Для нахождения конфликтов нужно заниматься очень тяжелой математикой на графах — поэтому в объектной модели возникают проблемы с шедулингом.
Неслучайно столько лет господствует база данных со страничной моделью, и поэтому все происходит так, как происходит. Хорошо ли, плохо ли, но этот метод доказал свою большую эффективность.
Без транзакций никак!
Транзакции, вопреки расхожему мнению, это не способ замедлить. Часто считают, что транзакции — это некий синтаксический сахар в базах данных с SQL, который просто все портит и бибикает, в смысле того, что все замедляет, потому что ждем блокировок.
На самом деле транзакции — это способ ускорения, поскольку позволяет параллельно обрабатывать больше данных без конфликтов, чтобы они не бились, чтобы получалось так, что операции выполнялись не строго одна за другой, а параллельно. Это позволяет эффективней расходовать вычислительные ресурсы и время.
Как говорил конструктор Туполев, когда его обвиняли в том, что он какую-нибудь модель самолета у кого-нибудь стянул: «Все самолеты одинаково устроены — чтобы летать, им нужно иметь крылья, фюзеляж и хвост!»
Все базы данных внутри похожи. Они либо устроены одинаково до той или иной степени, либо туда идут. Появился noSQL -без транзакций, без блокировок, без всего, абсолютно не похож ни на какую базу данных. Но мы наблюдаем, что постепенно и там появляются блокировки, схемы, язык запросов, оптимизация и т.д.
В некоторых даже, наверное, скоро появится параллелизм, и это будет большая победа сил разума.
Если посмотреть на современные Percona server, MariaDB, MySQL 8, видно, что они в значительной степени перенимают теоретические основы и сейчас гораздо больше похожи на классические базы данных по своему устройству.
Ну что, убедились, что общие теоретические аспекты нужно знать?
Но не теорией единой… и на РИТ++, который успешно завершился, и на Highload++ Siberia уже через месяц, всегда много-много реального опыта и практических кейсов.
Например, про базы данных и системы хранения есть такие потрясающие заявки:
- Иван Шаров и Константин Полуэктов из ЦФТ расскажут, как работать с Oracle от 7-ми до 18-ти, то есть о длинной истории миграций.
- Владислав Клименко (Altinity) и Валерий Панов (Ivinco) поделятся способами репликации данных из MySQL в ClickHouse в реальном времени для плавной миграции аналитики.
- Алексей Миловидов (Яндекс) обещает поделиться приёмами ускорения разжатия LZ4.
