Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Базы данных. Лекции по курсу. В 4 частях. Ч.1. Учебное пособие
.pdf
влияние этого запроса на выполнение других параллельно выполняемых запросов и работоспособность системы в целом (число
устанавливаемых блокировок, применяемые методы управления транзакциями, …).
Остановимся на основных правилах, которых следует придерживаться при написании SQL-запросов.
1. Грамотно проектируйте базы данных.
От чего зависит скорость выполнения SQL-запроса? Она зависит от
многих факторов:
размеров таблиц (не зависит от разработчика);
структуры таблиц;
степени нормализации таблиц;
типов данных ключей (числовые – нечисловые).
Если размеры таблиц от разработчика SQL-запросов не зависят, то
структуры таблиц, степень их нормализации, типы данных ключей –
это те характеристики, которыми может управлять разработчик.
2. Хорошо знайте свои данные и бизнес-приложение. Отсюда сле-
дуют рекомендации:
знайте источники и объемы получения данных;
тестируйте свои запросы на реалистических данных;
вы должны иметь полное понимание используемой модели дан-
ных (равно как и связей между разными бизнес-объектами) до написания требуемых операторов SQL.
3. Для фильтрации записей используйте конструкцию
where, а не
having.
При использовании конструкции
having вместе с group by на ин-
дексированных столбцах индекс не используется. Если для таблицы
EMP существует индекс на столбце DEPTID, при выполнении следу-
ющего запроса этот индекс использоваться не будет:
SELECT DEPTID, SUM(SALARY)
FROM EMP
GROUP BY DEPTID
HAVING DEPTID
= 100
Однако этот запрос можно переписать так, чтобы индекс применялся:
31

SELECT DEPTID, SUM(SALARY)
FROM EMP
WHERE DEPTID
= 100
GROUP BY DEPTID
4. Минимизируйте число просмотров таблиц.
Предположим, что таблица
именами
NAME, STATUS, PARENT_INCOME и SELF_INCOME. Значе-
STUDENT содержит четыре столбца с
ние «статус» равно нулю для студентов, обучающихся по бюджету,
и единице – для контрактников. Ниже приведен запрос с двумя просмотрами таблицы
STUDENT:
SELECT NAME, PARENT_INCOME
FROM STUDENT WHERE STATUS =
1
UNION
SELECT NAME, SELF_INCOME
FROM STUDENT WHERE STATUS =
0
Тот же самый результат будет получен запросом с одним просмотром таблицы:
SELECT NAME, PARENT_INCOME * STATUS +
SELF_INCOME *
(1 - STATUS) FROM STUDENT
5. Где возможно, избегайте использования неявных соединений.
Ставится задача найти всех сотрудников некоторой организации
SELECT LNAME, FNAME
FROM PERSONS, COMPANIES
WHERE PERSONS.COMPANY=COMPANIES.COMPANY_ID
AND COMPANY.NAME=”SONY”
Предположим, что в таблице PERSONS содержится N (1000) строк,
в таблице
SQL-запрос требует проверки
COMPANIES содержится M (100) строк. Написанный выше
N*M строк (1 000 000).
Более оптимальный по времени поиска SQL-запрос можно записать
следующим образом:
SELECT LNAME, FNAME
32

FROM PERSONS
WHERE PERSONS.COMPANY IN
(SELECT COMPANY_ID
FROM COMPANIES
WHERE COMPANY.NAME=”SONY”
)
Этот SQL-запрос потребует проверки всего N + M строк (1100).
6. Если это неизбежно, соединяйте таблицы в правильном порядке.
Порядок соединения таблиц в запросах с соединениями имеет критическое значение. Всегда следует выполнять сначала максимально ограничивающий поиск, чтобы отфильтровать как можно большее число
строк на ранних фазах выполнения запроса с соединениями. Тогда на
следующих фазах соединения оптимизатору придется иметь
дело с
меньшим числом строк, что повысит эффективность. Следует убедиться,
что главная таблица (просматриваемая во внешнем цикле соединения на
основе вложенных циклов) содержит наименьшее число строк.
7. Поскольку не всегда можно заменить запрос с соединением на
более простой, как показано в п. 6, нередко выгоднее (особенно на
этапе освоения языка SQL) разбить сложный
запрос на несколько простых с использованием, возможно, временных таблиц. Однако не стоит
впадать и в другую крайность, используя временные таблицы к месту и
не к месту.
8. Старайтесь избегать коррелированных запросов. В большинстве
случаев коррелированный запрос можно заменить на некоррелированный.
Общие рекомендации к порядку предпочтения конструкций языка
SQL:
некоррелированные запросы;
соединения;
коррелированные запросы.
Однако эти рекомендации довольно условны, и многое зависит от
того, как написан SQL-запрос.
Рассмотрим пример. Требуется получить все пары «номер детали»–
«номер изделия» такие, что для изделий детали полностью поставляет
поставщик 'S2'. В табл. 1 сравнивается время выполнения SQL-запросов при количестве записей в таблице SPJ ~25000.
33

