- •Глава 1. Базы данных и системы управления 9
- •Глава 2. Организация доступа к данным 45
- •Глава 3. Реляционная алгебра 60
- •Глава 4. Основы sql 67
- •Глава 5. Проектирование реляционных баз данных 89
- •Глава 6. Взаимодействие sql с приложениями 116
- •Глава 7. Некоторые проблемы администрирования баз данных 154
- •Базы данных и системы управления
- •Файловые системы
- •Концепция баз данных
- •Основные функции субд
- •Непосредственное управление данными во внешней памяти
- •Управление буферами оперативной памяти
- •Управление транзакциями
- •Журнализация
- •Поддержка языков баз данных
- •Трехуровневая модель архитектуры систем баз данных
- •Модели данных
- •Характеристика связей
- •Компьютерно-ориентированные модели данных
- •Реляционный подход
- •Ключи и целостность реляционных данных
- •Моделирование концептуальной схемы базы данных
- •Организация доступа к данным
- •Страницы и файлы
- •Индексирование
- •Структуры типа б-дерева
- •Хеширование
- •Методы сжатия
- •Метод дифференциального сжатия
- •Иерархические методы сжатия
- •Кодирование по методу Хаффмена
- •Реляционная алгебра
- •Традиционные реляционные операции
- •Специальные реляционные операции
- •Дополнительные реляционные операции
- •Примеры использования реляционной алгебры для выражения словесных запросов в виде формул
- •Основы sql
- •Типы данных
- •Строковые типы данных
- •Битовые типы данных
- •Точные числовые типы данных
- •Вещественные числовые типы данных
- •Календарные типы данных
- •Значения null
- •Создание и обслуживание таблиц
- •Запрос на выборку
- •Статистические функции
- •Создание соединений
- •Вложенные запросы
- •Запрос на объединение
- •Запросы, выполняющие реляционные операции вычитания, пересечения и деления
- •Запросы на изменение
- •Перекрестные запросы
- •Проектирование реляционных баз данных
- •Нормализация отношений
- •Функциональные зависимости
- •Н ормальные формы, обоснованные функциональными зависимостями
- •Нормальная форма Бойса–Кодда
- •Нормальные формы, обоснованные более сложными зависимостями
- •Процедура нормализации и проектирования
- •Пример проектирования базы данных
- •Назначение и предметная область
- •Проектирование базы данных
- •Взаимодействие sql с приложениями
- •Встраивание sql-операторов в программный код
- •Тип курсора
- •Триггеры
- •Хранимые процедуры
- •Стандартные интерфейсы для доступа к данным
- •Информационное окружение веб-сервера
- •Стандарт odbc
- •Уровни соответствия
- •Уровень соответствия odbc
- •Задание имени источника данных odbc
- •Расширяемый язык разметки xml
- •Xml как язык разметки
- •Материализация хмl-документов с помощью xslt
- •Создание хмl-документов на основе информации из базы данных
- •Некоторые проблемы администрирования баз данных
- •Оптимизация запросов
- •Параллельная обработка данных
- •Потеря обновления
- •Зависимость от незафиксированных обновлений
- •Несогласованный анализ
- •Блокировки транзакций
- •Согласованность и уровень изоляции транзакций
- •Распределенные системы баз данных
- •Фрагментация
- •Репликация
- •Распространение обновлений
- •Управление каталогом
- •Распределенная обработка запросов
- •Типы распределенных систем баз данных
- •Нераспределенные мультибазовые субд
- •Клиент-серверные системы
- •Системы с общими ресурсами
- •Технические аспекты администрирования базы данных
- •Восстановление базы данных
- •Безопасность баз данных
- •Шифрование данных
- •Производительность баз данных
- •Администрирование данных
- •Литература
Некоторые проблемы администрирования баз данных
Оптимизация запросов
Целью оптимизации является организация эффективной обработки запроса к базе данных. Как правило, существует множество способов отбора требуемых данных и оптимизация запроса предназначена для выбора наилучшего из них, т.е. такого способа, который потребует минимальное количество операций ввода-вывода. В реляционных системах, как правило, организована автоматическая оптимизация – пользователь может не задумываться над способом выражения своих запросов, т.е. над тем, как сформулировать запрос, чтобы система выполнила его с максимально возможной производительностью. Более того, существует реальная возможность, что оптимизатор сформулирует запрос лучше, чем пользователь. Во-первых, автоматический оптимизатор располагает некоторыми статистическими данными о состоянии отношений, благодаря которым он способен более точно оценивать эффективность любой стратегии реализации конкретного запроса. Во-вторых, если с течением времени статистика базы данных значительно изменится (например, база данных будет физически реорганизована), то для реализации запроса может потребоваться совсем иная стратегия, чем до реорганизации; другими словами, может понадобиться повторная оптимизация, или реоптимизация. В реляционных системах процесс реоптимизации – это просто повторная обработка исходного запроса системным оптимизатором. В-третьих, оптимизатор – это программа, поэтому он более "настойчив" по сравнению с человеком. Оптимизатор вполне способен рассматривать буквально сотни различных стратегий реализации данного запроса, в то время как программист вряд ли изучает более трех-четырех стратегий.
Рассмотрим в качестве примера следующий запрос: найти имена поставщиков, поставляющих тестеры. Выражение реляционной алгебры и команда SQL могут иметь следующий вид:
(((Поставки JOIN (Детали WHERE Имя_Д=’Тестер’))JOIN Поставщики))[Имя_П].
SELECT Поставщики.Имя_П FROM Поставщики, Детали, Поставки WHERE Поставщики.ПN=Поставки.ПN AND Поставки.ДN=Детали.ДN AND Детали.Имя_Д=’Тестер’;
Предположим, что существует порядка 100 строк поставщиков, 50 000 строк поставок и 200 строк деталей. Примерно 1% поставок приходится на деталь «Тестер». Будем полагать, что за одно считывание в оперативную память передается одна запись.
Для обслуживания данного запроса потребуется выполнить последовательность алгебраических операций соединения (Join), выборки (Restrict) и проекции (Project). Большое значение имеет порядок выполнения этих операций. Предположим, что операции будут выполнятьcя в следующем порядке:
Выполнить соединение ПОСТАВЩИКИ и ПОСТАВКИ по атрибуту ПN. В худшем случае (при отсутствии индексов и кластеров) для этого потребуется считать 100 записей о поставщиках и для каждой из них считать все 50 000 записей поставок, чтобы создать возможные соединения, т.е. потребуется произвести 100×50 000 (5 миллионов) считываний. В результате будет получено временное отношение Temp1, состоящее из 50 000 строк поставок, соединенных с соответствующими строками поставщиков.
Выполнить соединение Temp1 с ДЕТАЛИ по атрибуту ДN. Для этого потребуется считать 50 000 строк отношения Temp1 и каждую из них сравнить с 200 строками ДЕТАЛИ. Потребуется произвести 50 000×200 (10 миллионов) считываний.
Результатом этого соединения будет временное отношение Temp2, состоящее из всех данных о поставках и поставщиках отношения Temp1, к которым добавлены данные о деталях. Отношение по-прежнему будет состоять из 50 000 строк, но сами строки будут очень длинными, так как они теперь содержат атрибуты всех трех отношений.
Произвести выборку всех строк отношения Temp2, в которых название детали имеет значение «Тестер». Для этого требуется считать 50000 строк отношения Temp2 и отобрать только те строки, атрибут ДЕТАЛИ которых имеет значение «Тестер». В результате получится временное отношение Temp3, состоящее из 500 строк.
Выполнить проекцию Temp3 по Имя_П, чтобы получить окончательный результат. Для этого требуется считать 500 строк отношения Temp3, выполнить проекцию и возвратить результат.
Итак, суммарное количество считываний при такой последовательности выполнения запроса составит 5 000 000 + 10 000 000 + 50 000 + 500, т.е. 15 050 500 считываний.
Такой же результат можно получить, выполнив первые три шага в обратном порядке:
Выполнить выборку строк отношения ДЕТАЛИ, в которых Имя_Д = ’Тестер’. Для этого требуется считать 200 строк отношения ДЕТАЛИ, в результате получится временное отношение T1, состоящее из 1 строки.
Выполнить соединение T1 с ПОСТАВКИ. Для одной строки T1 считывается 50 000 строк поставок, в результате соединения получается временное отношение T2, состоящее из 500 строк.
Выполнить соединение T2 с ПОСТАВЩИКИ. Для каждой из 500 строк отношения T2 считывается 100 строк клиентов, т.е. производится 50 000 считываний и в результате получается временное отношение T3 из 500 строк, содержащее всю информацию о поставщиках, поставках и детали.
Выполнить проекцию T3 по атрибуту Имя_П и получить требуемый результат. Для этого считывается 500 строк отношения T3 и выполняется проекция.
При выполнении запроса в такой последовательности потребуется 200 + 50 000 + 50 000 +500 = 100 700 считываний, что в 150 раз меньше, чем при использовании первой стратегии отбора. Приведенный пример показывает, что даже простая база данных может функционировать в 150 раз быстрее, используя правильную стратегию обработки запроса.
В нашем примере экономия была достигнута за счет изменения порядка выполнения операций. Используя специальные правила эквивалентности, реляционная система может преобразовывать заданное множество операций в эквивалентное ему множество операций. Каждое правило эквивалентности показывает, что результат, полученный с помощью заданной последовательности операций, можно получить с помощью другой последовательности операций. Такие последовательности операций называются эквивалентными (обозначение – ).
Рассмотрим три отношения А, В и С с атрибутами – А.Х, A.Y, A.Z, B.I, B.J, В.Н, C.L, С.М и C.N соответственно. Для последовательностей реляционных операций существуют следующие правила эквивалентности.
Join (A,B) where A.X=B.IR1
Restrict (R1) on R1.Y
Restrict A on A.YR1
Join (R1,B) where R1.X = B.I
То есть, соединение (Join) двух отношений, за которым следует выборка (Restrict) в результирующем отношении, дает тот же результат, что и первоначальное выполнение выборки в соответствующем отношении (A) и последующее соединение полученного отношения (R1) со вторым отношением (B).
Join (A,B) where A.X=B.IR1
Project (R1) on R1.Y
Project (A) on A.Y, A.XR1
Project (B) on B.IR2
Join (R1.R2) where R1.X = R2.lR3
Project (R3) on R3.Y
Это правило гласит, что если за соединением следует проекция, то того же результата можно достичь, если сначала выполнить проекцию необходимых атрибутов заданных отношений вместе с ключами, по которым производится соединение, а затем уже соединить полученные проекции отношений. Промежуточный результат необходимо вновь подвергнуть проекции, чтобы удалить ключ соединения. Последний шаг не нужен, если ключ соединения должен входить в окончательный ответ.
Restrict (A) on A.XR1
Restrict (R1) on R1.Y
Restrict (A) on A.X and A.Y
То есть, последовательные выборки (Restrict), использующие значения атрибутов одного отношения, можно сгруппировать в одну выборку.
Restrict (A) on A.XR1
Project (R1) on R1.Y
Project (A) on A.X, A.YR1
Restrict (R1) on A.XR2
Project (R2) on R2.Y
Согласно этому правилу, если в отношении за выборкой (Restrict) следует проекция (Project), того же результата можно достичь с помощью проекции, за которой следует выборка. Если атрибут, по которому выполнялась выборка (А.Х), не является частью конечного результата, его нужно включить в первую проекцию, а затем исключить с помощью последней операции проекции.
Join (A,B) where A.X=B.I
Join (B,A) where B.I=A.X
То есть, при соединении двух отношений не имеет значения, в каком порядке они указаны в операторе соединения.
Join (A,B) where A.X=B.IR1
Join (R1 ,С) where R1 .I=C.L
Join (B,C) where B.I=C.LR1
Join (R1,A) where R1.I=A.X
Это правило гласит, что при соединении трех и более отношений соединения можно выполнять в произвольной последовательности.
Правила эквивалентности дают множество путей выполнения заданного запроса. Желательно, чтобы соединения выполнялись как можно позже, так как для выполнения соединений требуется много повторных считываний одного и того же отношения, чтобы найти совпадающие значения атрибутов. Чем меньше соединяемые отношения, тем быстрее удастся выполнить соединение. В рассмотренном примере второй способ эффективнее первого, потому что выборка в отношении ДЕТАЛИ была выполнена раньше, чем выполнялись соединения. Поскольку только одна строка из 200 удовлетворяла условию, удалось сократить соединение ДЕТАЛИ с ПОСТАВКАМИ на 99,5%. Также предпочтительней выполнять проекции перед соединением, поскольку это позволяет уменьшить ширину промежуточных отношений и для их хранения может потребоваться меньше страниц памяти. В нашем примере, вероятно, можно добиться дальнейшего сокращения числа операций ввода-вывода, если выполнить в самом начале проекции по ПОСТАВЩИКИ.ПN, ПОСТАВЩИКИ.Имя_П, ПОСТАВКИ.ПN, ПОСТАВКИ.ДN, ДЕТАЛИ.ДN, ДЕТАЛИ.Имя_Д. Благодаря этому промежуточные отношения станут намного меньше, возможно, они даже полностью разместятся в оперативной памяти, что позволит вовсе избежать операций ввода-вывода.
Чтобы по-настоящему оптимизировать базу данных, нужно обладать статистической информацией о ней. Предположим, имеются три отношения А, В и С, причем А содержит 1000 строк, В – 1000, а С – 10. Каждую из 1000 строк отношения А нужно проверить на возможность соединения с одной из 1000 строк отношения В, а 10 строк отношения С нужно сравнить с 1000 строками отношения В. Используя правило эквивалентности 6, можно выполнить соединение этих трех отношений двумя способами.
Join (A,B) R1 (10001000 считываний, предположим, что получится 1000 строк) Join (R1,C) Result (100010 считываний)
Join (В,С) R1 (100010 считываний, может получиться 10 строк) Join (R1,A) Result (101000 считываний)
В первом случае понадобится 1 010 000 считываний, во втором – 20 000; экономия составит до 98%. Таким образом, соединения нужно выполнять в таком порядке, который позволит как можно быстрее уменьшить количество строк. Система базы данных сможет это сделать только в том случае, если она обладает статистической информацией о размерах файлов, строк и доле совпадений внешних ключей различных отношений. Тогда можно приблизительно оценить вероятное количество необходимых операций ввода-вывода для различных последовательностей выполнения операций.
При оптимизации запросов СУБД должна также принимать во внимание существование таких объектов базы данных, как индексы, хеш-индексы и кластеры.
Когда два отношения кластеризованы, они уже соединены физически. Любая последовательность операций, в которой участвуют эти отношения, должна начинаться с их соединения (Join), так как оно фактически уже произошло.
На соединения по внешним ключам большое влияние оказывают индексы. Рассмотрим соединение двух отношений А и В, где X – первичный ключ А и внешний ключ В.
Join (A,B) where A.X = В.Х
Если в отношении В существует индекс по атрибуту X, то скорость соединения существенно возрастает: для каждой строки А производится поиск в индексе В.Х и соединяются извлеченные строки. Если индекс В.Х отсутствует, для нахождения в отношении В множества строк, соответствующих каждой определенной строке А, придется полностью его сканировать. Правило эквивалентности 5 гласит, что порядок вхождения отношений в оператор Join не влияет на его результат. Если в отношении А существует индекс по первичному ключу, а в отношении В индекса по внешнему ключу нет, то, вероятно, лучше взять отношение В и для каждой его строки находить соответствующую строку отношения А с помощью индекса А.Х.
Для сложных запросов может существовать много альтернативных последовательностей эквивалентных операций. Вместо того, чтобы генерировать много разнообразных последовательностей операций и оценивать их "стоимость" в плане количества вводов-выводов, в большинстве систем предпочитают просто руководствоваться принципом "выполнять соединения как можно позже", не пытаясь оценить стоимость различных последовательностей соединений. Такой подход не обязательно приводит к максимальной производительности при выполнении отдельных запросов, но зато избавляет систему от дополнительной работы, необходимой для всесторонней оценки запросов с использованием статистической информации. Затраты на эту дополнительную работу могут оказаться гораздо выше, чем выигрыш, полученный от оптимизации запроса.