Добавил:
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.2. КОДИРОВАНИЕ ИНДЕКСОВ 191
int count = 0;
char buf[ 64];
for( d1 = 10.0; d1 < 20.0; d1 += 0.00001)
{
sprintf( buf, "%.15G", d1);
d2 = atof( buf);
if( d1 != d2)
{
printf( "%.15G\n", d1);
count++;
if( count >= 5)
{
break;
}
}
}
return 0;
};
Первые 5 найденных чисел:
10.00003
10.00004
10.00005
10.00006
10.00007
Для решения проблемы могут быть применены различные решения
про лексикографическому кодированию целых и дробных чисел и строк
так, чтобы при сортировке результата как массива байт значения сортировались по стандарту MUMPS.
Как один из вариантов может быть использован вариант лидирующего байта для разделения пустых строк, чисел и строк, после которого
следует либо само число, либо строка:
Лидирующий байт
0x00 пустая строка
иное число
0xFF строка
Для чисел выполняется приведение к виду мантисса + порядок, например:

192 ГЛАВА 2. ГЛОБАЛЫ
Значение Кодированное
123456 .123456 * E+6
0.0078 .78 * E-2
123.456 .123456 * E+3
Для отрицательных чисел используются дополнения знаков до 10 для
обеспечения сортировки отрицательных чисел:
Значение Кодированное
-123456 .987654 * E+6
-0.0078 .32 * E-2
-123.456 .987654 * E+3
Для корректности сортировки используем первые байты для записи
порядка, сами байты порядка также формируем так, чтобы отрицательные числа сортировались перед положительными, используя дополнение.
Сами мантиссы могут быть также представлены как в десятичной, так и
в любой другой системе счисления.
Различные М системы могут использовать различные основания для
предствления порядка и мантиссы чисел и различные дополнения для
отрицательных. Поэтому у разных М систем может варьироваться число
байт, требуемых для кодирования чисел. Отдельной интересной задачей
может стать исследование оптимальных параметров для дополнений и
основания при лексикографическом кодировании.
Для определения длины байтовой последовательности, сколько занимает кодированное представление индекс, используются либо байты, задающие длину, либо байты - терминаторы, как в случае системы GT.M.
В качестве терминатора GT.M использует нулевой байт. Поскольку он
зарезервирован, то для кодирования двух специальных байт используется кодирование в виде не одного, а двух байт:
0 -> 1 1
1 -> 1 2
Соответственно, при декодировании значений эти последовательности заменяются на обратные им.
Различные М системы и другие СУБД используют различные алгоритмы и схемы кодирования чисел для обеспечения сортировки чисел.
Современные СУБД скрывают сложности кодирования и представления
информации так, что результатом разработчики могут пользоваться совершенно не замечая внутренних трудностей и особенностей.
Использование кодирования национальных алфавитов имеет своими
следствиями то, что разработчики должны применять при чтении данных то же самое определение символов, которое было использовано при

2.3. РАЗМЕР БЛОКА 193
их записи. В случае экспорта и импорта данных в стандартных форматах СУБД используют для формирования внешних файлов кодирование
данных как есть, в том виде в котором они попали в базу. В случае использования бекапов или блочного экспорта и импорта используются не
отдельные логические записи, а блоки целиком. Поэтому для чтения восстановленных баз и для импорта блочного экспорта нужно использовать
то же самое определение символов. Если требуется сменить определение
символов, то необходимо экспортировать данные в стандартном переносимом формате и импортировать на другой системе или базе данных с
другим определением символов.
2.3 Размер блока
Различные СУБД, и системы класса MUMPS в том числе, управляя
данными в базе данных, оперируют блоками данных. И у каждого из
блоков имеется размер. Обычно он указывается справочно в описании
системы и для программистов обычно не имеет особенного значения,
чему он равен.
При этом для тех, кто хочет изучить и понимать работу с базами
данных более углубленно, стоит обратить внимание на этот параметр и
на факторы, влияющие на производительность.
Первым пунктом, который на первый взгляд вообще не упоминается,
но подразумевается и является важным, это равенство размеров блоков.
Каким бы ни был выбран размер, у разных блоков он одинаковый. Вообще говоря, СУБД алгоритмически может использовать и блоки разных
размеров, это вопрос техники, и даже вообще не использовать блочное
строение. Но если все блоки одинакового размера, то это позволяет существенно и упростить и ускорить операции вычисления положения и
адресацию нужного блока, а также использовать заранее зарезервированные пространства в кеше блоков вместо постоянного динамического
распределения памяти.
Размер блока технически может быть любым, но в целях оптимизации операций ввода - вывода с дисками, физически являющимися блочными устройствами, используются размеры, кратные степени 2, обычно
начиная с размера 512 байт. 512 байт - это обычно минимальный размер
сектора в байтах. Впоследствии размер блоков опирали не на размер
сектора, а на размер кластера, а размер кластеров у современных среднестатистических компьютеров составляет обычно 4 - 8 килобайт.
Современные СУБД используют блоки размером, конечно, не такие
маленькие, как один сектор или просто кластер, а начиная с размера

