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

4.5. ФУНКЦИЯ $INCREMENT 341
Для СУБД, основанных на SQL, также зачастую поддерживается
функция автоинкрементного поля как упрощенный и специализированный для прикладного применения механизм. Автоинкрементные поля автоматически совмещают в себе как безоткатное в транзакции получение
следующего идентификатора, так и неявную систему именования таких
идентификаторов. Те СУБД, которые поддерживают автоинкрементные
поля, также должны поддерживать и механизм получения последнего
сгенерированного текущим процессом такого значения, либо, если было
автоматическое увеличение для нескольких таблиц, то получение полученного номера для указанной таблицы.
Технически в MUMPS системах безоткатное действие выполняется в
виде системной функции $increment(). Функции указывается переменная и необязательный второй аргумент, на сколько следует увеличить
значение. Если второй аргумент не указан, то функция увеличивает на
1. Если такой переменной не было или было нечисловой значение, то
функция считает что было значение 0 и создает переменную с новым
значением. В большинстве случаев разработчиками используется одноаргументная форма функции.
Вышеприведенный код с применением этой функции уже может выглядеть так:
NextId()
n id
l +^DATA
s id=$increment(^DATA)
l -^DATA
q id
И уже такой вариант обеспечивает корректный сценарий получения
следующего идентификатора для различных процессов, в том числе выполняющих откаты транзакций. Но, в силу того, что эта операция особенная, MUMPS реализации не требуют выполнения отдельной специальной блокировки используемой переменной и выполняют корректный
атомарный доступ с выполнением арифметических операций самостоятельно. Также корректным поэтому является такой код:
NextId()
q $increment(^DATA)
При выполнении функции $increment MUMPS реализация гарантирует, что, пока эта операция выполняется, ни один другой процесс не
сможет изменить это значение даже если будет устанавливать блокировки или также одновременно выполнит $increment. Во втором случае

342 ГЛАВА 4. КОНКУРЕНТНЫЙ ДОСТУП
система обеспечивает строго последовательное выполнение увеличения
значения и оба процесса получат различные значения идентификаторов.
В техническом отношении функция $increment предусматривает явное задание значения, на сколько следует увеличить значение переменной, и, в том числе, это приращение может быть дробным, нулевым и
отрицательным.
Важным моментом для более глубокого понимания работы функции
$increment является то, что ее нельзя полноценно заменить на отключение журналирования. В основную формулировку входит требование
невозврата значения при откате. И, казалось бы, если разработчик отключит журналирование на период вычисления нового значения, то эта
запись не попадет в журнал и откат транзакции не выполнит возврат
для инкрементируемого значения.
С точки зрения работы прикладной системы такая замена может оказаться корректной заменой, но с точки зрения работы сервера - нет. Если
мы будем выполнять восстановление базы данных после сбоя, то часть
данных сервер берет из бекапа, а часть данных дописывается из журнала в том, что касается изменений, еще не попавших в базу данных из
журнала. Поэтому, если в журнале не будет записей об изменении инкрементируемой переменной, то она может быть восстановлена на одно
из предыдущих состояний по файлу бекапа, но счетчик не восстановится
в корректное состояние соответствующее уже использованным номерам
идентификаторов, содержащимся в журнале.
Поэтому, для корректного выполнения операции $increment, в журнал пишется специальная запись, которую игнорирует операция отката
транзакции, но не игнорирует операция восстановления базы данных по
журналу. И в запись пишется не предыдущее значение, а значение, которое получено после изменения переменной.
Для тех MUMPS систем, которые используют автоматическую репликацию по журналу, например Cach´e, также важно использовать именно функцию $increment, а не отключение журналирования, поскольку
результат инкремента переменной также должен быть доставлен в реплицируемую базу.
Одним из важных выводов для разработчиков на М при использовании транзакций является то, что следует внимательно пересмотреть
операции получения идентификатора и стратегии использования переменных для их хранения. Нужно иметь в виду также и тот факт, что смешивание операций set и $increment никак не пресекается самой СУБД.
Если происходит их смешивание, то система при откате вернет значение,
бывшее предыдущим для set, несмотря на то, что в транзакции также
была использована и операция $increment.

