Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MUMPS СУБД. Практика применения и опыт программирования.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
3.6. КЛАСТЕРНЫЙ ИНДЕКС 241

3.6 Кластерный индекс

Кластерный индекс отображает совокупность значений одного или нес­кольких атрибутов на совокупность значений остальных атрибутов. В случае применения простого индекса он также может быть назван кла­стерным, если значение id задается неавтоматически и является равно­значным с остальными атрибутом, и некластерным если поддерживается автоматически и не входит в перечень атрибутов. То есть хранение в предыдущем случае структуры данных как
^Data(id)=Figure~Color~Count
Является кластерным индексом если значение id задается неавтома-
тически и id является одним из атрибутов записи.
Интересна история с развитием идей SQL в отношении кластерных индексов. Первоначальная формулировка SQL касалась такого объеди­нения строк в таблице, что все значения в строке равнозначны и строка объединяет их в одну запись. Каким бы образом не выполнялось хра­нение записей, во внутренних механизмах любой СУБД присутствует способ адресации и идентификации любой записи. При этом практиче­ски сразу перед разработчиками встала проблема того, что этот внут­ренний идентификатор на языке SQL напрямую практически недоступен и для указания определенной записи среди набора имеющих одинаковые значения необходимо было применить дополнительное поле данных, в которое самостоятельно писать условный идентификатор такой записи, но к которому СУБД относится так же, как и к любому другому полю записи.
Структуру вида
IntId Color Figure F23H Синий Круглый FM90 Синий Круглый
было необходимо трансформировать в
IntId Id Color Figure F23H 14 Синий Круглый FM90 15 Синий Круглый
Впоследствии достаточно быстро обнаружилось, что именно эти ус­ловные дополнительные значения, добавляемые к каждой из записей, и
242 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
идентифицируют запись в практически всех операциях, и всегда на та­кое искусственное поле добавляется индекс. Получалось, то из-за недо­ступности внутреннего идентификатора разработчики заставляли систе­му выполнять то же самое врукопашную.
Далее разработчики таких SQL СУБД переизобрели заново кластер­ный индекс и исключили внутренний идентификатор, заменив структуру на
Id Color Figure 14 Синий Круглый 15 Синий Круглый
и используя поле Id (указываемое при объявлении как кластерного хра­нения, так и кластерного индекса) вместо своего внутреннего иденти­фикатора IntId, хотя многие СУБД и приписывают свой идентификатор записи для выполнения внутренних системных операций.
В настоящее время большинство коммерческих SQL СУБД либо уже добавили поддержку кластерного хранения записей и кластерных ин­дексов, либо ими пока не восхищаются. Хотя при этом в большинстве случаев применений в SQL системах кластерный индекс используется не столько для действительно индексирования, сколько для улучшения эффективности структуры хранения, и в качестве кластерного поля ис­пользуется именно искусственно добавленный ключ.
Построение кластерного индекса необязательно означает уникаль­ность индексируемого атрибута. Но, в случае такой уникальности, при­менение кластерного индекса более оправдано. Пусть мы строим кла­стерный индекс по атрибуту Figure. Поскольку атрибут неуникален, то мы должны отобразить значение Figure на совокупность Color + Count:
^Data(Figure,internalid)=Color~Count
Здесь значение internalid является вспомогательным строго в целях создания уникальности отображения Figure на Color + Count. Вообще говоря, значение internalid по его назначению даолжно быть уникально лишь в пределах каждого Figure. Но также можно использовать уни­кальность в пределах всего набора записей.
Вообще говоря, большинство систем баз данных поддерживают со­здание только одного кластерного индекса на массив. По своему смыслу кластерный индекс сочетает в себе и простой индекс и саму запись дан­ных. В отличие от простого идентификатора в случае простого индекса при применении кластерного индекса идентификатор строки становит­ся составным. В нашем случае это совокупность значений атрибутов, входящих в индекс, и внутреннего дежурного идентификатора.
3.6. КЛАСТЕРНЫЙ ИНДЕКС 243
Внутрисистемная адресация строки в случае применения кластерного индекса везде заменяется с использования простого id на совокупность Figure + internalid.
Операции выборки в случае использования кластерного индекса, есте­ственно, должны учитывать, что хранение массива данных уже не одно­уровневое, а двухуровневое и, скажем, при выборке всех записей исполь­зовать либо вложенный цикл с перечислением по $o(), либо применять перечисление по $q().
Применение кластерного индекса нисколько не мешает применять также и простые индексы на тот же массив данных по другим атри­бутам, а также и по тому же самому. Опять же, в отличие от простого индекса, следует использовать составной идентификатор. Например, до­полнительный индекс по цвету может выглядеть так:
^Index("Color",Color,Figure,internalid)=""
Или можно использовать некоторую функцию, которая по значениям Figure и internalid составляет значение, которое не может быть получено при иных значениях Figure и internalid.
^Index("Color",Color,$$func(Figure,internalid))=""
Или в случае уникальности internalid в пределах массива данных
^Index("Color",Color,internalid)=Figure
Структура второго варианта позволяет сохранить уникальность отоб­ражения и одновременно получить составной идентификатор. По значе­нию Color получаем набор internalid и для каждого из них восстанавли­ваем полный идентификатор, используя значение Figure.
Основными преимуществами кластерных индексов являются эффек­тивность использования дискового пространства в случае уникально­сти атрибута кластерного индекса и эффективность кеширования блоков данных при кластеризации таблиц.
Небольшой пример реализации кластерного индекса:
ind02 ; кластерный индекс по атрибуту Figure
q
CreateRecords()
k ^Index k ^Data n i,Figures,Colors,Counts,Figure,Color,Count,id s Figures="квадрат~круг~отрезок~треугольник" s Colors="красный~зелёный~синий~белый"
244 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
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
InsertRecord(RecordValues)
n internalid,Figure,id s Figure=$p(RecordValues,"~",1) s internalid=$i(^Data) l +^Data(Figure,internalid) s $p(RecordValues,"~",1)="" s ^Data(Figure,internalid)=RecordValues s id=$lb(Figure,internalid) d InsertIndexRecords(id,RecordValues) l -^Data(Figure,internalid) q id
DeleteRecord(id)
n RecordValues,Figure,internalid s Figure=$lg(id,1),internalid=$lg(id,2) q:’$d(^Data(Figure,internalid)) l +^Data(Figure,internalid) s RecordValues=$g(^Data(Figure,internalid)) d DeleteIndexRecords(id,RecordValues) k ^Data(Figure,internalid) l -^Data(Figure,internalid) q
UpdateRecord(id,RecordValues)
d DeleteRecord(id) q $$InsertRecord(RecordValues)
DeleteIndex(IndexName)
k ^Index(IndexName) q
InsertIndexRecords(id,RecordValues)
d InsertIndexRecord("Color",id,$p(RecordValues,"~",2)) d InsertIndexRecord("Count",id,$p(RecordValues,"~",3)) q
DeleteIndexRecords(id,RecordValues)
d DeleteIndexRecord("Color",id,$p(RecordValues,"~",2)) d DeleteIndexRecord("Count",id,$p(RecordValues,"~",3)) q
InsertIndexRecord(IndexName,id,Value)
n Figure,internalid s Figure=$lg(id,1),internalid=$lg(id,2) l +^Index(IndexName,Value,Figure,internalid) s ^Index(IndexName,Value,Figure,internalid)="" l -^Index(IndexName,Value,Figure,internalid) q
3.7. ХЕШ-ИНДЕКС 245
DeleteIndexRecord(IndexName,id,Value)
n Figure,internalid s Figure=$lg(id,1),internalid=$lg(id,2) l +^Index(IndexName,Value,Figure,internalid) k ^Index(IndexName,Value,Figure,internalid) l -^Index(IndexName,Value,Figure,internalid) q
В этом примере при обновлении строки данных производится её уда­ление, потом вставка новой. Это связано с тем, что новое значение атри­бута Figure может быть изменено. В более практичной реализации луч­ше определять именилось ли значение кластерного атрибута. Эта тема рассматривается позднее, в теме дифференциального перестроения ин­декса. Построение остальных двух индексов такое же как в предыдущем случае, поскольку они также простые.