194 ГЛАВА 2. ГЛОБАЛЫ
хотя бы 1 килобайт. Типовые размеры блоков - 1, 2, 4, 8, 16, 32, 64
килобайт. Б´ольшие размеры используются редко. Кроме фиксированного для выбора размера блоков некоторые СУБД дополнительно могут
предлагать индивидуально задаваемые значения для размера блока.
Изменение размера блока имеет своими последствиями улучшение
или ухудшение эффективности. Если выполняется чтение блока, а это
самая тормозящая работу СУБД операция, то привод диска физически
возвращает операционной системе в действительности порцию размером
в один кластер, и ему нет особенной разницы, был запрошен один байт
или сто, или весь кластер целиком.
Несложно провести эксперимент, определяющий производительность
чтения при различных размерах читаемых порций. Пусть есть файл с
размером мегабайт и программа, использующая чтение разными порциями:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
char* filename = "test.dat";
char* openmode = "rb";
int total_size = 1024 * 1024;
int read_size[] =
{
1,
16,
32,
64,
128,
256,
512,
1024,
2048,
4096,
8192,
16384,
32768,
65536
};
__int64 GetCurrentMilliseconds()
{
SYSTEMTIME system_time;
FILETIME file_time;
__int64 ret;
GetLocalTime( &system_time);

2.3. РАЗМЕР БЛОКА 195
SystemTimeToFileTime( &system_time, &file_time);
ret = *(__int64*)&file_time;
ret /= 10000;
return ret;
};
#pragma argsused
int main(int argc, char* argv[])
{
static char buffer[ 1024 * 100];
for( int i = 0; i < sizeof( read_size) /
{
sizeof( read_size[ 0]); i++)
int size = read_size[ i];
printf( "size: %6d ", size);
__int64 start = GetCurrentMilliseconds();
for( int j = 0; j < 100; j++)
{
FILE* file = fopen( filename, openmode);
fseek( file, 0, SEEK_SET);
for( int pos = 0; pos < total_size; pos += size)
{
fread( buffer, size, 1, file);
}
fclose( file);
}
__int64 end = GetCurrentMilliseconds();
int ms = (int)( end - start);
printf( "time: %6d\n", ms);
}
return 0;
};
Здесь собственно чтение повторяется многократно, чтобы получались
значения времени заметно больше нуля и можно было сделать оценку.
При работе такого теста получается следующий отчет:
size: 1 time: 2856
size: 16 time: 385
size: 32 time: 307
size: 64 time: 268
size: 128 time: 252
size: 256 time: 240
size: 512 time: 220
size: 1024 time: 115
size: 2048 time: 63

196 ГЛАВА 2. ГЛОБАЛЫ
size: 4096 time: 35
size: 8192 time: 23
size: 16384 time: 17
size: 32768 time: 14
size: 65536 time: 12
Здесь выполнялось чтение файла, размещенного в файловой системе
с кластером 4096 байт. Можно проанализировать соотношение времени
выполнения и порции чтения, например размеры чтений 4096 и 32768
отличаются в 8 раз, но времена выполнения для них отличаются в 2, 5
раза. При этом размеры чтений 4096 и 512 также отличаются в 8 раз,
но времена чтения отличаются уже в 6, 2 раз. Соотношение показывает,
что используется весьма неплохой диск и контроллер диска.
Дополнительно оценим, каково соотношение времен при шаге чтения,
отличающемся в 2 раза:
size: 32 time: 307
size: 64 time: 268
size: 128 time: 252
size: 256 time: 240
size: 512 time: 220
size: 1024 time: 115
size: 2048 time: 63
size: 4096 time: 35
size: 8192 time: 23
size: 16384 time: 17
size: 32768 time: 14
size: 65536 time: 12
1,1
1,1
1,0
1,0
1,1
1,9
1,8
1,8
1,5
1,4
1,2
Отношения показывают, то относительная эффективность чтения в
данном случае группируется вокруг размера, соответствующего размеру
кластера.
Таким образом, с точки зрения эффективности чтения с диска нет
смысла делить работу с базой на блоки по размеру существенно меньше
или существенно больше чем кластер.