4.6. ФУНКЦИЯ $BIT 343
И еще одним немаловажным фактом применения функции $increment
является то, что она не входит в стандарт языка MUMPS и стандарт,
хотя и предусматривает откат транзакций, но не предусматривает механизма безоткатных изменений. Сама функция $increment введена в
различные реализации MUMPS систем их производителями самостоятельно и согласованно между собой, опираясь на наиболее приемлемые
практики и методики.
4.6 Функция $BIT
При использовании битовых индексов в MUMPS системах важным для
понимания работы системы и ее применения является дополнительный
к механизму битовых функций механизм конкурентного доступа, выполненный специально для них.
Битовый индекс представляет собой совокупность сегментов, каждый
из которых это последовательность байт, и каждый бит в ней значим,
может иметь значение либо "объект с идентификатором, равным номеру
бита, существует" (1), либо "не существует" (0). Кроме того, применяется
правило умолчания, что если бит находится за пределами реально физически существующих байт, либо строка байт вообще не существует, то
логически для битовых операций это эквивалентно последовательности
нулей.
В техническом отношении битовые операции могут быть добавлены
к MUMPS системе внешними по отношению к ней функциями в динамической библиотеке. Автору довелось участвовать в проектах, где
использовался именно такой вариант. Разработки блестяще работали в
режиме OLAP и имели некоторые непреодолимые недочеты в режиме
OLTP.
Если операции с битами выполняются внешними по отношению к
MUMPS системе средствами, то для записи в глобал остается использовать операцию set. Для MUMPS системы это операция полной перезаписи всего значения. Конечно, есть MUMPS системы в которых применяется дифференциальное журналирование, например как в MiniM, и в
журнал записывается по возможности лишь изменение строки байт, а не
вся строка, но в целом, вообще говоря, журналируется именно операция
set.
В логическом отношении значимым изменением является изменение
одного бита, а физически это для MUMPS системы целый большой полноценный set. Это приводит к двум проблемам:

344 ГЛАВА 4. КОНКУРЕНТНЫЙ ДОСТУП
1. Для простановки одного бита требуется взять полное значение сегмента, изменить в нем 1 бит и записать новое полное значение
сегмента.
2. Для MUMPS системы видна лишь операция set.
Эти проблемы приводят к следующим последствиям: первая требует
блокировать весь сегмент на время изменения, чтобы другой процесс не
перезаписал значение сегмента ранее и его изменения не были утрачены. Поэтому возникает конфликт блокировок - хотя разным процессам
требуется изменить разные биты, им необходимо использовать одно и то
же имя блокировки. При высоконагруженной работе это приводит к увеличению вероятности взаимоблокировок. Вторая проблема приводит к
большому объему журналирования, хотя из значимых изменений - всего
один бит.
Эти две проблемы структурно не являются характерными для какойлибо определенной архитектуры или типа СУБД. В случае применения
битовых индексов в любой другой системе они также существуют. Можно увидеть в рекомендациях, в том числе и для других типов СУБД,
рекомендации использовать битовые индексы лишь для хранилищ данных, приближенных по своему режиму работы к режиму read-only, и
рекомендации по возможности не использовать битовые индексы для
задач класса OLTP.
В современных MUMPS системах, таких как Cach´e и MiniM, эти обе
проблемы были решены на уровне СУБД введением двух дополнительных механизмов, работающих при использовании битовых функций:
1. При изменении бита система выполняет эту операцию атомарно, с
внутренней синхронизацией, не попадающей в множество блокировок команды lock, и не удерживающейся до окончания транзакции.
2. При изменении бита система использует специальную запись в
журнале.
В совокупности эти обе меры приводят к тому, что один единичный
бит может быть проставлен независимо от других и при откате транзакции именно этот бит будет возвращен в предыдущее состояние, даже если другие процессы продолжают изменять битовую строку. Точно также
значимые биты будут проставляться по отдельности при восстановлении
из бекапа с дополнением по журналу.
Выполненные архитектурные меры по отношению к функции $bit
снимают необходимость использовать блокировки индексных структур

