Добавил:
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 - рутины
- •Планирование файлов
- •Память и сборка мусора

2.8. КАНОНИЧНОСТЬ ИНДЕКСОВ 221
4. Строка не является каноническим числом, если содержит лидирующий знак "+" или более чем один знак "-" или знак "-" с одним или
более знаками "+" или знак "-" после которого следует лидирующий
ноль.
5. Строка не является каноническим числом, если содержит иные буквы или цифры, не формирующие экспоненциальную форму числа.
6. Экспоненциальная форма числа не является канонической, если
число после представления в виде строки не совпадает с исходной.
7. Значение подходит под определение канонического числа, если задано числовой а не строковой константой или вычислено арифметически.
Пропустив через такое небольшое сито определений, М система от-
носит строку либо к числам, либо оставляет строкой.
Вот несколько примеров, демонстрирующих различную запись единицы, но представляющие различные с точки зрения М систем индексные
значения:
USER>s a("1.0")="1.0"
USER>s a("01.")="01.0"
USER>s a("01.0")="01.0"
USER>s a("1.")="1."
USER>s a("1")="1"
USER>s a("01")="01"
USER>s a("-1")="-1"
USER>s a("+1")="+1"
USER>w
a(-1)="-1"
a(1)="1"
a("+1")="+1"
a("01")="01"
a("01.")="01.0"
a("01.0")="01.0"
a("1.")="1."
a("1.0")="1.0"

222 ГЛАВА 2. ГЛОБАЛЫ
Здесь под определение канонически заданного числа подошли только
две записи:
s a("-1")="-1"
s a("1")="1"
Отметим, что большинство М систем при выводе имен переменных
отмечает кавычками те индексы, которые она использут как строковые,
и без кавычек те, которые использует как числа.
Приведем также пример, показывающий каноничность экспоненциальной формы:
USER>s a("1e100")="1e100"
USER>s a("1E+100")="1E+100"
USER>w
a(1E+100)="1E+100"
a("1e100")="1e100"
Здесь каноничной формой М системы считают только второй вариант,
с явным указанием знака показателя.
Для выяснения того, является ли строка каноническим представлением числа, разработчики на М используют оператор унарного плюс:
если применение унарного плюс к строке дает ту же строку, то строка
представляет собой каноническое число:
USER>w 1E+100
1E+100
USER>w +"1E+100"
1E+100
USER>w +"1E100"
1E+100
Поэтому, для того чтобы гарантированно использовать в качестве
значений индексов именно числа, разработчики используют при индексации унарный плюс. В этом случае М система приводит строку к числу по
правилам приведения и используется результат. Зачастую, пользователи
при вводе значений могут набрать числа, соответствующие допустимой
записи с точки зрения человека, но не каноничное с точки зрения М
системы, и унарный плюс канонизирует полученное значение.
Другой особенностью, которую следует учитывать, является возможность формирования логически эквивалентных, но физически различных
строк. Есть функции, которые могут дать логически эквивалентные но

2.8. КАНОНИЧНОСТЬ ИНДЕКСОВ 223
физически различные результаты в зависимости от особенностей внутреннего формата кодирования.
К функциям, формирующим физически различные значения, относятся функции семейства $list. Если элемент списка получен в виде
числовой константы либо был вычислен арифметически то функции формируют числовой элемент, иначе строковый. Например:
USER>s list=$lb(123.456)
USER>w $list(list,1)
123.456
USER>s list=$lb("123.456")
USER>w $list(list,1)
123.456
USER>w $lb(123.456)=$lb("123.456")
0
Здесь элементы списка в одних случаях числовые, в других строковые, но во внутреннем кодировании списков формируются различные
последовательности байт.
Автору приходилось сталкиваться с использованием списковых структур в качестве составных значений индексов в большой программной системе. Система прекрасно работала до тех пор, пока значения в список
попадали, только будучи вычисленными как числа. Как только системе
были переданы строковые значения, тут же произошла ошибка. В качестве исправления ошибки был выбран отказ от использования списковой
структуры в индексе в пользу разделителей. Хотя автор и не уверен, что
такое решение проблемы может быть единственным. Нормализация (или
канонизация) числовых значений там, где известно, что они должны
быть числовыми, а также принудительное приведение чисел к строкам
конкатенацией с пустой строкой там, где должны быть строки, также
могло бы быть решением.
К функциям, формирующим физически различные последовательности байт для логически эквивалентных значений, также относятся функции $bit, поскольку логический результат проверки бита определяется
не только тем, был ли он записан в строку, но и тем правилом, что
незаписанные биты рассматриваются как нулевые. Например, различное
формирование битовых строк из нулей:
USER>s $bit(bits1,100)=1
USER>s $bit(bits1,100)=0