Таблица 1
Тип запроса Запрос
Коррелированный
подзапрос
Соединение
источников
select distinct b.n_izd, b.n_det from spj b
select distinct b.n_izd, b.n_det from spj b
Некоррелированный
подзапрос
select distinct b.n_izd, b.n_det from spj b
where not exists (select *
from spj a where a.n_izd = b.n_izd and
a.n_post<>'S2' )
select distinct b.n_izd, b.n_det from spj b
join (select a.n_izd from spj a
group by a.n_izd
having sum(case when (a.n_post<>'S2')
then 1 else 0 end) = 0) a
on a.n_izd = b.n_izd
join (select n_izd from spj
except
select n_izd from spj where n_post<>'S2') a
on a.n_izd = b.n_izd
left join (select distinct n_izd
from spj where n_post<>'S2') a
on a.n_izd = b.n_izd
where a.n_izd is null
select distinct b.n_izd, b.n_det from spj b
where not b.n_izd in
(select distinct a.n_izd from spj a where
a.n_post<>'S2')
Время выполне-
ния запроса
32.338
26.111
41.904
32.819
30.617
9. Если возможно, старайтесь строить более простые запросы. Оптимизатор может не справиться со слишком сложными операторами
SQL, иногда написание нескольких более простых операторов позволяет добиться лучшей эффективности, чем задание одного сложного
34

оператора. Основанный на оценках оптимизатор СУБД не является
абсолютно устойчивым.
Следует признать, что при разработке SQL-запросов не всегда
можно учесть в полном объеме дальнейшее расширение базы данных и
часто приходится оптимизировать SQL-запросы уже на этапе сопровождения информационной системы. Этот процесс называется рефакторингом.
Все это общие рекомендации. Более точные оценки
эффективности
вашего SQL-запроса можно получить, выполнив анализ плана выполнения запроса.
2.2. ОПТИМИЗАЦИЯ ЗАПРОСА SQL
Будем предполагать, что у нас есть две таблицы: таблица publishers
с данными об издательствах и таблица
books с данными об изданных
книгах.
create table publishers
(id serial,
name text not null,
address text,
country text,
owners text
);
create table books
(id serial,
title text not null,
authors text,
pages integer,
price numeric
(12, 2),
description text,
publisher_id integer,
genre text,
publication_date date
);
35

Будем считать, что в таблице publishers содержится 10 000 строк,
размер одной строки составляет ~ 95 байт и общий размер таблицы
составляет 1.5 Мбайт. В таблице
books содержится 200 000 строк, раз-
мер одной строки составляет ~ 1500 байт и общий размер таблицы
составляет 313 Мбайт.
Запрос «Получить список стран, издательства которых выпускали
книги в заданном жанре, указав количество выпущенных книг, отсортировав список по убыванию количества выпущенных книг» можно
записать, например, в следующем виде:
select p.country, count (*) books_count
from books b, publishers p
where b.publisher_id = p.id and b.genre = 'Жанр
45'
group by p.country
order by
2 desc
На рис. 1 показан результат выполнения SQL-запроса при использовании программы PgAdmin. Здесь же представлен экран с текстом
SQL-запроса, а также экран с результатами выполнения SQL-запроса.
Этапы выполнения запроса, запущенного в PgAdmin, показаны на
рис. 2. Клиент (PgAdmin) обращается к серверу баз данных, устанавливает соединение и посылает на сервер SQL-запрос. Сервер баз данных
считывает SQL-
запрос, обрабатывает его, анализирует возможные планы выполнения запроса и выбирает план (алгоритм), который будет
выполняться. После этого сервер баз данных выполняет выбранный
алгоритм, выбирая данные, определенные SQL-запросом. При выборке
данных сервер использует курсор, связанный с SQL-запросом.
Выбранные данные посылаются клиенту, и программа PgAdmin
выводит их на экран. После получения всех данных
программа PgAd-
min закрывает соединение с сервером.
Как можно видеть из рис. 1, программа PgAdmin получила и выве-
ла на экран 10 строк, и все это заняло 583 мс.
36

Рис. 1. Результат выполнения SQL-запроса в PgAdmin
37

Рис. 2. Этапы выполнения SQL-запроса в PgAdmin
38

На рис. 3 показан результат выполнения того же SQL-запроса при
использовании программы phpPgAdmin. Здесь представлены также
результаты выполнения SQL-запроса и характеристики выполнения
запроса.
Рис. 3. Результат выполнения SQL-запроса в phpPgAdmin
Этапы выполнения запроса, запущенного в phpPgAdmin, показаны
на рис. 4. В этом случае мы имеем трехзвенную архитектуру клиентсервер: клиент phpPgAdmin и два сервера. Клиент (phpPgAdmin) обращается к Web-серверу, тот, используя метод POST, устанавливает соединение с сервером баз данных и посылает SQL-запрос. Сервер баз
данных считывает SQL-запрос, обрабатывает его аналогично тому, как
было описано
выше, анализирует возможные планы выполнения за-
проса и выбирает тот, который будет выполняться. После этого сервер
39

баз данных выполняет SQL-запрос, выбирая данные, определенные
запросом.
Выбранные данные посылаются Web-серверу, и Web-сервер возвращает полученные данные клиенту phpPgAdmin, который выводит
их на экран. Как можно заметить, все это потребовало существенно
большего времени ~3061 мс.
Рис. 4. Этапы выполнения SQL-запроса в phpPgAdmin
План выполнения запроса – последовательность операций в терминах языка реляционной алгебры, необходимых для получения результата SQL-запроса в реляционной СУБД.
40
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