3.7 Хеш-индекс

Хеш-индекс - это структура данных, отображающая на идентификатор строки не значение или совокупность значений атрибутов, а хеш-код этого значения, или совокупности значений. Выбор хеш-функции и сама необходимость применения хеш-индексов в каждом конкретном случае выбирается программистом отдельно. В расчет должны браться факторы как возможности применения индекса для выборки, так и скорость кода поддержки обновления индексных записей.
Структурно хеш индекс выглядит также как и другие:
^Index("Figure",$$hash(Figure),id)=""
Также хеш-индекс может быть составным или хеш строится по двум или более атрибутам. В этом случае хеш-функция применяется к сово­купности значений атрибутов:
^Index("FigureColor",$$hash(Figure,Color),id)=""
Поскольку, по определению, хеширование не является однозначным отображением, применив вместо значений атрибутов их суррогаты в ви­де хеш кодов мы получаем грязную выборку. То есть, зафиксировав значение Figure, идентификаторы которых мы хотим, получим хеш-код Figure. Но в силу неуникальности хеширования в подъиндексах узла ˆIndex("Figure",$$hash(Figure)) будут идентификаторы также записей с другим значением атрибута Figure.
246 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
Применяется хеш-индекс в случаях когда допустимо построение вре­менных структур, и быстрое деление записей на группы существенно ускоряет какую-либо операцию. Применение хеш-индексов весьма эф­фективно если необходимо соединить две выборки по длинному атрибу­ту или по совокупности атрибутов, поскольку хеш-структура занимает в памяти гораздо меньше места и для относительно небольших выборок (несколько сот записей) обеспечивает достаточно быстрый поиск.
При соединении двух таблиц, для который нет индексов для полей соединения, в которых есть, скажем, N и M записей, выполняется ска­нирование первой и для каждой найденной записи производится полное сканирование второй. Получается N*M сравнений.
В случае применения хешированного соединения сервер строит вре­менный хеш-индекс на вторую таблицу. Это M операций. Положим, что хеширование дает приблизительно равномерное разбиение на n групп. Проходом по записям первой таблицы получаем значение атрибута со­единения, вычисляем его хеш-код, и перебираем группу из примерно M/n записей с этим хеш-кодом. На одну запись из N таким образом приходится примерно M/n сравнений. То есть количество сравнений мы уменьшили примерно пропорционально n. В силу того, что обычно в вы­борках участвует конечное число элементов (M), используя характерный для такой выборки параметр разбиения хеша n мы сводим временную сложность от квадратичной к почти линейной. Если число n сопостави­мо с M, то мы можем получить почти простой индекс.
В случае если число n намного меньше кардинальности атрибута, такой способ гораздо эффективнее, чем обычный индекс, по расходам памяти. То есть построение временного полноценного индекса на вторую таблицу в приведенном случае может быть менее эффективным из-за расхода памяти и обесценивания кеша.
Другим способом является получение выборки сразу с хешированием атрибутов, участвующих в соединении.
Применение хеш-индексов отличается от применения обычных, опять же, в силу неуникальности хеширования. Они обязательно требуют при­менения пусть некоторого, но дополнительного перебора записей в хеш­группе, или, другими словами, выборки с фильтрацией.
Эффективность хеш-функций, вообще говоря, не предмет индекса­ции, поэтому ниже приведем только простейшую. Сами же хеширующие функции тем более качественны, чем более равномерное разбиение по группам дают для входного набора строк. Ко второй качественной харак­теристике хеширования относится число групп, на которое хеш разбива­ет входные данные. Третья качественная характеристика хеширования
- скорость вычисления хеша. Во многих случаях оправдано применение
3.8. БИТМАП ИНДЕКС (BITMAP) 247
встроенных системных функций вычисления CRC.
hash(Value,groupsize)
n ret,i,magic1,magic2 s magic1=0 s magic2=142 s:’+$g(groupsize) groupsize=255 s ret=magic1 f i=1:1:$l(Value) d . s ret=((ret+magic2)*$a($e(Value,i)))#groupsize q ret
Здесь каждой строке ставится в соответствие одно из чисел в диапа-
зоне от 0 до groupsize, где groupsize по умолчанию 255.
На практике хеш-индексы редко используются в качестве постоянных структур, и чаще применяются для соединения временных выборок. В случае, если строится выборка для дальнейшего соединения с другой выборкой по хешу, то при их построении сразу применяется построение выборки в хеш-структуре.
В действительности, приведенная структура хеш-индекса это лишь приближение, поскольку MUMPS всегда физически оперирует парами ключ - значение, объединенными в деревья. Любое обращение по номеру хэш-кода в любое дерево это в действительности итеративная процедура поиска с отсечением, пусть и довольно быстрая, в то время как хеш­таблицы - это массивы прямой адресации, где хэш-код используется в качестве номера начала списка коллизий.