224 ГЛАВА 2. ГЛОБАЛЫ
USER>s $bit(bits2,10000)=1
USER>s $bit(bits2,10000)=0
USER>w bits1=bits2
0
К общим рекомендациям для разработчиков на М можно добавить
рекомендацию не использовать форматы кодирования индексных значений, если они не дают канонического представления, иначе работоспособность кода будет зависеть от определенного прикладной системой и
комплексом тестов способа попадания данных в систему.
2.9 Маппинг
Маппинг глобалов или отображение глобалов - это механизм замены
обращения к глобалу на физическое обращение к глобалу в определенной
базе данных или группе файлов (томов).
Маппинг может выполняться на уровне имен глобалов и на уровне
индексов. Физическая трансляция имени глобала в другую базу данных может выполняться в глобал с тем же именем, с другим именем, с
другими индексами.
Традиционно, маппинг используется разработчиками, как минимум,
для так называемых системных и временных глобалов. Большинство современых рализаций поддерживает соглашение о так называемых системных рутинах и системных глобалах. В действительности, это обычные глобалы, но для них поддерживается специальное соглашение об
отображении на системную базу данных. Традиционно, системные глобалы и системные рутины первым символом имени имеют символ процент (%).
Общепринятое соглашение позволяет различным разработчикам понимать друг друга с первого символа имени. Если имя системное, то,
из какой бы текущей базы данных к ним ни обратились, физически они
(глобалы и рутины) располагаются в одной единственной системной базе данных. Такое соглашение приводит к тому, что системные рутины и
глобалы доступны всем процессам в единственном экземпляре, процессы
из различных баз данных могут обмениваться данными, и использовать
единые для системы рутины общего назначения.
Таким же правилам подчиняются так называемые временные глобалы. Для них общеиспользуемого соглашения о формировании имени нет,
но принцип тот же - они отображаются в специальную базу данных, к

2.9. МАППИНГ 225
которой применены облегченные настройки записи и журналирования.
Временные данные, оставшиеся от прошлого сеанса работы сервера, при
его старте могут быть не только удалены, но и база может быть пересоздана. Для использования временных глобалов и формирования имени
такого глобала необходимо обратиться к соответствующей части документации на используемую М систему.
Маппинг в зависимости от применяемой М системы может быть предопределенным (как в MiniM), так и полностью настраиваемым (как в
Cach´e). Если в MiniM понятие текущей области и текущей базы данных
совпадают, то в Cach´e это отдельные понятия. В Cach´e процессы логически обращаются к области, но область как таковая существует лишь как
набор правил отображения глобалов на системную, временную и обычно
специально для этой области созданную базу данных.
Cach´e позволяет организовать маппинг весьма сложно и использовать множество физически различных баз данных. Нередко применяется конфигурация области, состоящая из отображения части глобалов на
системную базу, на временную, на базу для глобалов содержащих данные, и на базу для рутин и специальных справочных данных. При этом
база данных с рутинами может передаваться целиком от разработчиков
в эксплуатацию, минуя процесс импорта.
При создании новой области средства Cach´e учитывают собственные
соглашения об отображении глобалов и рутин по умолчанию и автоматически создают их для новой области, но эти настройки всегда можно
изменить.
Пример маппинга на уровне имени:
^%SRV
^%WM
Здесь процентное имя глобала полностью отображается в системную
базу данных.
Пример маппинга на уровне индексов:
^ROUTINE("%RI")
^ROUTINE("%RO")
^ROUTINE("%BACKUP")
Здесь первый символ имени рутины - это процент (%), поэтому ветка глобала для хранения рутин ˆROUTINE физически отображается в
системную базу данных. Такое же соглашение о маппинге на уровне
индексов применяется к глобалам хранящим макрорутины и компилированный байткод.

