Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:MUMPS СУБД. Практика применения и опыт программирования.pdf
X
- •Предисловие
- •Введение
- •Среда исполнения
- •Команды
- •Команды присваивания
- •Условные команды
- •Команды передачи управления
- •Команды ввода-вывода
- •Служебные команды
- •Постусловия
- •Операторы
- •Переменные
- •Числа и строки
- •Функции
- •$DATA
- •$GET
- •$ORDER
- •$NEXT
- •$QUERY
- •$NAME
- •$QLENGTH
- •$QSUBSCRIPT
- •$ASCII
- •$CHAR
- •$EXTRACT
- •$PIECE
- •$LENGTH
- •$REVERSE
- •$FIND
- •$TRANSLATE
- •$JUSTIFY
- •$FNUMBER
- •$TEXT
- •$RANDOM
- •$VIEW
- •$SELECT
- •$STACK
- •Списковые функции
- •Битовые функции
- •Модули
- •Рутины
- •Передача параметров
- •Неопределенные значения
- •Шаблоны
- •Косвенность
- •Косвенность имени
- •Косвенность индексов
- •Косвенность метки
- •Косвенность аргумента
- •Косвенность шаблона
- •Интерпретатор
- •Голая ссылка
- •Очередность выполнения
- •Очередность вычисления выражений
- •Очередность вычисления имен
- •Стекование $test
- •Комментарий
- •Стандарт и расширения
- •Глобалы
- •B-дерево
- •Кодирование индексов
- •Размер блока
- •Кеширование блоков
- •Структуры
- •Индексация
- •Группировка
- •Каноничность индексов
- •Маппинг
- •Индексация данных
- •Общие принципы
- •Механизм поддержки индекса
- •Простой индекс
- •Составной индекс
- •Покрывающий индекс
- •Кластерный индекс
- •Хеш-индекс
- •Битмап индекс (bitmap)
- •Битслайс индекс (bitslice)
- •Нормирование значений
- •Выборки по индексу
- •Многоиндексная выборка (zig-zag)
- •Дифференциальное индексирование
- •Индексация длинных атрибутов
- •Межтабличный индекс
- •Индекс с условием на вставку
- •Индекс на вычисляемый атрибут
- •Индекс поиска по фрагменту
- •Индексация для шаблона (like)
- •Индексация уникального атрибута
- •Массовое перестроение индексов
- •Операции с древовидными индексами
- •Операции с битовыми индексами
- •Сортировка по индексу
- •Статистики и кардинальность
- •Конкурентный доступ
- •Параллельность выполнения
- •Блокировки
- •Функция $INCREMENT
- •Транзакции
- •Блокировки в транзакциях
- •Функция $BIT
- •Дедлоки
- •Обработка ошибок
- •Состояние ошибки
- •ZTRAP
- •GT.M
- •MiniM
- •ETRAP
- •Определение
- •$ETRAP
- •$ECODE
- •$ESTACK
- •Ошибки в обработчике ошибок
- •$STACK()
- •Трассировка
- •BREAK
- •MiniM Debugger
- •Serenji Debugger
- •Внешний мир
- •Общие принципы
- •Терминальный интерфейс
- •Сокеты
- •HTTP клиент
- •Вебсервер на MUMPS
- •WebLink
- •Проблемы HTTP
- •Поверх HTTP
- •Подключаемые DLL (SO)
- •Файлы
- •Внешние процессы
- •Порты
- •Практика применения
- •Терминальный режим
- •Редакторы рутин
- •Экспорт и импорт
- •Препроцессор
- •Формат $HOROLOG
- •Опции устройств
- •$X и $Y
- •Возврат результатов
- •Возврат по значению ($$)
- •Возврат по ссылке
- •Запись в предопределенную переменную
- •Возврат значений косвенно
- •Итеративный возврат
- •Потоковый возврат
- •%Z - рутины
- •Планирование файлов
- •Память и сборка мусора

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 означает число бит, а не байт. В зависимости
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
