Главная цель данной главы - научить АБД разбираться со стоящими перед ним проблемами настройки приложений. Для этого будет полезно документировать некоторые плохие приемы программирования SQL, с которыми нам приходилось встречаться и которые из раза в раз снижают производительность приложения. Это далеко не полный список. Некоторые из ловушек (там, где возможно) содержат образцы кодов, показывающих, что было и что стало. Ни. же дано несколько не самых лучших "примеров" написания операторов SQL, которым не нужно подражать. Постарайтесь следовать рекомендациям.
• Не применяйте индексы в тех случаях, когда фраза where предписывает оператору SQL посетить при использовании индексов больше блоков данных, чем при проведении сканирования полной таблицы. Этого можно добиться, добавив к индексированному столбцу безвредное выражение (например, + 0 для цифрового столбца или конкатенацию с пустой строкой (") для алфавитно-цифровых столбцов), либо путем включения в оператор SQL подсказки FULL (в случае стоимостного
оптимизатора).
Итак, рассмотрим примеры:
1. В таблице LINE_ITEMS насчитывается 1 млн строк.
2. Столбец ShippedDate индексирован и используется последующим
запросом.
запросом.
3. Распределение данных в столбце Shipped_Date таково, что строки
разбросаны по значительному числу блоков таблицы.
разбросаны по значительному числу блоков таблицы.
4. Избирательность индекса хорошая.
Что было: select *
from LINEITEMS where Shipped_Date between SYSDATE and (SYSDATE - 30);
В предыдущем запросе используется индекс по столбцу Shipped_Date, несмотря на то, что он не оптимален для этой цели. Следовательно, приведенный выше запрос нужно переписать, используя подсказку FULL (чтобы принудительно вызвать сканирование полной таблицы), а, может быть, учитывая размер таблицы LINE_ITEMS, даже подсказку PARALLEL (в этом случае необходимо предварительно установить параметры файла init.ora). Переписанный запрос может выглядеть так:
Что стало:
select /* + FULL(LINE_ITEMS) PARALLEL(LINE_ITEMS, 2) */ * from LINE_ITEMS
where Shipped_Date between SYSDATE and (SYSOATE - 30);
Запретите сканирование полной таблицы, когда фраза
where
предписывает оператору SQL обработать или вернуть лишь малую часть
таблицы, если только таблица не слишком сильно фрагментирована
-
(мало строк в нескольких блоках, но отметка максимального уровня Очевидным исключением из этого правила является случай очень маленькой таблицы (по отношению к общему размеру базы данных). Чтобы добиться цели, можно в явном виде включить подсказку INDEX или, создать подходящие индексы для таблицы. Будьте осторожны при создании дополнительных индексов, особенно для таблиц, которые по своей природе являются
поскольку индексы оказывают влияние на время
вставки, удаления
иобновления
строк таблицы. Рассмотрим следующие моменты:В таблице ORDERS 1 млн строк.
2. Столбец Ord_Id может содержать алфавитно-цифровые значения
(комбинацию чисел и алфавитных данных), которые записываются в
верхнем регистре. Но приложение допускает ввод значений Ord_Id как в верхнем, так и в нижнем регистрах.
3. Таблица имеет составной индекс по столбцам Ord_Id, Ord_Status и
Ord_Date.
Ord_Date.
4. Избирательность индекса хорошая.
Что было: select *
from ORDERS where upper(Ord_Id) = ' :Ы
and OrcLStatus = 'Not Filled'
and Ord_0ate = SYSDATE;
В предыдущем запросе индекс не используется, потому что к его ведущему столбцу применена функция
upper.
Переменная связи ':Ы 'находится в правой части выражения. Если удалить функцию м^егизведущего столбца и перенести ее в правую часть выражения, то появится возможность использовать индекс. Можно переписать запрос так:Что стало:
select * from ORDERS
where Ordjd = upper (':Ы') and Ord_Status = 'Not Filled' and Ordinate = SYSDATE;
Если значения столбца Ord_Id хранятся не в каком-либо предопределенном формате, например в верхнем регистре, а версия базы данных -Oracle8i, можно создать индексы на базе функции, чтобы избежать сканирования полной таблицы. В более ранних, чем OracleSi, версиях стоит
рассмотреть хранение значений столбца в каком-либо стандартном реги-
стре:
слов записываются прописными буквами, а остальные - строчными). Да,
при этом происходит данных, но отцы-основатели реля-
стре:
верхнем, нижнем
илиinitcap
(режим, при котором начальные буквыслов записываются прописными буквами, а остальные - строчными). Да,
при этом происходит данных, но отцы-основатели реля-
ционных баз данных и правил нормализации поймут наше затруднительное положение.
Не смешивайте при сравнении значения с разными типами данных.
Иначе это приведет к тому, что оптимизатор проигнорирует индекс.
К примеру, если в столбце хранятся данные типа
использовать одиночные кавычки вокруг числового значения во фразе
оно будет сравниваться со столбцом алфавитно-цифрового типа,
скажем, если столбец задан как и для этого столбца
Иначе это приведет к тому, что оптимизатор проигнорирует индекс.
К примеру, если в столбце хранятся данные типа
number,
не нужноиспользовать одиночные кавычки вокруг числового значения во фразе
where.
Аналогично, не забывайте ставить кавычки вокруг значения, еслионо будет сравниваться со столбцом алфавитно-цифрового типа,
скажем, если столбец задан как и для этого столбца
определен индекс. В таком случае помещайте значения констант в одиночные кавычки. Даже если в столбце хранятся числа, кавычки вокруг используемых во фразе
where
значений должны присутствовать, потому что их отсутствие автоматически приводит к сканированиюполной таблицы.
* Не следует использовать оператор
null
для индексированного столбца, так как оптимизатор проигнорирует индекс. Не стройте приложения, содержащие операторы SQL, которые идентичны во всем, кроме жестко закодированных значений во фразахwhere.
Такой эффект обычно наблюдается в приложениях, создающих динамические операторы SQL. По своему внутреннему дизайну жестко закодированные значения во фразе where не позволяют повторно использовать операторы SQL в области коллективного пула. При поддержке в коллективном пуле нескольких операторов SQL с жестко закодированными значениями возникает вопрос о выделении для подобных структур огромного количества памяти. Поддерживаемое приложение спроектировано так, чтобы ничего из имеющегося в нем нельзя было использовать коллективно.Что было:
select First_Name, Last_Name, Hire_Date from EMP where Empno = 1234; select First_Name, Last_Name, Hire_Date from EMP where Empno = 9876;
Что стало:
select First_Name, Last_Name, Hire_Date from EMP where Empno = :Ы;
Допустим, что приложение нельзя перепрограммировать с переменны-
ми связи, а используемая версия базы данных - OraclcSi (8.1.6). В таком
случае может существенно помочь применение параметра файла init.ora
коллективного SQL, но и уменьшается конкуренция за защелку библио-
течного кэша. Параметр
лен на уровне сеанса. Однако следует знать, что установка
таксического анализа, так что предпочтительнее переписать
приложение.
ми связи, а используемая версия базы данных - OraclcSi (8.1.6). В таком
случае может существенно помочь применение параметра файла init.ora
CURSOR_SHARING=for,
При этом не только вводится новое понятиеколлективного SQL, но и уменьшается конкуренция за защелку библио-
течного кэша. Параметр
CURSOR_SHARIN(ka^e
может быть установ-лен на уровне сеанса. Однако следует знать, что установка
force
иногда вызывает существенное замедление син-таксического анализа, так что предпочтительнее переписать
приложение.
Не кодируйте внутри цикла PL/SQL итеративные одиночные
операторы
выполнения в виде групповой операции. Это классический тип
проблемы с производительностью при проектировании итеративного
приложения PL/SQL. Например, если возможна групповая операция
аварийном завершении операции, ее можно выполнить, указав
подсказку которая дает возможность прямой загрузки и
операторы
insert, update или delete
для таблицы при возможности ихвыполнения в виде групповой операции. Это классический тип
проблемы с производительностью при проектировании итеративного
приложения PL/SQL. Например, если возможна групповая операция
insert и
для неё не требуется обеспечивать восстановление приаварийном завершении операции, ее можно выполнить, указав
подсказку которая дает возможность прямой загрузки и
виртуального устранения генерации записей журнала обновления. К сожалению, подобная опция недоступна для операторов
delete
иupdate,
В любом случае операция, которая может быть выполнена как групповая, не должна проводиться итеративным способом, поскольку операции сами по себе не масштабируются, когда число итеративнообрабатываемых строк существенно возрастает. Приведем пример: Что было:
declare
Ord_Struct 0RDERS%R0WTYPE; Cursor c_ord is select *
from ORDERS where Ord_Status = 'Not Filled' and Ord_Date = SYSDATE;
begin
open .c_ord; loop
fetch c_ord into Ord_Struct; exit when c_ord%N0TF0UND; insert into TEMPJJRD values (Ord_Struct.Ord_Id, Ord„Struct.Ord_Date,
(Ord„Struct.Ord„Price * 1.1), Ord_Struct.Ord_Status);
commit;
end loop;
close c_ord;
end;
/
Предшествующий блок кода PL/SQL представляет упрощенный пример использования классической итеративной методики ввода и вычисления
значений для последующей обработки. Здесь определяется новая цена
для путем увеличения ее на 10%. Приведенный ниже эквива-
лентный код осуществит те же самые действия за небольшую долю времени и стоимости, необходимые для выполнения приведенного выше примера. Это будет справедливо даже в том случае, если подсказку использовать не удастся.
Заметьте, что атрибут таблицы должен быть уста-
новлен до выполнения описываемой попытки. Для этого нужно выпол-
нить команду alter table. Да, конечно, в результате Oracle не выполнит
полноценной журнализации действий по вставке данных. Это может
привести к тому, что в случае аварийного завершения задания из-за отка-
за любого вида данные в таблице станут невосстанавливае-
мыми. Но ввиду временной природы этих данных все, что нужно
сделать, - это еще раз стартовать задание после того, как будут устранены
последствия сбоя. Кроме того, отметьте, что операция commit в настроен-
ной версии выполняется всего один раз, а не на каждой итерации цикла.
нить команду alter table. Да, конечно, в результате Oracle не выполнит
полноценной журнализации действий по вставке данных. Это может
привести к тому, что в случае аварийного завершения задания из-за отка-
за любого вида данные в таблице станут невосстанавливае-
мыми. Но ввиду временной природы этих данных все, что нужно
сделать, - это еще раз стартовать задание после того, как будут устранены
последствия сбоя. Кроме того, отметьте, что операция commit в настроен-
ной версии выполняется всего один раз, а не на каждой итерации цикла.
Что стало:
declare begin
insert /-append./ into TEMP_0R0
select Ord_Id, Ord_Date, (Ord_Price * 1.1), Ord_Status from ORDERS where Ord_Status = 'Not Filled' and Ord_Date = SYSDATE;
commit;
end;
/
* He кодируйте в своем приложении коррелированные подзапросы, так как они отрицательно влияют на производительность системы, поглощая значительные ресурсы ЦП, потенциально вызывая связанные с ЦП узкие места и не позволяя приложениям масштабироваться.
Значит, если число строк в коррелированных подзапросах растет, мы сами подписали своим ЦП смертный приговор. Вместо этого нужно использовать встроенные представления (подзапросы во фразе
from
оператораselect,
которые стали доступны с версии 7. ?>), работающие на порядок быстрее и намного лучше масштабирующиеся. В приведенном далее запросе отражены все сотрудники, получающие зарплату выше средней по департаменту, в котором они трудятся. Смотрите , и наслаждайтесь!Что было: select OUTER. from EMP OUTER where OUTER.Salary > (select Avg(Salary) from EMP INNER where INNER.Dept_Id = OUTER.Dept_Id);
В предыдущем запросе содержится коррелированный подзапрос, кото-
рый крайне неэффективен и очень интенсивно использует ЦП. Он рабо-
тает для каждой записи служащего в таблице ЕМР. По мере того как
растет число строк в ЕМР, производительность может начать экспонен-
циально снижаться. Таким образом, для базы данных будет искусственно
увеличиваться коэффициент попадания в буферный кэш до таких высот,
что бедный пользователь останется пребывать в уверенности, что его
база данных как зверь, хотя на самом деле это совсем не так.
рый крайне неэффективен и очень интенсивно использует ЦП. Он рабо-
тает для каждой записи служащего в таблице ЕМР. По мере того как
растет число строк в ЕМР, производительность может начать экспонен-
циально снижаться. Таким образом, для базы данных будет искусственно
увеличиваться коэффициент попадания в буферный кэш до таких высот,
что бедный пользователь останется пребывать в уверенности, что его
база данных как зверь, хотя на самом деле это совсем не так.
Переписанный с помощью встроенных представлений запрос является
функционально эквивалентным, но он в существенно большей степени допускает масштабирование и гарантированно превосходит по производительности своего предшественника.
Что стало: select
from ЕМР Е1, (select Е2. Dept^Id Dept_Id, Avg(E2. Salary) Avg_Sal from EMP E2 group by Dept_Id) DEPT_AVG_SAL where E1.Dept_Id = DEPT_AVG_SAL.Dept_Id and E1.Salary > DEPT_AVG_SAL.Avg_Sal;
Этот пример приводится только для того, чтобы нас не упрекнули в неполноте нашего списка. Не стройте предикаты фразы
where
оператораselect,
если у вас не полностью представлены условия соединения для всех таблиц, упомянутых во фразеfrom.
Мы совсем не хотим, чтобы месье Декарт со своим картезианским произведением (у нас оно больше известно как прямое, или декартово, произведение таблиц. -Прим. тф.)
помогал нам осуществлять план выполнения запроса. Вы будетеудивлены, когда узнаете, как много операторов SQL, не удовлетворяющих этому критичному требованию, довелось нам встретить.
Не кодируйте транзакционную логику ваших приложений буквально
так, как это описывается в документе по проектированию. Звучит это
весьма радикально, но давайте попробуем объяснить. Поймите, что как
SQL, так и PL/SQL позволяют объединять несколько операций в одну.
Предлагаем вам пример. В документе по проектированию логика
транзакции определяется следующим образом: во временную таблицу
необходимо вставить 1 млн строк, а затем последовательно провести по
12 изменений для каждой из этих строк. Но эти действия не должны
выполнять именно так. Исследуйте использование функции
поддерживает логику г/в операторе SQL) в операторе
так, как это описывается в документе по проектированию. Звучит это
весьма радикально, но давайте попробуем объяснить. Поймите, что как
SQL, так и PL/SQL позволяют объединять несколько операций в одну.
Предлагаем вам пример. В документе по проектированию логика
транзакции определяется следующим образом: во временную таблицу
необходимо вставить 1 млн строк, а затем последовательно провести по
12 изменений для каждой из этих строк. Но эти действия не должны
выполнять именно так. Исследуйте использование функции
decode
(онаподдерживает логику г/в операторе SQL) в операторе
insert, чтобы по возможности избежать применения операторов
На самом деле
трактовке ее можно использовать операций
update.
На самом деле
decode
является очень мощной функцией, при правильнойтрактовке ее можно использовать операций
greater than
и
less than.
Исследуйте применение оператора внешнего соединенияон бывает полезен в довольно большом числе приложений и облегчает
объединение нескольких операций в одну. Помните, что нет ничего плохого в том, чтобы выполнять какие-либо дополнительные вычисления для операции
insert,
тем самым замедляя ее. С другой стороны, экономия ресурсов и вычислений за счет невыполнения одного или нескольких операторовupdate
должна уравновесить увеличение стоимости операцииinsert.
Попытки оптимизировать время реакции каждого оператора SQL без нацеливания на общее время реакции пакетного задания лишает управление вычислениями и ресурсами всякого смысла. Приведенный ниже псевдокод содержит операторcreate table,
в котором объединено в одну несколько операций и оптимизируется использование ресурсов: create table DM_SUMMARY_TBL ( Factjd,D2_Key
,D1 Key
,Datekey
, D2_Col
,D1_Col1
,D1_Col2,
)
parallel (degree 12) nologging
partition by range(datekey)
(PI values less than...P36 values less than NOMAXVALUE)
as
select /*+ FUlL(F) FU!..L(D1) FULL(02) */ F.Fact_Id, F.D2_Key, F.D1_Key, F.Datekey, D2.D2_Col, D1.D1_Col1, D1.D1_Col2
from FACT F, DIMENSION D1, DIMENSION D2 where D1.D1_key(+) = F. D1_Key and F.D2_key = D2.D2_Key(+);
He нужно принудительно добиваться, чтобы каждый оператор SQL работал по методологии соединения с вложенными циклами. Хотя многие транзакциоиные операторы SQL оптимально выполняются именно при использовании этой методики, некоторые пакетные задания, несомненно, выиграют от применения хешированных соединений. Например, в случае, когда оператор
select
соединяет две или несколько таблиц и соединяемые пары во фразеwhere таковы,
что одна из таблиц очень мала (скажем, 1 тысяча строк), а другая -слишком велика (1 млн строк). Если при этом предикаты фразыwhere
таковы, что придется обработать подавляющую часть большой таблицы, то предпочтительной методологией соединения для такой пары будет именно хешированное соединение. Если вы с поистине религиозным пылом пытаетесь устранить сканирование полной таблицы, принудительно заставив каждый оператор SQL приложения использовать вложенные циклы, вы закончите тем, что база данных Oracle получит фантастически высокий коэффициент попадания в буферный кэш, возможно, даже на уровне 99,999%, но ее производительность все же будет оставлять желать лучшего.Избегайте использования операторов типа select х from DUAL везде, где только возможно. Как бы безобидно он ни выглядел, он может буквально поглотить производительность системы.
К примеру, если требуется заполнить цифровой столбец с помощью
генератора последовательностей, не кодируйте независимый оператор
select
для выборки следующего значения (nextval,
в переменную PL/SQL с последующим использованием этой переменной во фразеvalues
оператораinsert.
Вместо этого используйте во фразеvalues
оператораinsert
операциюnextval
для последовательности (sequcnce_name.nextval). Обратите внимание на существенную разницу между приведенными ниже кодами (что было и что стало). Во втором коде удалена необязательная ссылка на таблицу DUAL.Что было: declare
Ord_Seq_Val ORDERS.Ord_Id%TYPE; begin
for i in 1. .10000 loop
select 0rd_Seq. NEXTVAL into Ord_Seq_Val from DUAL;
insert into TEMP_ORD(Ord_Id) values (Ord_Seq_Val); /* Do more misc. processing */ end loop;
/
Что стало:
declare begin
for i in 1. .10000
loop
insert into TEMP_ORD(Ord_Id)
values (Ord_Seq_Val.NEXTVAL); /* Do more misc. processing */ end loop; /
И, наконец (этот пример относится не к управлению
производительностью, а к управлению здравым смыслом), не
конструируйте имена столбцов/таблиц и не пишите операторы SQL
на иностранных языках (скажем, на Пали или Пракрит). Применяйте
осмысленные удобные соглашения об именах и перестаньте
производительностью, а к управлению здравым смыслом), не
конструируйте имена столбцов/таблиц и не пишите операторы SQL
на иностранных языках (скажем, на Пали или Пракрит). Применяйте
осмысленные удобные соглашения об именах и перестаньте
использовать свой родной язык, если, конечно, хотите, чтобы кто-нибудь из нас смог помочь, если у вас возникнет проблема. У нас уже был один производитель систем планирования ресурсов предприятия (ERP), который пытался заставить нас быть двуязычными. И хватит с нас!
< Предыдущая | Следующая > |
---|