226 ГЛАВА 2. ГЛОБАЛЫ
Практически любая многопользовательская СУБД поддерживает в
той или иной форме понятие функций и данных общего назначения, или
системную базу данных. В системах MUMPS это выполняется по первому символу, или специальным соглашением для временных глобалов.
Разумеется, у разработчика есть возможность всегда обратиться к
рутинам или глобалам хранящимся в другой базе данных, задав их место
хранения явно, и указав базу данных. Например
^|"%SYS"|COMMON(...)
^|"TEMP"|SELECT($J,...)
В этом случае М система обращается к глобалу в указанной базе,
но к используемому имени также применяется правило отображения.
Например, имена
^|"USER"|%COMMON
^|"TEMP"|%COMMON
приводят к обращению к базам USER и TEMP, но для них также применяются соглашения об отображении системного имени и физически
используются глобалы в области %SYS. Такое соглашение позволяет не
нарушить правила отображения вне зависимости от того, как были специфицированы имена глобала или рутины.
В зависимости от реализации М системы, если она поддерживает
журналирование, различные базы данных могут иметь различные настройки журналирования. Разработчикам необходимо учитывать правила настройки журналирования и маппинга для корректной работы приложений. Например, чтобы не возникла ситуация что при откате транзакций одни глобалы были восстановлены в начальное значение, а другие
нет, но приложение использует их взаимозависимо, что и приводит к
ошибке.

Глава 3
Индексация данных
3.1 Общие принципы
В этой главе речь пойдет об алгоритмах и структурах данных для индексов, их организации, поддержке и применении.
Термин индекс далее используется строго в целях обозначения дополнительных поисковых или оптимизирующих структур. Основным языком
примеров выбрано стандартное подмножество языка МUMPS. Но, хотя по возможности применяется страндартный синтаксис, в некоторых
исключительных случаях для большей читаемости применяются Cache
Object Script и MiniM Database Server - расширения. Их применение
ограничено и допускает альтернативную замену на эквивалентные выражения в иных диалектах МUMPS. Применение битмап индексов ограничено теми MUMPS системами, которые поддерживают расширенные
$BIT функции.
Индексы - это структуры данных, размещаемые параллельно и поддерживаемые синхронно основным структурам данных и имеющие основным назначением поддержание структур данных, ориентированных на
ускорение поиска или оптимизацию хранения основных данных. Здесь
под основными данными понимаются данные, хранение и работа с которыми является основным назначением системы базы данных.
При использовании основных данных система базы данных выполняет операции вставки, поиска, удаления и изменения в общем массиве их хранения. При использовании дополнительных индексных структур система параллельно обновляет индексные структуры при изменении
(вставке, изменении и удалении) основных данных и в некоторых случаях получает возможность использовать индексные структуры, ориентированные на поиск данных. Наличие такой возможности определяется
227

228 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
характеристиками и структурой индекса.
Как следует из вышеприведенного, введение индексов в систему базы
данных утяжеляет операции, связанные с изменением данных, но ускоряет операции связанные с поиском и, как обычно, в следствии этого, с
выборкой данных.
Индексные структуры сами по себе обычно не являются необходимыми для основной работы системы базы данных. И их применение
определяется программистом или администратором системы.
В большинстве общераспространенных систем баз данных поддержка
индексных структур и их использование выполняется автоматическими
средствами. В этой главе мы будем составлять структуры и алгоритмы, которые можно использовать вне автоматики и пользоваться всеми возможностями безотносительно ограничений системы базы данных.
Примерно как если бы по частям реализовали внутренние механизмы
большой системы, но в несколько упрощенном варианте.
3.2 Механизм поддержки индекса
Индексная структура по своему состоянию должна соответствовать состоянию индексируемых данных. Поэтому операции обновления индексов обычно делят на две группы - динамическое обновление индексных
структур при обновлении одной записи и массовые операции удаления /
построения индексов.
Далее будем рассматривать строки данных, устроенные для простоты
следующим образом:
1. Идентификатор записи получаем инкрементом узла ˆData
2. Значение записи хранится в узле ˆData(id)
3. Запись состоит из полей с разделителем ˜ (тильда)
4. Индексные записи храним с глобале ˆIndex
5. В записи предполагаем поля - фигура, цвет, количество
6. Общее строение записи: ˆData(id)=Figure˜Color˜Count
Операции динамического обновления индексов могут вызываться из
операции обновления записи, и либо предшествовать собственно сохранению основной записи, либо последовать ему, либо обрамлять.
Например:

3.2. МЕХАНИЗМ ПОДДЕРЖКИ ИНДЕКСА 229
; просто сохранение объекта
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 нового значения записи обрамляется
командами

230 ГЛАВА 3. ИНДЕКСАЦИЯ ДАННЫХ
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
3.3 Простой индекс
Простой индекс в некоторой литературе ещё называется обратным списком. Если структуры основных данных отображают идентификатор записи (назначенный программистом или поддерживаемый автоматически
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