3.8 Битмап индекс (bitmap)

Битовый индекс является дополнительной структурой данных к основ­ной, отображающей значения индексируемого атрибута на набор иден­тификаторов записей. В целом определение такое же, как и у простого индекса, но набор идентификаторов записей формируется иначе. Если в случае простого индекса хранятся значения идентификаторов и, вооб­ще говоря, они могут быть произвольными, в том числе строками или составными, то в случае битовых индексов идентификаторы рассматри­ваются строго как целые числа.
Набором идентификаторов является битовая последовательность, в которой идентификатору строки соответствует положение бита в соот­ветствии с его величиной. Наличие записи с заданным значением атри­бута отмечается 1, отсутствие - 0 или пустым хвостом. Положим, что есть три записи:
248 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
1 шарик красный 12 2 шарик синий 5 3 кубик красный 7 4 кубик синий 3
В этом случае есть 4 идентификатора, их значения рассматриваются как позиции битов в битовой карте. В карте получается в данном случае 4 бита. По атрибуту фигура два различных значения, поэтому в этом индексе будет две карты. То же самое по атрибуту цвет.
Индекс по фигуре значение атрибута биты 0 1 2 3 4 шарик 0 1 1 0 0 кубик 0 0 0 1 1 Индекс по цвету значение атрибута биты 0 1 2 3 4 красный 0 1 0 1 0 синий 0 0 1 0 1
Чтобы использовать битовые карты, база данных должна поддержи­вать операции с битовыми строками. Это может быть либо встроенный функционал, либо быть реализован с помощью какого-либо модуля рас­ширения. В примере будем использовать функции $bit из состава Cach´e или MiniM.
Операции получения идентификаторов строк по значениям атрибу­тов сводятся к использованию битовых операций с битовыми картами и выборке из получившейся битовой карты позиций ненулевых битов. Значения этих позиций считаются идентификаторами строк.
К особенностям битовых индексов относится то, что они могут быть, как и простые индексы, составными, но не могут быть кластерными. Другим ограничением является то, что идентификатором строки данных должно быть строго натуральное число. В большинстве систем второе ограничение совершенно незаметно, поскольку идентификаторы и без того поддерживаются в автоинкрементном режиме.
Битовые индексы и простые индексы по одной и той же таблице могут быть совмещены. Но если для таблицы используется кластерный индекс, то битовый индекс для нее уже не может быть применен, поскольку иден­тификатор строки фактически нечисловой. Совмещение и одновременное применение простых и битовых индексов выполняется алгоритмически. Также может быть организована многоиндексная выборка совместно из нескольких простых и битовых индексов.
Небольшой пример реализации битового индекса:
3.8. БИТМАП ИНДЕКС (BITMAP) 249
ind04 ; битовые индексы, автоматическое поддержание идентификатора
q
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
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
DeleteIndex(IndexName)
k ^Index(IndexName) q
InsertIndexRecords(id,RecordValues)
d InsertIndexRecord("Figure",id,$p((RecordValues),"~",1)) d InsertIndexRecord("Color",id,$p((RecordValues),"~",2)) d InsertIndexRecord("Count",id,$p((RecordValues),"~",3)) q
DeleteIndexRecords(id,RecordValues)
d DeleteIndexRecord("Figure",id,$p((RecordValues),"~",1))
250 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
d DeleteIndexRecord("Color",id,$p((RecordValues),"~",2)) d DeleteIndexRecord("Count",id,$p((RecordValues),"~",3)) q
InsertIndexRecord(IndexName,id,Value)
s $bit(^Index(IndexName,Value),id)=1 q
DeleteIndexRecord(IndexName,id,Value)
s $bit(^Index(IndexName,Value),id)=0 q
Сравнив с рутиной ind01 для простых индексов, несложно понять, что единственные отличия содержатся в функциях InsertIndexRecord и DeleteIndexRecord. Это простой учебный пример, здесь не проверяется что идентификатор действительно является натуральным числом.
Также строится только одна битовая карта. В реальном приложении, несмотря на то, что хранится только один бит на запись, битовые кар­ты должны быть сегментированы, поскольку работа с неограниченными строками обычно не предусматривается. Разбиение по сегментам выпол­няется вычислением номера сегмента и позиции в сегменте по значению идентификатора. В системе должна быть выбрана одна и та же гра­нулярность сегмента, чтобы битовые логические операции выполнялись корректно. Точное значение размера битового сегмента, вообще говоря, лучше определять экспериментально. Об этом пойдет речь в части срав­нения битовых и простых индексов.
Простой пример сегментирования битового индекса:
#define BITSEGMENT 32000 InsertIndexRecord(IndexName,id,Value)
n seg,pos s seg=id\$$$BITSEGMENT s pos=id#$$$BITSEGMENT s $bit(^Index(IndexName,Value,seg),pos)=1 q
DeleteIndexRecord(IndexName,id,Value)
n seg,pos s seg=id\$$$BITSEGMENT s pos=id#$$$BITSEGMENT s $bit(^Index(IndexName,Value,seg),pos)=0 q
Здесь номер сегмента получается делением идентификатора нацело на размер сегмента, а позиция в сегменте определяется остатком от де­ления нацело идентификатора на размер сегмента. Здесь, поскольку зна­чения идентификаторов рассматриваются как позиции битов, величина сегмента BITSEGMENT означает число бит, а не байт. В зависимости