4.7. ДЕДЛОКИ 345
при перестроении битмап индексов, хотя в примерах в целях методологии
они могут присутствовать.
Кроме того, при использовании битовых индексов в современных реализациях MUMPS систем также снимается рекомендация не использовать битмап индексы в OLTP задачах, а использовать по возможности
только в OLAP задачах. В силу транзакционности таких битмап индексов они точно также применими в любых OLTP задачах, как и индексы
других типов. Это дает разработчикам свободу выбора при построении
качественно других прикладных систем.
Точно так же, как и в случае с функцией $increment, разработчики должны разделить глобалы на те, к которым применяются операции
$bit и те, к которым применяется прямое изменение другими формами
команды set. В случае смешивания способов изменения байтовой строки
MUMPS система будет журналировать именно использованную операцию вне зависимости от того, было ли изменение этой же строки иными
способами.
Нужно отметить, что функции семейства $bit не являются частью
стандарта MUMPS, соглашения принятые в одних системах, могут не
поддерживаться в других. Этот функционал не входит в уровень переносимости. Кроме того, различные MUMPS системы могут использовать
различные методы компрессии и кодирования битовых строк для уменьшения общего объема хранения. При переносе данных, таким образом,
нельзя переносить битмап индексы как есть, их необходимо перестроить
на целевой системе заново.
4.7 Дедлоки
Конкурентный доступ нескольких процессов к одним и тем же данным содержит крупную неприятность, называемую отдельным термином
мертвой блокировки. Алгоритмически эта проблема не является спецификой MUMPS систем или систем основанных на других архитектурах
или методах, а характерна для конкурентного доступа как такового.
Дедлоки (deadlock), или мертвая блокировка - это явление, событие
или состояние двух или более процессов, один из которых, заблокировав
ресурс 1, ожидает освобождения ресурса 2, но ресурс 2 в свою очередь
заблокирован другим процессом, и он ожидает освобождения ресурса 1.
Цепочка взаимоблокировок может быть более длинной, с вовлечением
нескольких процессов.
Для иллюстрации примера приведем код на MUMPS, условно воспроизводящий ситуацию с обновлением нескольких (для простоты двух)

346 ГЛАВА 4. КОНКУРЕНТНЫЙ ДОСТУП
объектов, имеющих два простых атрибута, имеющих всего два значения.
Функция action выполняет обновление как строки данных
^zAug("data")
так и двух индексов
^zAug("prop1")
^zAug("prop2")
Функция run1 выполняет имитацию изменения объектов.
Основной задачей примера является поиск решения проблемы с deadlock,
возникающим при работе с битовыми индексами, в который вероятность
конфликта доступа при обновлении индексов возрастает в тысячи раз.
Поэтому в приведенном примере воспроизведения deadlock блокировка
узла данных не выполняется.
run1()
n id,prop1,prop2
f d
. w "."
. s id=$r(2),prop1=$r(2),prop2=$r(2)
. d action1(id,prop1,prop2)
q
action1(id,prop1,prop2)
n oldprop1,oldprop2
;
s oldprop1=$lg($G(^zAug("data",id)),1)
s oldprop2=$lg($G(^zAug("data",id)),2)
;
l:oldprop1’="" +^zAug("prop1",oldprop1,id)
l:oldprop2’="" +^zAug("prop2",oldprop2,id)
l +^zAug("prop1",prop1,id)
l +^zAug("prop2",prop2,id)
;
k:oldprop1’="" ^zAug("prop1",oldprop1,id)
k:oldprop2’="" ^zAug("prop2",oldprop2,id)
;
s ^zAug("prop1",prop1,id)=""
s ^zAug("prop2",prop2,id)=""
;
s ^zAug("data",id)=$lb(prop1,prop2)
h .5
;
l:oldprop1’="" -^zAug("prop1",oldprop1,id)
l:oldprop2’="" -^zAug("prop2",oldprop2,id)
l -^zAug("prop1",prop1,id)
l -^zAug("prop2",prop2,id)
q