2.3. РАЗМЕР БЛОКА 197
Следующим фактором для оценки ценности чтения является потенциальное кеширование. Один блок содержит целый набор порций. Даже
если нам необходима только одна из них, мы читаем весь блок. При
этом, поскольку блок помещается в кеше, то также в кеше оказываются
остальные порции, размещенные в этом же блоке. И, если у нас высока
вероятность обращения к данным, находящихся далее в сортированном
порядке в том же блоке, то высока вероятность их получения уже из кешированного блока без чтения с диска. Таким образом, увеличивая размер блока, мы, не сильно ухудшая относительную эффективность физического времени доступа, улучшаем коэффициент кеширования данных,
или вероятность получения следующей порции без чтения.
Другим фактором для оценки эффективности работы в зависимости
от размера блока является применяемое структурирование самих данных. Если наши порции (ключи в случае MUMPS) маленькие, то для
поиска нужного ключа в одном блоке надо перебрать (пусть и в сортированном виде) несколько ключей, находящихся в блоке. При увеличении
размера блока мы статистически увеличиваем число ключей в блоке и
время поиска в одном блоке. При этом, увеличивая число ключей в
блоке, мы одновременно увеличиваем число ссылок, выдаваемых этим
блоком и увеличиваем коэффициент ветвления этого блока, что очень
важно для быстроветвящихся деревьев, к которым и относятся B* деревья.
Грубо говоря, имея блок большего размера, мы сильнее отсекаем область поиска среди дочерних блоков, и два соседних ключа вырезают
в базе меньшее число дочерних ключей, и в идеале каждая ссылка с
блока ссылок должна вести на блок данных. И обратно, если в блоке
ключи большие, то их в блоке помещается меньше, и их быстрее найти
при поиске в блоке, но такой блок дает меньшее отсечение области поиска. В самом крайнем и экстремальном случае, если в блоке всего один
ключ и две ссылки, то такой блок делит область базы всего на 2 части,
и относительная эффективность такого блока (необходимо целое чтение
блока для деления всего на 2) минимальна.
Есть и обратная сторона размера блока - если он слишком маленький,
то на нем может вообще не поместиться три ключа (логический минимум
для делящегося дерева), и не стоит применять блоки такого размера, что
в них СУБД не сможет записать что-то осмысленное.
Несложно видеть, что, в действительности, вопрос размера блока
непрост. Для того, чтобы СУБД работала эффективно, надо как-то выбрать такой размер. И, действительно, крупными фирмами проводились соответствующие исследования, при каких размерах блоков данных
СУБД работают более эффективно.

198 ГЛАВА 2. ГЛОБАЛЫ
Общее резюме таких исследований, проводимых в том числе целенаправленно фирмой Oracle, вылилось в набор простых эмпирических
правил:
1. Для применяемых в настоящее время компьютеров, жестких дисков, файловых систем и среднестатистических характеристик данных оптимум размера находится примерно между 6 и 12 килобайт.
2. Если данные по размеру стремятся к малым порциям, то размер
блока оптимальнее принять 2 или 4 килобайт.
3. Если данные стремятся к увеличению порций, то размер блока
оптимальнее увеличить до 16 или 32 килобайт.
4. Если данные изначально составляют очень большие порции, то размер блока оптимальнее использовать в 64 килобайт или использовать отдельные файлы не блочного формата.
В настоящее время большинство СУБД класса MUMPS используют
размер блока 8 килобайт в качестве основного.
2.4 Кеширование блоков
Формально говоря, системе базы данных, работающей с цепочками блоков, по каждому обращению к глобалу требуется несколько чтений с
диска. Для того, чтобы доступ к глобалам был не настолько медленным,
СУБД применяют механизм кеширования.
Основной принцип оптимизации состоит в том, чтобы по возможности не делать то, что уже было сделано и возможно использовать повторно. Кеширование блоков данных позволяет не читать блоки повторно,
если блок уже был прочитан и его можно использовать повторно.
Современные СУБД придерживаются одного из ключевых принципов
серверного программирования - возможность работать на ограниченной
памяти и никогда не выходить за административно устновленные пределы. Такое же ограничение устанавливается на кеши СУБД. Их может
быть несколько. М системы поддерживают кеширование различных данных - блоков файлов баз данных, байткода рутин, каталог глобалов, и
другие.
Общие принципы кеширования одинаковы для разных алгоритмов
кеширования. Если база данных читает блок, то помещает его в кеш
блоков. Если в кеше есть еще не занятое место, то оно занимается. Если

