Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MUMPS СУБД. Практика применения и опыт программирования.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
3.19. ИНДЕКСАЦИЯ ДЛЯ ШАБЛОНА (LIKE) 291
s id="" f s id=$o(ids(id)) q:id="" d . i ^LIKEDATA(id)’?@pat k ids(id)
И, наконец, контрольный тест для запуска:
test ;
n ids,like f like="* *","*ef","*hj?" d . k ids . d LIKESELECT(like,.ids) . d dump q
dump
w "like template=""",like,"""",! n id="" f s id=$o(ids(id)) q:id="" d . w "found id=",id,", string=""",^LIKEDATA(id),"""",! q
При необходимости разработчик может вставить трассировку выпол­нения или счетчики операций и, последовательно совершенствуя алго­ритм, добиться большей оптимизации, например выполнять поиск по индексу не в индексной сортировке, а использовать сначала отсечение по фрагментам максимальной длины, например построить структуру
^LIKEDATA(id)=word ^LIKEIND(len,part,id)=""
Здесь id - идентификатор слова, word - значение слова, part - фраг­мент слова, len - длина фрагмента.
Соответственно может быть модифицировано и применение алгорит­ма поиска $$ANDnext() с тем, чтобы он выполнял проход сначала по самым длинным фрагментам.
Кроме того, каждый разработчик видит проблему по-своему, и у каж­дого могут быть свои варианты, как построить индексацию для поиска по шаблону, чтобы она выполнялась максимально быстро. Вполне возмож­но, что разработчик может использовать специфику разрабатываемой системы и / или особенности индексируемых данных.
Интересно отметить, что у поиска по шаблону может быть найде­на масса вариаций. В одном из проектов, разработанных на MUMPS с прямым программированием индексных структур и нестандартными вы­борками из индексов было применено решение, содержащее разбиение значения атрибута на фрагменты и построение индексов по фрагментам. Далее, при вводе искомой строки она также разбивалась на фрагмен­ты. Поиск выполнялся по начальным частям полученных фрагментов и
292 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
нечувствительно к регистру. Можно было ввести одну - две буквы из начала каждого из необходимых фрагментов и система автоматически и весьма быстро находила в огромном массиве те записи, в которых присутствовали фрагменты начинающиеся на те же символы.
Такой поиск решал проблему поиска по названиям или именам, когда одно название или полное имя может включать много слов, но затруд­нительно набрать в тоности то же самое, что было введено при записи данных в базу. В частности, название
ООО "Белый Медведь"
может быть введено в базу данных в разном виде, но логически эти названия эквивалентны для пользователя:
ООО "Белый Медведь" ООО Фирма "Белый Медведь" Белый Медведь, ООО ООО БЕЛЫЙ МЕДВЕДЬ
После разбиения на отдельные слова и приведения к общему регистру в индексах системы использовались значения
OOO БЕЛЫЙ МЕДВЕДЬ
Разумеется, что при применении такого механизма поиска, когда пользователь мог просто ввести в строке поиска
бе ме
система оперативно отыскивала также и необходимое ему название ООО "Белый Медведь", и, возможно, еще пару подходящих под такой же шаблон.
Скорость поиска по описанным алгоритмам такова, что поиск первых 10 - 20 строк может выполняться интерактивно, при очередном вводе символа в строке поиска.

3.20 Индексация уникального атрибута

Одной из интереснейших и спорных тем в индексации является тема ин­дексации уникального атрибута. Индексы в данном случае используются по меньшей мере в двух различных задачах:
3.20. ИНДЕКСАЦИЯ УНИКАЛЬНОГО АТРИБУТА 293
1. Для поиска нужной записи по заданному значению атрибута.
2. Для поддержания условия уникальности значения атрибута среди всех имеющихся записей.
Сам факт использования уникальных значений атрибутов вызывает споры, подобные религиозным - использовать ли их в качестве есте­ственных ключей или нет, и на каком уровне реализации системы долж­на происходить поддержка уникальности - на уровне сервера приложе­ния (прикладная логика) или на уровне сервера базы данных (нижний уровень хранения). Чтобы не вызывать не относящихся к теме вопроса противоречий, выберем условный пример, с которым могут согласиться сторонники различных подходов. Положим, что у нас есть учетная си­стема, в которой сохраняются сведения о пользователях и нам предстоит сохранить в ней атрибуты "номер паспорта" и "логин".
Оба атрибута всегда должны быть уникальны среди всех записей, никак не связаны друг с другом, и номер паспорта может со временем меняться. Кажется очевидным, что при удалении записи пользователя из системы его логин впоследствии также не может быть использован по­вторно, и что у одного пользователя могут быть два или более паспортов, с единственным ограничением, что только один из них является приме­нимым для совершения новой сделки. Назовем его текущим активным. Кроме того, пользователь системы может перестать в ней существовать в том смысле, что с одной стороны он может быть как-бы удален, с дру­гой стороны имеющиеся на него ссылки должны остаться действующими для расшифровки имевшихся на какой-то предыдущий момент времени в системе взаимосвязей.
Чтобы не возникло желания использовать логин в качестве есте­ственного ключа (догматический подход на практике обычно не рабо­тает). Мы наложим еще одно условие - логин также может со временем меняться, также как номер паспорта, логин не может быть впоследствии повторен и всегда должен быть уникален среди всех логинов, известных системе. Приведенный пример выглядит вполне разумным для того, что­бы согласиться с мыслью о правомерности существования уникальных атрибутов.
Чтобы жизнь медом не казалась, придумаем еще один уникальный атрибут - номер служебного мобильного телефона, выдаваемого фирмой своему сотруднику. Номер может со временем быть передан другому сотруднику, он может отсутствовать, и одновременно не может принад­лежать двум сотрудникам. В качестве значения такого атрибута нам без разницы что использовать - сам номер или идентификатор записи об
294 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
отдельном объекте учета. В любом случае среди всех записей о поль­зователях заданный номер телефона может или не быть ни у кого, или быть только у одного сотрудника.
В примере номер телефона, наверное, наиболее типичный пример
уникального атрибута. Часть схемы данных о пользователе будет такой:
Запись о сотруднике: ^D(iduser)=$lb(phone) Индексная запись: ^I(phone,iduser)=""
На каком уровне будет проводиться проверка уникальности атрибута, на уровне логики или на уровне хранения, видимо, не принципиально, поскольку действия в принципе должны быть выполнены одни и те же.
Рассмотрим, какие операции могут привести к модификации записи и какие из них к нарушению уникальности атрибута:
Вставка новой записи
При вставке может возникнуть конфликт уникальности, если такое значение уже было. Если не было, то вставка безопасна.
Удаление имеющейся записи
Если запись была, то ее удаление гарантированно оставляет усло­вие "либо один либо ноль" истинным, поэтому удаление записи всегда безопасно с точки зрения уникальности атрибута.
Изменение атрибута
При изменении атрибута может произойти конфликт, если новое зна­чение уже существует. Если такого не было, то изменение безопасно.
Откат транзакции
При откате транзакции сервер базы данных восстанавливает преды­дущее значение. В случае, если оно было (операции удаления и измене­ния), то сервер базы данных самостоятельно выставит значения данным, не согласуясь с поддерживаемым нами ограничением уникальности. Для того, чтобы обезопасить систему от нарушения уникальности при откате транзакции с изменением значения, необходимо применять блокировки.
В целом, уникальность значения атрибута есть лишь один из очень простых видов ограничений целостности базы данных - они могут быть объявлены гораздо более комплексно и затрагивать большое количество сущностей в весьма сложной взаимосвязи.
Для того, чтобы гарантировать, что значение всегда одно, может быть использован индекс вида
^Index(IndexName,Value)=id
В этом случае в индексе физически не может одновременно суще­ствовать две записи с разным id.
3.21. МАССОВОЕ ПЕРЕСТРОЕНИЕ ИНДЕКСОВ 295
Вне зависимости от использования механизмов транзакций, разра­ботчик должен применять блокировки, если налагаются условия не на отдельное значение атрибута, а на его зависимость от значений атрибу­тов других записей. Проверять и модифицировать записи, если значения атрибутов взаимозависимы с другими записями можно, только если на­ложена блокировка, означающая, что это условие проверяется текущим процессом. Блокировки могут быть выполнены как монопольно, так и с различением блокирования на операции чтения и записи в зависимости от выполняемой операции.
Имя блокировки не обязано соответствовать имени глобала индекса, это может быть в целом любое имя, главное чтобы система именования отображала блокируемое условие. Зачастую строение индексного глоба­ла достаточно близко к имени блокировки, и именно его разработчики и используют в типовых задачах.
Поддержание уникальности значения атрибута есть частный случай наложения условий на взаимные значения различных записей. В частно­сти, при традиционной трактовке уникальность означает существование записи с данным значением атрибута в количестве от 0 до 1. Значение 1 может быть лишь частным случаем, одним из значений для более обще­го параметра уникальности N . При замене N на, например, 7, получаем условие чтобы указанное значение присутствовало в записях числом от 0 до 7.
При выполнении таких более общих условий уникальности в дру­гих, не MUMPS системах, необходимо приводить условие к условию простой уникальности. Например, завести отдельный справочник допу­стимых значений, и ссылаться на записи из этого справочника, но с ограничением что на каждую запись справочника может сослаться лишь одна запись. Например, если необходимо выполнить условие "не более 7 синих одновременно", то придется завести 7 записей в справочнике с значением "синий" и ссылаться на такие записи, поддерживая особую дисциплину добавления и удаления записей в справочнике администра­тивно. Кроме того, понадобится реализовать стратегию монопольного захвата одной из свободных записей справочника, например с примене­нием опций
SELECT ... FOR UPDATE

3.21 Массовое перестроение индексов

Операция поддержания индексов в актуальном состоянии обычно состо­ит в обновлении индексных структур при изменении основных, индекси-
296 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
руемых. Некоторым особняком стоит операция массового перестроения индекса по каким-либо причинам. При ее исполнении перестраиваются индексные записи для одновременно большого числа записей. В эксплуа­тационном отношении это массовое перестроение может быть оптимизи­ровано как программно, так и административно (если такая возможность предусмотрена).
К массовому перестроению индексов / одного индекса приводят за-
дачи:
Создание индекса
При создании индекса СУБД должна для каждой имеющейся индек­сируемой записи выполнить вставку индексных записей. При этом, пока идет их вставка, сами данные не изменяются.
Изменение кластерного индекса
При изменении кластерного индекса все записи меняют идентифика­торы, поэтому все другие индексные записи должны быть перестроены. При перестроении записей сами данные не изменяются.
Массовый импорт данных
Обычно перед импортом данных индексы отключают, если есть такая возможность, проводят импорт, потом включают индекс. Пока индекс выключен, он просто накапливает информацию о необходимости пере­индексации определенных записей - вместо внесения индексных струк­тур делается отметка о том, что после включения индекса может быть не полное перестроение индексных записей, а только для измененных / добавленных / удаленных записей. После включения (или активации) индекса перестроение идет не по всем записям, а только по тем, о кото­рых есть отметка.
Удаление данных таблицы
При удалении индексируемых записей СУБД может выполнить как позаписное удаление (delete * from table), так и массовое освобождение занимаемого пространства (truncate table). В случае М-систем полного аналога truncate не существует, поэтому оптимизировать можно лишь удаление индексных структур - при полном удалении данных таблиц полностью удалять все индексные деревья.
Возможное изменение структуры индексируемых данных
Если в структуре индексных записей как-то использовалась информа­ция о структуре индексируемой записи, то в случае ее изменения такие индексы также должны быть перестроены - имеющиеся записи удалены и построены новые.
Изменение определения индекса
При поддержании индекса с условием на вставку или индекса с вы­числяемым значением это условие и выражение вычисления могут изме-
3.21. МАССОВОЕ ПЕРЕСТРОЕНИЕ ИНДЕКСОВ 297
ниться. В этом случае все индексные записи по такому индексу должны быть удалены и построены заново. В целях оптимизации по времени выполнения операции массового перестроения индексов мы можем ис­пользовать специфические для этих операций признаки:
1. Индексируемые данные не меняются. При этом мы выполняем цикл. Следовательно, у нас могут оказаться инварианты цикла, которые мы можем вынести за пределы цикла. Например, мы можем эска­лировать блокировки до более общих, сэкономить на создании и очистке временных переменных и так далее. Вынос инварианта за пределы цикла - это оптимизационная задача, и она может быть выполнена обычно только после составления самого кода, который нужно оптимизировать.
2. При массовой вставке индексных записей мы предполагаем, что эта операция может быть длительной и затронуть значительное ко­личество узлов. Поэтому мы может использовать наиболее общую блокировку индексных и индексируемых записей. Более того, такая эскалация может сделать бессмысленной работу пользователей, и могут быть предприняты специальные административные меры по отключению пользователей от системы на время таких длительных операций.
3. В случае если идет массовое удаление индексных записей мы в итоге должны получить состояние их полного отсутствия. Поэто­му нет нужды удалять каждый узел - можно удалить все дерево целиком.
4. В случае если идет массовая вставка индексных записей то в боль­шинстве случаев мы можем использовать специфические для СУБД средства, например $SortBegin / $SortEnd в СУБД Cach´e.
5. Мы можем программно включить или отключить опцию журна­лирования / не журналирования. Чтобы при выполнении опера­ции администратор мог выбрать - использовать журналирование каждой операции или выключить журналирование, провести пере­строение записей и снова включить журналирование, с тем чтобы после этого выполнить бекап базы - полный или инкрементный. В первом случае для восстановления базы используется предыдущий бекап плюс накат журнала, во втором - предыдущий бекап плюс повтор перестроения индексов либо просто бекап после перестро­ения индексов. Второй вариант многие администраторы выбирают
298 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
по причине его более быстрой работы - бекап выполняется опти­мизированно, поблочно, а перестроение индексных записей - поза­писно, поэтому операции с бекапом могут быть более эффективны чем операции с журналом.
Программно для массового перестроения индексов предпочтительно иметь специальную утилиту, которую можно вызвать из других утилит для составления простого средства управления сложной системой.
Поскольку при массовом перестроении индексов индексные структу­ры не соответствуют данным, выполнение операций перестроения для проектируемой системы может быть специальной операцией, выполняе­мой регламентно, в период неактивности пользователей. Иначе для ра­боты системы во время перестроения индексов разработчикам придется реализовать временные алгоритмы поиска не по индексам, а по самим данным.

3.22 Операции с древовидными индексами

К операциям с древовидными индексами относятся операции, использу­ющие деревья с индексами и реализующие над ними теоретико - мно­жественные операции. К таким операциям относят логические операции над множествами: OR (ИЛИ), AND (И), и другие. При этом деревья ис­пользуются для хранения множеств - операндов и результата операции.
Структурно древовидный индекс удерживает как значения атрибутов, так и идентификаторы записей. Поэтому в операциях над индексными деревьями есть две группы операций - как над деревьями значений атри­бутов, так и над деревьями, включающими идентификаторы записей.
Для простоты будем оперировать разработанным ранее примером под­держки простых индексов:
CreateRecords()
k ^Index k ^Data n i,Figures,Colors,Counts,Figure,Color,Count,id s Figures="квадрат~круг~отрезок~треугольник" s Colors="красный~зелёный~синий~белый" s Counts="2~5~12~8" f i=1:1:12 d . s Figure=$p(Figures,"~",$r(4)+1) . s Color=$p(Colors,"~",$r(4)+1) . s Count=$p(Counts,"~",$r(4)+1) . s id=$$InsertRecord(Figure_"~"_Color_"~"_Count) q
3.22. ОПЕРАЦИИ С ДРЕВОВИДНЫМИ ИНДЕКСАМИ 299
InsertRecord(RecordValues)
n id s id=$i(^Data) l +^Data(id) s ^Data(id)=RecordValues d InsertIndexRecords(id,RecordValues) l -^Data(id) q id
DeleteRecord(id)
q:’$d(^Data(id)) l +^Data(id) n RecordValues s RecordValues=$g(^Data(id)) d DeleteIndexRecords(id,RecordValues) k ^Data(id) l -^Data(id) q
UpdateRecord(id,RecordValues)
q:’$d(^Data(id)) l +^Data(id) n OldRecordValues s OldRecordValues=$g(^Data(id)) d DeleteIndexRecords(id,OldRecordValues) s ^Data(id)=RecordValues d InsertIndexRecords(id,RecordValues) l -^Data(id) q
InsertIndexRecords(id,RecordValues)
d InsertIndexRecord("Figure",id,$p((RecordValues),"~",1)) d InsertIndexRecord("Color",id,$p((RecordValues),"~",2)) q
DeleteIndexRecords(id,RecordValues)
d DeleteIndexRecord("Figure",id,$p((RecordValues),"~",1)) d DeleteIndexRecord("Color",id,$p((RecordValues),"~",2)) q
InsertIndexRecord(IndexName,id,Value)
s ^Index(IndexName,Value,id)="" q
DeleteIndexRecord(IndexName,id,Value)
k ^Index(IndexName,Value,id) q
Рассмотрим операции над деревьями значений атрибутов. К ним мож­но отнести операции выборки поддерева с заданным значением атрибута, выбрать поддерево со значениями атрибута меньшими чем заданное, со значениями большими чем заданное, со значениями между двумя за­данными, вычесть поддерево из другого поддерева, и получить число различных значений атрибутов. Приведем примерные реализации этих операций и как их использовать в контрольном примере.
Выборка поддерева с заданным значением атрибута.
300 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
EQ(ret,name)
m @ret=@name q ret
USER>d EQ^TREEOP($NA(a),$na(^Index("Figure","круг")))
Получить поддерево из заданного поддерева кроме указанного значе-
ния.
NE(ret,name,value)
m @ret=@name k @root@(value) q
NE2(ret,name,value...)
m @ret=@name n i f i=1:1:value k:$d(value(i)) @ret@(value(i)) q
USER>d NE^TREEOP($na(res), »
$na(^Index("Figure")),"круг")
USER>d NE2^TREEOP($na(res), »
$na(^Index("Figure")),"круг","квадрат")
Получили всё поддерево с индексом кроме поддерева с указанным значением. В первом случае стандартный М код, с вычитанием одно­го значения, во втором - с расширением COS, с вычитанием значений, заданных списком.
Выборка поддерева со значениями атрибута, меньшими заданного.
LT(ret,name,value)
n i s i=value f s i=$o(@name@(i),-1) q:i="" m @ret@(i)=@name@(i) q
USER>d LT^TREEOP($na(r),$na(^Index("Figure")),"отрезок")
Выборка поддерева со значениями атрибутов, большими чем задан­ное.
GT(ret,name,value)
n i s i=value f s i=$o(@name@(i)) q:i="" m @ret@(i)=@name@(i) q
USER>k d GT^TREEOP($na(r),$na(^Index("Figure")),"отрезок")
Выборка поддерева со значениями между двумя заданными.