Ю. А. Григорьев, Г. И. Ревунков - Банки данных
.pdf12. Логическое проектирование систем распределенной обработки данных
Сначала исходный запрос S\ преобразуется в формулу реляционной алгебры. Используя законы этой алгебры, СУБД выполняет оптимизацию этой формулы, а затем преобразует оптимизированную формулу в совокуп ность подзапросов {Si, S3 и S^).
Подзапросы S2 и Ss могут быть выполнены параллельно в следующих случаях.
1. Сервер СУБД функционирует на SMP-компьютере и задействовано средство параллельной обработки; подзапросы выполняются на разных процессорах.
2. Таблицы «Клиент» и «Счет» хранятся на разных компьютерах и в СУБД задействовано средство распределенной обработки; подзапросы вы полняются на разных узлах системы. Затем таблица /?2 (она имеет меньший объем данных) передается на узел, где получена таблица R], и там выполня ется соединение этих таблиц.
Оптимизация запросов повышает эффективность их выполнения. В данном случае это проявляется в следующем:
•подзапросы Si и ^з выполняются параллельно,
•соединяемые таблицы R\ и Ri имеют меньшую размерность (число строк и столбцов), чем исходные таблицы клиент и счет.
Подзапрос S4, выполняющий соединение больших промежуточных таблиц, также может быть декомпозирован (рис. 12.18). В этом случае под
запрос 5*5 будет соединять подтаблицы R^nR^c кодом клиента не более 500, а подзапрос 5*6 — подтаблицы RXHRIC КОДОМ клиента больше 500 и меньше 1000 (см. подзапрос S2).
Таким образом, подзапросы Ss и Se параллельно обрабатывают (на пример, на SMP-компьютере) как бы по половине (хотя на самом деле это может быть и не так) таблиц R\ и /?2-
Рекомендации по кодированию операторов SQL. Язык SQL отно сится к классу непроцедурных языков, т.е. программист указывает, что он хочет получить, но не определяет, как это сделать. Последовательность опе раций выполнения SQL-оператора определяет ядро СУБД. Поэтому с виду невинный SQL-оператор программы может вызвать перегрузку сервера СУБД.
Большинство рекомендаций по кодированию SQL-запросов связано с применением индексов. Поэтому прежде всего рассмотрим, как СУБД ис пользует индексы для поиска данных.
Пусть в БД хранится некоторая таблица Счет (рис. 12.19). Числа в этой таблице вымышленные.
Здесь Номер счета — это первичный ключ таблицы. Для такого ключа СУБД, как правило, автоматически строит индекс. Предположим, что в сис теме часто выполняются запросы SELECT с ограничениями, которые накладываются на столбцы Код клиента и Тип счета. В этом случае целесо образно построить сложный (составной индекс):
230
|
|
12.2. Разработка прилооюёний |
|
||||
54 |
|
|
|
|
|
|
|
|
SELECT |
адресклиента |
|
|
|
|
|
|
FROM |
Ri,R2 |
|
|
|
|
|
|
WHEM |
Rl.кoд_Icлиeнтa = R2.кoд_клиeнтa |
|
||||
|
ORDER BY |
адрес_клиента |
|
|
|
|
|
|
|
|
|
|
|
||
S5 |
|
|
|
se |
|
|
|
SELECT |
адресклиента |
|
SELECT |
адрес_клиента |
|
||
FROM |
R,, R2 |
|
|
FROM |
R,, R2 |
|
|
WHERE |
Ri .кодклиента = |
|
WHERE |
R|.кoд_клиeнтa = |
|
||
|
R2. кодклиента AND |
|
|
R2. код_клиента AND |
|||
|
Ri .код_клиента <=500 |
|
|
RI .код_клиента > 500 |
|||
Таблица Rз(aдpec_клиeнтa) |
|
|
Таблица R4(aдpec_клиeнтa) |
||||
57 |
|
|
|
|
|
|
|
|
SELECT |
адрес_клиента |
FROM |
R3 |
|
|
|
|
UNION |
|
|
|
|
|
|
|
SELECT |
адрес_клиента |
FROM |
R4 |
|
|
|
|
ORDER BY |
адрес_клиента |
|
|
|
|
|
|
|
Таблица R5(aдpec_клиeнтa) |
|
||||
|
|
|
^ |
Ha рабочую станцию |
|
||
|
Рис. 12.18. Декомпозиция подзапроса соединения |
|
|||||
|
Счет |
|
|
|
|
|
|
№ записи |
Номер счета |
Код клиента |
|
Тип счета |
Остаток |
||
1 |
1 |
|
3 |
|
|
1 |
150 |
2 |
4 |
|
1 |
|
|
2 |
300 |
3 |
3 |
|
1 |
|
|
4 |
99 |
4 |
2 |
|
3 |
|
|
1 |
44 |
5 |
7 |
|
2 |
|
|
1 |
55 |
6 |
6 |
|
6 |
|
|
2 |
33 |
7 |
5 |
|
4 |
|
|
1 |
139 |
8 |
8 |
|
5 |
|
|
2 |
100 |
9 |
9 |
|
1 |
|
|
1 |
3 |
Рис. 12.19. Пример первых девяти строк таблицы «Счет»
231
72. Логическое проектирование систем распределенной обработки данных
|
|
|
|
3-й уровень |
|
|
|
|
|
|
Код |
Тип |
№ |
|
|
|
|
клиента |
счета |
записи |
|
|
|
|
1 |
1 |
9 |
|
|
2-й уровень |
|
1 |
2 |
2 |
|
|
|
1 |
4 |
3 |
|
|
|
Код |
Тип |
|
|
|
1-й уровень |
|
клиента счета |
2 |
1 |
5 |
|
|
1 |
1 |
||||
(корень индекса) |
2 |
1 |
3 |
1 |
4 |
|
Код |
Тип |
4 |
1 |
3 |
1 |
1 |
клиента |
счета |
|
|
4 |
1 |
7 |
1 |
1 |
|
|
|||
|
|
|
|
5 |
2 |
8 |
|
|
|
|
6 |
2 |
6 |
Рис. 12.20. Структура составного индекса (код клиента, тип счета)
CREATE INDEX имя индекса ON счет (код клиента, тип счета)
СУБД ведет индекс автоматически в специальном индексном файле. Индекс представляет собой совокупность иерархически связанных блоков данных, имеющих специальную структуру (рис. 12.20). Минимальное число уровней иерархии в индексе равно 3. Каждому оператору CREATE INDEX соответствует свой индекс. Рассмотрим организацию индекса.
Блоки всех уровней, кроме последнего, имеют следующую структуру: в первых двух столбцах хранятся значения атрибутов «Код клиента» и «Тип счета», а третий столбец содержит ссылку на блок индекса более высокого уровня. В третьем столбце блока последнего уровня индекса хранятся номера записей таблицы Счет. Например, номер записи 9 означает, что в 9-й записи таблицы Счет хранятся код клиента и тип счета, равные 1 и 1. Строки каж дого блока индекса лексикографически упорядочены (упорядочены коды) по значениям атрибутов, т.е. сначала строки упорядочены по атрибуту «Код клиента», а затем — по атрибуту «Тип счета».
Рассмотрим, как выполняется поиск записей с помощью индекса. Пусть на сервер поступил запрос:
SELECT |
остаток |
FROM |
счет |
WHERE |
код клиента = 1 AND тип счета <= 4; |
СУБД формирует код 14 и начинает поиск с корня индекса. В корне ин декса выбирается первая строка, так как она единственная в блоке. Далее по
232
J2.2. Разработка прилоэюений
указателю считывается блок индекса 2-го уровня. В этом блоке выполняется поиск первой строки, значения атрибутов которой превышают код 14, и затем выполняется возврат на строку выше. В данном случае будет выбрана 1-я строка, так как во второй строке блока хранится код 21 > 14. Далее по указа телю считывается первый блок 3-го уровня и здесь выбираются строки, для которых выполняется неравенство 10 =^ (Код клиента. Тип счета) ^ 14. Этому требованию удовлетворяют все три строки считанного блока 3-го уровня. Таким образом, условию поиска удовлетворяют записи с номерами 9, 2, 3. Затем эти записи считываются из таблицы Счет и их поля Остаток помещают ся в результирующую таблицу (см. рис. 12.19).
Использование индексов для больших таблиц очевидно. Предполо жим, что в таблице Счет хранится 1 млн записей. Если бы таблица Счет не была индексирована по атрибутам Код клиента и Тип счета, то для провер ки условия серверу СУБД необходимо было бы считать 1 млн записей. В данном же случае СУБД просмотрела только три блока индекса и считала три записи.
Следует отметить, что использование индексов приводит к увеличе нию времени обновления таблицы. Предположим, выполняется оператор включения новой записи в таблицу Счет:
INSERT INTO счет (номер счета, код клиента, тип счета, остаток) VALUES (15, 1,3,55);
Пусть номер первой пустой записи в таблице Счет равен 10. Сделаем упрощающее предположение, что максимальное число строк в каждом бло ке индекса равно 3 (см. рис. 12.20). СУБД не только включает новую запись в таблицу, но и обновляет индекс.
Сначала ядро СУБД формирует код 13 (код клиента и тип счета в но вой записи). В индексе выполняется поиск блока 3-го уровня, где хранится строка с наименьшим кодом, удовлетворяющим условию (код клиента, тип счета) > 13. Это третья строка первого блока 3-го уровня (14 > 13). Перед этой строкой и должна быть включена новая строка (1, 3, 10). Но этот блок заполнен. В этом случае СУБД выполняет следующие действия: за прашивается память под еще один блок 3-го уровня, верхняя половина строк первого блока (3-го уровня) остается в нем, а нижняя строка вместе с новой переписывается в новый блок. Так как появился новый блок 3-го уровня, то в блок 2-го уровня необходимо включить новую строку с указа телем на этот новый блок. Но блок 2-го уровня заполнен. Поэтому созда ется новый блок 2-го уровня и в блок 1-го уровня помещается новая стро ка с указателем на этот новый блок. В результате формируется индекс, показанный на рис. 12.21.
Так как при включении новой строки в заполненный блок выполняет ся его «расщепление» (т.е. из одного блока получаются два), то индекс на зывается Б-деревом {В означает Bindery).
233
12. Логическое проектирование систем распределенной обработки данных
|
|
|
|
|
3-Й уровень |
|
|
|
|
|
|
|
|
Код |
Тип |
№ |
|
|
|
|
|
|
клиента |
счета |
записи |
|
|
|
|
|
|
1 |
1 |
9 |
|
|
|
|
|
|
1 |
2 |
2 |
|
1-й уровень |
|
2-й уровень |
|
|
|
|
|
|
|
Код |
Тип |
|
|
|
Жw |
|
|
(корень индекса) |
клиента |
счета |
|
"'Т |
•Y |
|
||
Код |
Тип |
|
1 |
|
|
|
|
|
клиента счета |
|
|
|
|
|
|
|
|
1 |
1 |
|
|
|
2 |
1 |
5 |
j |
"V^^'T^'ff , |
't |
2 |
1 |
" / |
3 |
1 |
4 |
|
|
|
|
|
|
||||
|
|
3 |
1 |
1 |
|
|||
|
|
4 |
1 |
|
|
|||
|
|
|
|
|
|
|
||
|
|
|
|
|
4 |
1 |
7 |
|
|
|
|
|
|
5 |
2 |
8 |
|
|
|
|
|
|
6 |
2 |
6 |
|
Рис. 12.21. Обновленный индекс
Ниже приведены рекомендации по кодированию операторов SELECT. 1. Если таблица имеет много записей, то важно следить, чтобы в усло вии поиска хотя бы один атрибут (столбец) был индексирован. Например,
запрос
SELECT |
код клиента |
FROM |
счет |
WHERE |
остаток < 100 AND остаток > 80; |
приведет к перебору всех записей таблицы Счет.
2. По возможности не используйте операцию NOT (не) для индекси рованных атрибутов. Например, запрос
SELECT |
код клиента, остаток |
FROM |
счет |
WHERE |
NOT(HOMep счета = 3); |
будет выполняться долго, так как в этом случае СУБД будет работать по следующему алгоритму (здесь предполагается, что атрибут Номер счета индексирован):
• считать из индекса все номера записей (список U1); при большой таблице этих номеров может быть много;
234
12.2.Разработка пргтоэюений
•считать из индекса номера записей, удовлетворяющих условию «но мер счета = 3» (список U2);
•найти разность списков (U3 = U1 - U2), т.е. исключить из U1 номе ра, содержащиеся в списке U2;
•читать записи с номерами из списка U3.
При выполнении запроса
SELECT |
код клиента, остаток |
FROM |
счет |
WHERE |
код клиента < 10 AND |
|
тип счета =1 AND |
|
NOT(HOMep счета = 3); |
СУБД организует поиск записей по сложному индексу (код клиента, тип счета) и уже после их чтения отфильтрует записи с условием «номер счета = 3».
3. Если создан сложный индекс (см. выше), то не используйте второй атрибут отдельно от первого в условии поиска, например:
SELECT |
код клиента, остаток |
FROM |
счет |
WHERE |
тип счета = 3; |
Отсюда можно было ожидать, что для выполнения запроса СУБД использу ет индекс. Однако, так как запрос не ссылается на первую часть сложного индекса (код клиента), СУБД не будет использовать этот индекс, что приве дет к перебору всех записей таблицы Счет. Использование в условии поиска атрибутов Код клиента и Тип счета или одного атрибута Код клиента при ведет к поиску записей с помощью индекса.
Приведем еще несколько рекомендаций по созданию индекса.
1. Индексируемая таблица не должна быть слишком маленькой. Если СУБД может записать все строки таблицы всего в несколько блоков дан ных, то создавать индекс нет смысла. Объем ввода/вывода при обращении к строкам таблицы от этого не уменьшится.
2.Если для работы с таблицей очень часто используются операторы INSERT, UPDATE, DELETE, то большое число индексируемых атрибутов может привести к существенному увеличению времени выполнения этих операторов из-за значительных временных затрат на модификацию индексов.
3.Если часто выполняется поиск по какой-либо группе атрибутов, то рекомендуется построить сложный индекс для этих атрибутов.
4.Рекомендуется индексировать атрибуты, которые имеют сравни тельно большое число различных значений. В этом случае уменьшается число номеров записей на одно значение атрибута, что сокращает время выполнения операций с индексными списками.
По возможности следует не использовать соединения таблиц и вло женных подзапросов, при выполнении которых также применяется опера-
235
12. Логическое проектирование систем распределенной обработки данных
ция соединения. Известно, что эта операция является самой ресурсоемкой в реляционных СУБД. Чтобы уменьшить время поиска в больших связанных таблицах, эти таблицы объединяют в одну таблицу. Например, если табли цы Клиент и Счет объединить в одну таблицу Счет, то сложный запрос можно существенно упростить. При выполнении этого запроса соединять таблицы не требуется:
Счет (Номер счета, Тип счета. Код клиента. Адрес клиента. Остаток)
SELECT |
адрес клиента |
FROM |
счет |
WHERE |
код клиента < 1000 AND |
|
остаток < 100 AND |
ORDER BY |
тип счета =1 |
адрес клиента; |
Особенности разработки приложений для работы с базой данных в сети. При многопользовательском доступе к серверу СУБД приложение и СУБД должны обеспечивать
•блокировку обновляемых записей БД,
•ведение логических транзакций,
•обработку тупиковых ситуаций.
Блокировка обновляемых записей БД. Большинство СУБД автомати чески блокирует записи БД при их обновлении.
Пример. Пусть в БД хранится таблица Счет и две операционистки банка од новременно выполняют следующие операции: первая операционистка переводит со счета 100 на счет 1001 условную сумму 300, а вторая операционистка перечисляет со счета 1001 на счет 100 условную сумму 150.
Предположим, что разработана одна хранимая процедура на языке PL/SQL Oracle для выполнения указанных операций:
1 |
CREATE PROCEDURE проводка (номер 1 IN NUMBER, номер 2 IN |
|
|
NUMBER, сумма IN NUMBER) IS |
|
2 |
CURSOR курсор (ном_счета1 NUMBER, номсчета 2 NUMBER) IS |
|
|
SELECT |
номер, остаток |
|
FROM |
счет |
|
WHERE |
номер=ном_счета 1 OR номер=номсчета 2 |
|
FOR UPDATE OF остаток; |
|
3 |
запись Kypcop%ROWTYPE; |
|
4 |
BEGIN |
|
5 |
OPEN курсор (номер 1, номер 2); |
|
6 |
LOOP |
|
7 |
FETCH курсор INTO запись; |
|
8 |
EXIT WHEN курсор % NOTFOUND; |
|
|
12.2. Разработка прилоэюений |
9 |
IF запись.номер = номер 1 |
|
ТНЕКзапись.остаток:=запись.остаток-сумма; |
|
ELSE запись.остаток:=запись.остаток+сумма; |
|
END IF; |
10 |
UPDATE счет SET остаток=запись.остаток |
|
WHERE CURRENT OF курсор; |
11END LOOP;
12CLOSE курсор;
13COMMIT;
14END;
Обращение к этой процедуре может быть выполнено из программы, выпол няющейся на рабочей станции:
1) на рабочей станции первой операционистки — проводка (100, 1001,300);
2) на рабочей станции второй операционистки — проводка (1001, 100, 150);
Конечно, эту задачу можно решить и по-другому, но здесь демонстри руется использование курсора, связывающего непроцедурный язык SQL с процедурным языком (в данном случае — с PL/SQL).
Прежде чем рассказать, каким образом СУБД блокирует записи, по ясним некоторые операторы рассмотренной процедуры:
1 — CREATE PROCEDURE: создание хранимой процедуры с пара метрами «HOMcpi» (номер счета «откуда»), «номерг» (номер счета «куда»), «сумма» (сумма проводки).
2 — CURSOR: декларативный оператор, который определяет курсор с именем «курсор». Курсор имеет входные параметры («номсчета!» и «номсчетаг»), а также описывает оператор SELECT для обновления (FOR UPDATE OF).
3 — декларативный оператор, который строит в оперативной памяти переменную «запись». Структура записи совпадает со структурой списка атрибутов, описанных в операторе SELECT (номер, остаток).
4 — BEGIN: начало описания тела процедуры.
5 — OPEN: открыть курсор с параметрами, получаемыми процедурой при вызове. При открытии курсора оператор SELECT после подстановки параметров выполняется ядром СУБД. Результаты поиска возвращаются обратно процедуре в виде множества записей курсора.
6 — LOOP: начать цикл по записям курсора.
7 — FETCH: скопировать поля (номер и остаток) текущей записи кур сора в переменную «запись».
8 — EXIT: выйти из цикла, если все записи курсора обработаны.
237
12, Логическое проектирование систем распределенной обработки данных
9 — IF: обновить поле «остаток» переменной «запись».
10 — UPDATE: обновить текущую запись курсора.
11 — END LOOP: завершить тело цикла LOOP.
12 — CLOSE: закрыть курсор.
13 — COMMIT: завершить транзакцию и снять все блокировки.
14 — END: завершить описание тела процедуры.
На рис. 12.22 показано, как с помощью блокировок СУБД корректно выполняет обновления записей таблицы Счет при одновременном обраще нии рабочих станций к процедуре «проводка».
Первый процесс открывает курсор, и ядро СУБД выполняет описан ный в этом курсоре оператор SELECT. Все найденные записи автоматиче ски блокируются, так как при описании курсора было указано FOR UPDATE OF. При открытии курсора другим процессом выполняется поиск записей, но так как они блокированы, выполнение оператора SELECT от кладывается и второй процесс переходит в состояние ожидания. После вы полнения первым процессом оператора COMMIT обновленные записи ста новятся доступными другим клиентам (т.е. перемещаются из сегмента отка та в таблицу Счет), записи 100 и 1001 разблокируются и выполняется опе ратор SELECT, связанный со вторым процессом. При этом поиск ведется уже по обновленной таблице Счет.
Ведение логических транзакций (ЛТ). Логическая транзакция — это совокупность изменений в БД, которые должны быть все зафиксированы в БД либо ни одного. Все СУБД, поддерживающие распределенную обработ ку, обеспечивают ведение транзакций. Различные СУБД обеспечивают ра боту с ЛТ примерно по одной схеме. Рассмотрим реализацию этой схемы на примере Oracle,
В PL/SQL для работы с транзакциями используют два оператора:
• COMMIT — это многофункциональная команда; она использует ся, чтобы
а) завершить предыдущую транзакцию и начать новую, б) отменить все блокировки, установленные предыдущей транзакцией,
• ROLLBACK — позволяет программным путем отменить изменения, выполненные текущей транзакцией.
Рассмотрим схему ведения транзакций (рис. 12.23).
Если программа выполняет изменение записи (программа 1), то это изменение фиксируется на диске в сегменте отката. Здесь сохраняются за писи до и после изменений. СУБД ведет в сегменте отката одну транзакцию для каждого активного процесса. Если другой процесс выполняет при этом поиск той же записи, то она читается не из сегмента отката, а из базы дан ных, т.е. до завершения транзакции этот процесс «не видит» никаких изме нений, выполненных первым процессом.
По команде COMMIT СУБД выполняет следующие действия.
238
|
12.2. Разработка прилооюений |
|
|
|
||
Рабочая станция 1-й операционистки |
Рабочая станция 2-й операционистки |
|||||
|
(1-й процесс): |
|
(2-й процесс): |
|
||
проводка (100, 1001,300); |
проводка (1001, 100,300); |
|||||
Процедура |
Ядро СУБД |
Процедура |
|
Ядро СУБД |
||
OPEN |
Выполнение |
OPEN |
|
Ожидание |
||
|
SELECT и |
|
|
выполнения |
||
|
блокировка |
|
|
SELECT |
||
|
записей 100 и |
|
|
|
|
|
|
|
1001 |
|
|
|
|
|
Сегмент отката |
|
|
|
|
|
UPDATE |
100 |
200 |
|
|
|
|
|
1001 |
1200 |
|
|
|
|
|
|
Nf |
|
|
|
|
|
Счет |
|
|
|
|
|
COMMIT |
100 |
200 |
|
|
|
|
|
1001 |
1200 |
|
|
|
|
|
Записи |
|
|
Выполнение |
||
|
100 и 1001 |
|
|
SELECT и |
||
|
разблокируются |
|
|
блокировка |
||
|
|
|
|
|
записей 100 и |
|
|
|
|
|
|
1001 |
|
|
|
|
|
Сегмент отката |
||
|
|
|
UPDATE |
1 |
100 |
350 |
|
|
|
|
1 1001 |
1050 |
|
|
|
|
|
|
|
f |
|
|
|
|
Счет |
|
|
|
|
|
COMMIT |
1 |
100 |
350 |
|
|
|
|
|
1001 |
1050 |
Записи 100 и 1001 разблокируются
Рис. 12.22. Схема обработки двух запросов процедурой «проводка»
239