2.4. КЕШИРОВАНИЕ БЛОКОВ 199
свободных мест в кеше нет, то механизм кеширования должен решить,
какое место освободить, или вытеснить из кеша. Различные алгоритмы
кеширования, собственно говоря, и различаются по стратегии вытеснения из кеша. Существуют самые различные стратегии вытеснения, есть
общераспространенные и общеприменяемые, есть специализированные,
учитывающие специфику доступа к кешируемым элементам, и для отдельных проектов могут быть разработаны собственные стратегии.
Для кеширования блоков B-деревьев достаточно хорошо подходит алгоритм LRU. У него есть несколько модификаций (например, K-LRU, 2SLRU), но уже применение стандартного алгоритма LRU дает очень высокий коэффициент попадания в кеш большинства блоков дерева. Большинство современных М систем для кеширования блоков использует
именно стратегию LRU или его модификацию.
Название LRU означает Least Recently Used, или "наименее недавно используемый", или вытеснение элемента к которому наиболее давно
не было обращений. В этой стратегии для элементов кеша ведется очередность обращений к ним таким образом, что любой, к которому произошло обращение, помещается в начало очереди. Помещается конечно
не сам элемент, а его индикатор (указатель, номер). При этом последний элемент всегда оказывается наиболее долго неиспользуемым и при
необходимости вытеснения вытесняется именно этот элемент.
Стратегия LRU приводит к тому, что при двух обращениях к различным веткам глобала повторно используется некоторая часть начала
дерева, или его корневая часть. При достаточно большом кеше в нем могут оказаться большинство используемых блоков. Одновременно с тем,
при чтении второй ветки глобала может вообще не произойти ни одного
чтения с диска, если вторая ветка хранится в уже прочитанных блоках.
32-битные системы ограничены размером используемой памяти в 2
гигабайта рабочего пространства, в котором также должны разместиться и другие области памяти, кроме кеша глобалов. В случае же использования 64-битных систем СУБД может использовать огромные объемы
кеша, столько сколько может быть предоставлено аппаратно.
Администратору СУБД обычно рекомендуется использовать размер
кеша, достаточный для комфортной работы сервера, но не стоит полагать, что увеличением кеша всегда можно добиться роста производительности. При увеличении кеша до какого-то предела действительно
можно увеличить общую производительность доступа к глобалам, но
нужно понимать, что другим программам работающим на сервере также
может понадобиться память и операционная система может вытеснить
часть кеша СУБД в файл подкачки, что обесценит кеширование.
Минусом кеширования является обратная сторона его плюса - если

200 ГЛАВА 2. ГЛОБАЛЫ
блоки отыскиваются в кеше, то производительность сервера стабильна
и относительно предсказуема, чего и добиваются применением кеширования. С другой стороны, если один или несколько процессов начнут
выполнять совершенно нехарактерные для выбранной стратегии кеширования глобалов операции с блоками, то кеш вымывается или обесценивается. Или, другими словами, из кеша вытесняются блоки, ценные
для большинства других процессов.
К таким характерным операциям выхода за пределы стратегии могут быть отнесены работа с большими данными в условиях недостаточно большого кеша, бекап, экспорт. При активной работе таких отдельных процессов кеш, несмотря на относительную стабильность алгоритма
LRU, все же может обесцениться так, что это станет заметно для других
процессов. Это проявляется в том, что общая производительность сервера при обычном выполнении большинства процессов начинает непредсказуемо падать и восстанавливаться. Для решения этой проблемы администраторы систем либо выполняют такие нестандартные операции,
обесценивающие кеш, в определенное регламентное время, либо добавляют объем кеша, согласуясь с аппаратными возможностями.
Что интересно, система Cach´e в отношении стратегии кеширования
допускает применение особого режима для процесса, так называемый
пакетный режим. При переводе процесса в такой режим ему ограничивается использование кеша таким образом, чтобы он вытеснял не все
блоки, а только в определенных пределах, не затрагивая б´ольшую часть
общего кеша. Такое решение весьма перспективно, и позволяет предоставить процессам общего назначения общую стратегию, а процессам
нестандартного выполнения отдельную стратегию.
2.5 Структуры
Системы MUMPS на физическом уровне оперируют только парами вида
ключ-значение. Сам ключ может быть составным, и состоять из последовательности значений индексов. Значение, записанное по этому ключу,
рассматривается как последовательность байт. В зависимости от реализации М системы количество байт в значении, количество байт в
значении каждого индекса и количество индексов может отличаться.
Вообще говоря, этим и исчерпывается определение хранения данных
в М системах. С одной стороны, определение простое, с другой стороны,
определение оставляет разработчикам огромный выбор.
Пары ключ-значение в М системах во-первых, не декларированы и,
во-вторых, не типизированы. По полному ключу мы можем записать в
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