4.7. ДЕДЛОКИ 347
После запуска
d run1
в двух процессах они в течении некоторого времени уверенно встают в
deadlock. Будем использовать этот код как базовый для модернизации с
целью нахождения решения.
Для возникновения ситуации мертвой блокировки необходимо воз-
никновение четырех условий:
1. Процессы требуют предоставления им права монопольного управления ресурсами, которые им выделяются (условие взаимоисключения).
2. Процессы удерживают за собой ресурсы, уже выделенные им, ожидая в то же время выделения дополнительных ресурсов (условие
ожидания ресурсов).
3. Ресурсы нельзя отобрать у процессов, удерживающих их, пока эти
ресурсы не будут использованы для завершения работы (условие
неперераспределяемости).
4. Существует кольцевая цепь процессов, в которой каждый процесс
удерживает за собой один или более ресурсов, требующихся следующему процессу цепи (условие кругового ожидания).
В задачах конкурентного доступа к базе данных все эти условия
присутствуют. Для решения проблемы необходимо разорвать одно (или
более) из перечисленных условий. Приведенное далее решение проблемы
использует изменение программистом выполнения процесса так, чтобы
разорвать второе условие. Решение состоит в разбиении имен ресурсов на группы, с тем, чтобы блокировать имена ресурсов одной группы
списком (или одновременно), и имена групп ресурсов упорядочить. При
блокировке списком отпадает наращивание блокировок той же группы в
дополнение к тем, которые могут ожидаться другими процессами.
Общий метод выглядит примерно так: разбиваем ресурсы на иерархические группы. Назовем их группа1, группа2, ... группаN. При этом вводится дисциплина блокирования: ресурсы группы n+1 могут быть блокированы только если нет других блокировок группы n+1 или старше
и если есть блокировка имен группы n или младше. Группой ноль будем считать отсутствие блокировок. В рамках одной группы блокировку
выполняем списком.

348 ГЛАВА 4. КОНКУРЕНТНЫЙ ДОСТУП
Группы следует организовать таким образом, чтобы имена блокировок группы n+1 были известны после блокирования имен группы n. В
примере используется инкрементальная блокировка списком чтобы удержать имевшиеся ранее блокировки и добавить к ним новые.
Текст примера для воспроизведения / тестов:
run3()
n id,prop1,prop2
f d
. w "."
. s id=$r(2),prop1=$r(2),prop2=$r(2)
. d action3bit(id,prop1,prop2)
q
; то же самое что action3 но имитируем битовые сегменты
action3bit(id,prop1,prop2)
n str,old,oldprop1,oldprop2
s str=$na(^zAug("prop1",prop1,1))_","_
$na(^zAug("prop2",prop2,1))
l +^zAug("data",id)
s oldprop1=$lg(^zAug("data",id),1)
s oldprop2=$lg(^zAug("data",id),2)
; это на случай запуска теста без созданных данных
s:oldprop1="" oldprop1=" "
s:oldprop2="" oldprop2=" "
s old=$na(^zAug("prop1",oldprop1,1))_","_
$na(^zAug("prop2",oldprop2,1))
x "l +("_old_","_str_")"
; ничего не значит, это имитация.
; реально тут битовая операция сброса бита
x "s ("_str_")="""""
s ^zAug("data",id)=$lb(prop1,prop2)
h 0.4
; тоже ничего не значит, это имитация.
; реально тут битовая операция установки бита
x "s ("_str_")="""""
x "l -("_old_","_str_")"
l -^zAug("data",id)
q
; обычный обратный индекс
action3(id,prop1,prop2)
n str,old,oldprop1,oldprop2
s str=$na(^zAug("prop1",prop1,id))_","_
$na(^zAug("prop2",prop2,id))
l +^zAug("data",id)
s oldprop1=$lg(^zAug("data",id),1)
s oldprop2=$lg(^zAug("data",id),2)
; это на случай запуска теста без созданных данных
s:oldprop1="" oldprop1=" "

