Архив Тэгов: plsql

SQL and PL/SQL

Мистический пробел, или неправильный способ обработки исключений

На днях коллеги из соседнего отдела столкнулись с проблемой. Иногда один и тот же вызов процедуры на PL/SQL с одними и теми же параметрами давал различные результаты. Причем, если в проблемном блоке, извлекающем одно значение из выборки в переменную, поставить пробел перед точкой с запятой, ошибки нет. Если убрать пробел, все хорошо. Но чудес не бывает. Выяснилось, что практически во всем пакете запросы, возвращающие одну строку в переменные, были обрамлены в блоки обработки исключений следующим образом:

BEGIN
   SELECT VAL
     INTO v_val
     FROM TBL
    WHERE condition ; -- мистический пробел!
EXCEPTION
   WHEN no_data_found OR too_many_rows
   THEN 
      NULL;
END;

И вот когда случалось, что точная выборка возвращает больше одной строки, ошибка гасилась на извлечении второй строки, а в переменную v_val уже успевало попасть значение из первой строки выборки. Установка или удаление мистического пробела меняла план запроса, а может, сам Oracle что-то предпринимал, одно ему, оракулу, ведомое, и в переменную в разных случаях попадало разное значение с последующей неправильной работой всей процедуры.

Посмотрите, как работает пример ниже:

declare
   v_1 NUMBER := 1;
begin
   dbms_output.put_line(v_1);
   begin
      SELECT 2
        INTO v_1
        FROM DUAL
       WHERE 1 = 0;
   exception
      when no_data_found or too_many_rows then
         null;
   end;
   dbms_output.put_line(v_1);
   dbms_random.seed(TO_CHAR(SYSTIMESTAMP, 'DD.MM.YYYY HH24:MI:SS.FF9'));
   begin
      SELECT Q.L
        INTO v_1
        FROM (SELECT LEVEL + 2 AS L, DBMS_RANDOM.VALUE(1, 100) O
                FROM DUAL
             CONNECT BY LEVEL <= 4) Q
       ORDER BY O;
   exception
      when no_data_found or too_many_rows then
         null;
   end;
   dbms_output.put_line(v_1);
end;

Вместо разных планов запроса здесь выступает сортировка по случайному значению. Прогнав данный скрипт, вы увидите, что при ошибке too_many_rows почти каждый раз меняется значение, попадающее в переменную v_1.

Рекомендация в данном случае — в обработке ошибок исключение каждого типа проверять отдельно и в каждом из вариантов инициализировать переменную нужным для дальнейшей корректной работы значением! Может появиться соблазн агрегировать результат при помощи MAX() или MIN(), но это тоже может впоследствии привести — нет, не к ошибке, — к неверной работе реального кода.

Oracle SQL and PL/SQL

Полезный DUAL

Псевдотаблица DUAL с единственным полем DUMMY и единственной строкой, содержащей «X» в качестве этого самого DUMMY, это просто бесценное изобретение инженеров Oracle, нелогично звучащая, но на самом деле изначально в ней было 2 строки. При помощи инструкции CONNECT BY из единственной строки DUAL получаются самые разные кортежи данных. Аналоги существуют во многих базах данных, ну, скажем так, не обязательно существуют, но могут быть созданы. К примеру, при переносе АПК «Нострадамус» (ныне — «ПрограмБанк.БизнесАнализ») на рельсы СУБД Firebird была искусственно создана таблица DUAL (позже — процедура селектного типа), в которую включили ряд вычисляемых столбцов по аналогии с Oracle, например, SYSDATE (для запросов SELECT SYSDATE FROM DUAL и ряда схожих).

В ходе разработки на Oracle DUAL проявляет себя в совершенно разных ипостасях. Например, запрос

SELECT * FROM DUAL WHERE TO_NUMBER('A') = 1

позволит определить номер (ORA-1722) и название оракловой ошибки invalid number, по аналогии в предикате можно определить ряд других ошибок преобразования типов, арифметики, логических операций.

SELECT NVL(MAX(DUMMY), '-') FROM DUAL WHERE 1 = 0

— запрос, демонстрирующий логику неявного GROUP BY.

SELECT LAST_DAY(DATE '2017-11-20') AS NOVEMBER_LAST_DAY FROM DUAL

— легко отлаживать функции даты-времени, CASE-конструкции и вообще любые системные или самописные функции, а также их вариации, за исключением попадающих под ограничения ANSI SQL — т.е. без использования типично PL/SQL типов вроде BOOLEAN или с OUT-параметрами.

И эти несколько запросов — лишь в ходе двух дней. Причем несколько куда более простых или зависимых от конкретной схемы запросов сюда не включил. Пользуйтесь DUAL, и будет Вам счастье!

Oracle SQL and PL/SQL

A problem with Bind Variable Peeking in DSS systems turns out to be a confederate

written by Victor Varlamov, OCP at 07-JUL-2017

There is a Reporting system, where a lot (more than 100) of complex long running queries are launched by Reporting Engine, triggered by different business events. Queries are executed with quite different input parameters (list of customers) and data ranges (daily, weekly, monthly). Because of skewed data in tables, one report can return anywhere from 1 row to 1 million rows depending on the input parameters of the report (different clients have different quantities of rows in fact tables). Every report is implemented as a PL/SQL package with a main function that accepts input parameters, prepares some additional data, and then opens a static cursor with PL/SQL variables as parameters, and finally returns the opened cursor. The CURSOR_SHARING parameter is set to FORCE in the DB server. In such scenarios, SQL can lead to poor performance if the optimizer re-uses unsuitable execution plans that otherwise would not be used if a hard parse was forced using literals. Bind variable peeking can cause a suboptimal plan.

In the book Expert Oracle Practices, Alex Gorbachev relates a story — told to him by Tom Kyte — in which the query plan would change on Monday mornings if it was raining. It’s a true story.

“According to the end-users’ observations, if it was raining heavily Monday morning, then database performance was terrible. Any other day of the week, or on Mondays without rain, there were no problems. Talking to the DBA responsible for the system, Tom found that the performance problems continued until the DBA restarted the database, at which point performance went back to normal. That was the workaround: Rainy Mondays = Reboots.”

It is a real case, and this case was solved without any mysteries and magic, just good knowledge of how to Oracle DB works. I’ll show the solution at the end of the article.

Here is a simple example of how bind variable peeking works:

читать далее »