Индексы

Евгений Каратаев
Речь пойдет об алгоритмах и структурах данных, их организации и поддержке. Термин индекс далее используется строго в целях обозначения дополнительных поисковых или оптимизирующих структур. Основным языком примеров выбран язык МUMPS. По возможности применяется страндартный синтаксис, в некоторых исключительных случаях для большей читаемости применяются Cache Object Script — расширения. Их применение ограничено и допускает альтернативную замену на эквивалентные выражения в иных диалектах МUMPS.
Индексы — это структуры данных, размещаемые параллельно и поддерживаемые синхронно основным структурам данных и имеющие основным назначением поддержание структур данных, ориентированных на ускорение поиска или оптимизацию хранения основных данных. Здесь под основными данными понимаются данные, хранение и работа с которыми является основным назначением системы базы данных.
При использовании основных данных система базы данных выполняет операции вставки, поиска, удаление и изменения в массиве основных данных. При использовании дополнительных индексных структур система параллельно обновляет индексные структуры при изменении (вставке, изменении и удалении) основных данных и в некоторых случаях получает возможность использовать индексные структуры, ориентированные на поиск данных. Наличие такой возможности определяется характеристиками индекса.
Как следует из вышеприведенного, введение индексов в систему базы данных утяжеляет операции связанные с изменением данных но ускоряет операции связанные с поиском и, как обычно, следствие этого, выборкой данных.
Индексные структуры сами по себе обычно не являются необходимыми для работы системы базы данных. И их применение определяется программистом или администратором системы.
В большинстве общераспространенных систем баз данных поддержка индексных структур и их использование выполняется автоматическими средствами. В этой работе мы будем составлять структуры и алгоритмы, которые можно использовать вне автоматики и пользоваться всеми возможностями безотносительно ограничений системы базы данных. Примерно как если бы по частям реализовали внутренние механизмы большой системы, но в несколько упрощенном варианте.
Обобщенный механизм поддержки индекса.
Индексная структура по своему состоянию должна соответствовать состоянию индексируемых данных. Поэтому операции обновления индексов обычно делят на две группы — динамическое обновление индексных структур при обновлении одной записи и массовые операции удаления / построения индексов.
Далее будем рассматривать строки данных, устроенные для простоты следующим образом
идентификатор записи получаем инкрементом ноду ^Data
значение записи хранится в узле ^Data(id)
запись состоит из полей с разделителем ~ (тильда)
индексные записи храним с глобале ^Index
в записи предполагаем поля — фигура, цвет, количество
общее строение записи ^Data(id)=Figure~Color~Count
Операции динамического обновления индексов могут быть встроены в виде вызова из операции обновления записи и либо предшествовать собственно сохранению основной записи, либо последовать ему, либо обрамлять. Например
; просто сохранение объекта
SaveObject(id,ObjVal)
i ‘+$g(id) s id=$i(^Data)
s ^Data(id)=ObjVal
q
; обновление индексов перед сохранением
SaveObject(id,ObjVal)
n OldValue
i ‘+$g(id) s id=$i(^Data)
s OldValue=$g(^Data(id))
d DeleteIndices(id,OldValue)
d InsertIndices(id,ObjVal)
s ^Data(id)=ObjVal
q
; обновление индексов после сохранения
SaveObject(id,ObjVal)
n OldValue
i ‘+$g(id) s id=$i(^Data)
s OldValue=$g(^Data(id))
s ^Data(id)=ObjVal
d DeleteIndices(id,OldValue)
d InsertIndices(id,ObjVal)
q
; обрамление обновления индексов при сохранении
SaveObject(id,ObjVal)
i ‘+$g(id) s id=$i(^Data)
d DeleteIndices(id,$g(^Data(id)))
s ^Data(id)=ObjVal
d InsertIndices(id,ObjVal)
q
Здесь DeletIndices удаляет индексные записи по этому объекту, а InsertIndices их создает. В данном случае подразумевается простой формат хранения записи — одной строкой, которая трактуется либо как строка с разделителями либо как список. Несмотря на то, что три метода в итоге дают одинаковый результат, между ними есть разница в том, насколько правильно будет работать конкурентный (одновременный для нескольких процессов) доступ к данным и индексам. В случае хранения только данных этот вопрос практически не стоит, поскольку операция set атомарная. В случае применения параллельных структур индексов существует момент между состояниями, когда записи нет, но индекс есть, или индекс есть но записи нет. Этот вопрос решается обычно с помощью применения блокировок. Операция set нового значения записи обрамляется командами
l +^Data(id)
s ^Data(id)=ObjVal
l -^Data(id)
И внутри функций удаления / вставки индексных записей также вставляются обрамляющие блокировки. Наличие блокировок особенно критично в случае исполнения кода в контексте транзакции и возможности выполнения операции trollback.
Различие в режиме перестроения индекса, а именно что раньше появится в базе — индексная запись или запись с данными, позволяет построить в некотором смысле самовосстанавливающуюся систему, которая будет иметь возможность восстановитсья в случае сбоя при записи строки данных. Если индекс построен раньше, то при выборке по индексу функция выборки данных может определить что индексная запись существует но ей не соответствует строка данных. В случае применения блокировок в операции обновления записи мы в функции выборки можем также попытаться заблокировать эту же запись и если блокировка оказалась успешной но записи нет, или ее состояние не соответствует индексным значениям, то значит что операция записи самой строки данных была неуспешной и следует просто удалить индексную запись. Механизм довольно громоздкий, но в ситуации когда из соображений эффективности не хочется применять транзакции, может оказаться полезным. Вопрос выбора стратегии обновления индекса при обновлении записи оставим программисту.
Операция перестроения индекса сводится к удалению всех индексных записей и перебору всех имеющихся записей с данными и построения индексных записей по каждой имеющейся записи данных. Полагаем, что есть функции DeleteIndex для удаления всех индексных записей по одному индексу. Тогда перестроение индекса может выглядеть как
UpdateIndex(IndexName)
d DeleteIndex(IndexName)
n id,ObjValue
s id=» f s id=$o(^Data(id),ObjValue) q id=» d
. d InsertIndex(IndexName,id,ObjVal)
Q