4.7. ДЕДЛОКИ 349
s:oldprop2="" oldprop2=" "
s old=$na(^zAug("prop1",oldprop1,id))_","_
$na(^zAug("prop2",oldprop2,id))
x "l +("_old_","_str_")"
x "k "_old ; ну просто совпало так
s ^zAug("data",id)=$lb(prop1,prop2)
h 0.4
x "s ("_str_")=""""" ; тут тоже удачно совпало
x "l -("_old_","_str_")"
l -^zAug("data",id)
q
Нетрудно убедиться, что замена блокировок списком на последовательные тут же приводит к ситуации deadlock в течении довольно короткого времени.
В приведенном примере две группы:
1. ˆzAug("data",...) - узел данных
2. ˆzAug("prop1",...) и ˆzAug("prop2",...) - узлы индексов
Некоторый условный пример с более осмысленным содержанием: пред-
положим, что есть объект документ с объектами - стр оки документа. Для
операций с объектом делим операции и имена блокировок на 4 группы:
1. Сохранения документа, вход - идентификатор документа, дальше
передаются имена для группы2 (имена индексов) и группы3 (идентификаторы объектов - строк документа).
2. Индексы документа.
3. Объекты строк документа, вход - идентификатор объекта строки,
дальше передаются имена для группы4 (имена индексов по строкам).
4. Индексы строк.
Ставим блокировку на идентификатор документа, выполняем операции с объектом документа. По его содержанию определяем имена блокировок для индексов документа и объектов строк документа. Блокируя
индексы, выполняем изменение индексов. Блокируя строки документа,
выполняем операции со строками. По блокированным строкам можем
определить имена индексов. Блокируем индексы, выполняем обновление
индексов.
Также следует отметить, что при использовании в битовых операциях
функций семейства $bit в современных версиях Cach´e и MiniM проблема
deadlock автоматически снимается, поскольку операция

350 ГЛАВА 4. КОНКУРЕНТНЫЙ ДОСТУП
s $bit(^data,n)=0
s $bit(^data,n)=1
корректно отрабатывается без необходимости блокирования всего сегмента, и корректно работает откат транзакции. При использовании в
качестве реализации битовых операций функций семейства $bit получаем выигрыш как в более свободной параллельной работе процессов,
так и в существенном снижении объемов журналирования изменений
индексных сегментов.
Другим решением проблемы дедлоков является разрыв четвертого
условия, кольцевого ожидания. Для этого выполнение процессов планируется таким образом, чтобы захват ресурсов выполнялся в строго определенном отношении условных имен этих ресурсов. Для этого вводится
правило упорядочивания ресурсов и дисциплина доступа, при которой
захват ресурса должен производиться строго в определенном направлении этого упорядочения.
Для вышеприведенного примера, для исключения взаимоблокировки,
нужно построить такой же список ресурсов, но блокировать не единым
списком, а провести сортировку имен в некотором обще-оговоренном
порядке и выполнять блокирование в порядке полученной очередности.
Большинство программных систем, в которых возможно возникновение взаимоблокировок, обычно применяют именно разрыв четвертого
условия и упорядочивают захват ресурсов. При этом разработчики прорабатывают ход выполнения для каждой из операций и при необходимости корректируют алгоритмы так, чтобы сохранить порядок блокирований.
Для тех ситуаций, где применение методов разрыва условий неприменимо или применение решений слишком трудоемко, разработчики должны для избежания останова системы вводить компенсатор для второго
условия (условие ожидания). Это выполняется указанием времени ожидания. Если по истечении определенного времени ресурс не был предоставлен, то процесс должен принять решение об откате выполняемого
действия и либо счесть обнаруженное состояние непреодолимым, либо
попытаться повторить операцию снова.
В MUMPS системах время ожидания указывается в одном из параметров команды lock:
lock +name:timeout
Разработчики всегда должны проверять состояние системной переменной $TEST, чтобы проверить, была ли блокировка с таймаутом успешной или нет, с помошью команд IF или ELSE, например:
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
