Оптимизация процедуры

Запросы, планы, оптимизация запросов, ...

Модераторы: kdv, CyberMax

Ответить
Лысый
Сообщения: 177
Зарегистрирован: 08 ноя 2004, 08:20

Оптимизация процедуры

Сообщение Лысый » 10 дек 2004, 12:44

Привет всем!
FB 1.5.1
Помогите оптимизировать процедуру...

Код: Выделить всё

CREATE PROCEDURE REP$AGE_DIAGRAM (
    ID_ACTION INTEGER)
RETURNS (
    AGE SMALLINT,
    MEN INTEGER,
    WOMEN INTEGER,
    MEN_EMB INTEGER,
    WOMEN_EMB INTEGER)
AS
BEGIN
  /* Процедура для диаграммы по возрастам в разрезе мужчин и женщин */

  FOR
    SELECT DISTINCT (CURRENT_DATE - P.DOB) / 365
      FROM PERSON P
     WHERE EXISTS(SELECT A.ID_PERSON
                      FROM ANSWER A
                     WHERE A.ID_ACTION = :ID_ACTION
                       AND P.ID_PERSON = A.ID_PERSON)
     ORDER BY 1
      INTO :AGE
  DO BEGIN

    SELECT COUNT(*)
      FROM PERSON P
     WHERE P.SEX = 'м'
       AND EXISTS(SELECT A.ID_PERSON
                    FROM ANSWER A
                   WHERE A.ID_ACTION = :ID_ACTION
                     AND P.ID_PERSON = A.ID_PERSON)
       AND (CURRENT_DATE - P.DOB) / 365 = :AGE
      INTO :MEN;

    SELECT SUM(A.EMBEDDED_CNT)
      FROM PERSON P LEFT JOIN ANSWER A ON P.ID_PERSON = A.ID_PERSON
     WHERE P.SEX = 'м'
       AND A.ID_ACTION = :ID_ACTION
       AND (CURRENT_DATE - P.DOB) / 365 = :AGE
      INTO :MEN_EMB;

    SELECT COUNT(*)
      FROM PERSON P
     WHERE P.SEX = 'ж'
       AND EXISTS(SELECT A.ID_PERSON
                    FROM ANSWER A
                   WHERE A.ID_ACTION = :ID_ACTION
                     AND P.ID_PERSON = A.ID_PERSON)
       AND (CURRENT_DATE - P.DOB) / 365 = :AGE
      INTO :WOMEN;

    SELECT SUM(A.EMBEDDED_CNT)
      FROM PERSON P LEFT JOIN ANSWER A ON P.ID_PERSON = A.ID_PERSON
     WHERE P.SEX = 'ж'
       AND A.ID_ACTION = :ID_ACTION
       AND (CURRENT_DATE - P.DOB) / 365 = :AGE
      INTO :WOMEN_EMB;

    SUSPEND;
  END
END
План ужасный:

Код: Выделить всё

PLAN SORT (SORT ((P NATURAL)))JOIN (P NATURAL,A INDEX (FK_PERSON__ANSWER))(P NATURAL)(A INDEX (FK_PERSON__ANSWER))JOIN (P NATURAL,A INDEX (FK_PERSON__ANSWER))(P NATURAL)(A INDEX (FK_PERSON__ANSWER))(A INDEX (FK_PERSON__ANSWER))

------ Performance info ------
Prepare time = 0ms
Execute time = 24s 812ms
Avg fetch time = 775,38 ms
Current memory = 4 744 112
Max memory = 5 487 496
Memory buffers = 2 048
Reads from disk to cache = 1
Writes from cache to disk = 34
Fetches from cache = 11 853 020
В Person ~20 тыс. записей, в Answer ~50 тыс. записей. Понимаю что тормоза от моей криворукости, но что то я лучше придумать не могу... :?

Лысый
Сообщения: 177
Зарегистрирован: 08 ноя 2004, 08:20

Сообщение Лысый » 10 дек 2004, 12:46

Не индексных чтений по Person - 5 597 610. Вот такая фигня...

dimitr
Разработчик Firebird
Сообщения: 888
Зарегистрирован: 26 окт 2004, 16:20

Сообщение dimitr » 10 дек 2004, 13:20

Код: Выделить всё

DO BEGIN

    SELECT
        COUNT(CASE WHEN P.SEX = 'м' THEN 1 ELSE 0 END),
        COUNT(CASE WHEN P.SEX = 'ж' THEN 1 ELSE 0 END)
    FROM PERSON P
    WHERE EXISTS(SELECT A.ID_PERSON
                    FROM ANSWER A
                   WHERE A.ID_ACTION = :ID_ACTION
                     AND P.ID_PERSON = A.ID_PERSON)
        AND (CURRENT_DATE - P.DOB) / 365 = :AGE
    INTO :MEN, :WOMEN;

    SELECT
        SUM(CASE WHEN P.SEX = 'м' THEN A.EMBEDDED_CNT ELSE 0 END),
        SUM(CASE WHEN P.SEX = 'ж' THEN A.EMBEDDED_CNT ELSE 0 END)
    FROM PERSON P
        LEFT JOIN ANSWER A ON P.ID_PERSON = A.ID_PERSON
    WHERE A.ID_ACTION = :ID_ACTION
        AND (CURRENT_DATE - P.DOB) / 365 = :AGE
    INTO :MEN_EMB, WOMEN_EMB;

    SUSPEND;
END
Возможно, стоит эти два запроса вообще объединить в один.

sag
Сообщения: 116
Зарегистрирован: 02 ноя 2004, 11:42

Сообщение sag » 10 дек 2004, 13:34

1. Попробуй заменить
SELECT DISTINCT (CURRENT_DATE - P.DOB) / 365
FROM PERSON P
WHERE EXISTS(SELECT A.ID_PERSON
FROM ANSWER A
WHERE A.ID_ACTION = :ID_ACTION
AND P.ID_PERSON = A.ID_PERSON)
ORDER BY 1

на

SELECT DISTINCT (CURRENT_DATE - P.DOB) / 365
FROM PERSON P, ANSWER A
WHERE A.ID_ACTION = :ID_ACTION
AND P.ID_PERSON = A.ID_PERSON
ORDER BY 1

2. Попарные запросы
SELECT COUNT(*)
FROM PERSON P
WHERE P.SEX = 'м'
AND EXISTS(SELECT A.ID_PERSON
FROM ANSWER A
WHERE A.ID_ACTION = :ID_ACTION
AND P.ID_PERSON = A.ID_PERSON)
AND (CURRENT_DATE - P.DOB) / 365 = :AGE
INTO :MEN;

и

SELECT COUNT(*)
FROM PERSON P
WHERE P.SEX = 'ж'
AND EXISTS(SELECT A.ID_PERSON
FROM ANSWER A
WHERE A.ID_ACTION = :ID_ACTION
AND P.ID_PERSON = A.ID_PERSON)
AND (CURRENT_DATE - P.DOB) / 365 = :AGE
INTO :WOMEN;

объедини в один, а :MEN и :WOMEN определяй через CASE.
Также через CASE объедини попарные запросы, возвращающие
:MEN_EMB и :WOMEN_EMB.

sag
Сообщения: 116
Зарегистрирован: 02 ноя 2004, 11:42

Сообщение sag » 10 дек 2004, 13:38

Вот ведь, пока писал, отвлекаясь на симпатичных работниц, dimitr уже все рассказал :oops:

Лысый
Сообщения: 177
Зарегистрирован: 08 ноя 2004, 08:20

Сообщение Лысый » 10 дек 2004, 14:22

Сделал так:

Код: Выделить всё

CREATE PROCEDURE REP$AGE_DIAGRAM (
    ID_ACTION INTEGER)
RETURNS (
    AGE SMALLINT,
    MEN INTEGER,
    WOMEN INTEGER,
    MEN_EMB INTEGER,
    WOMEN_EMB INTEGER)
AS
BEGIN
  /* Процедура для диаграммы по возрастам в разрезе мужчин и женщин */

  FOR
    SELECT DISTINCT (CURRENT_DATE - P.DOB) / 365
      FROM PERSON P
     WHERE EXISTS(SELECT A.ID_PERSON
                      FROM ANSWER A
                     WHERE A.ID_ACTION = :ID_ACTION
                       AND P.ID_PERSON = A.ID_PERSON)
     ORDER BY 1
      INTO :AGE
  DO BEGIN

    SELECT SUM(CASE WHEN P.SEX = 'м' THEN 1 ELSE 0 END)
         , SUM(CASE WHEN P.SEX = 'ж' THEN 1 ELSE 0 END)
      FROM PERSON P
     WHERE EXISTS(SELECT A.ID_PERSON
                    FROM ANSWER A 
                   WHERE A.ID_ACTION = :ID_ACTION 
                     AND P.ID_PERSON = A.ID_PERSON)
       AND (CURRENT_DATE - P.DOB) / 365 = :AGE
      INTO :MEN
         , :WOMEN;

    SELECT SUM(CASE WHEN P.SEX = 'м' THEN A.EMBEDDED_CNT ELSE 0 END)
         , SUM(CASE WHEN P.SEX = 'ж' THEN A.EMBEDDED_CNT ELSE 0 END)
      FROM PERSON P LEFT JOIN ANSWER A ON P.ID_PERSON = A.ID_PERSON
     WHERE A.ID_ACTION = :ID_ACTION
       AND (CURRENT_DATE - P.DOB) / 365 = :AGE
      INTO :MEN_EMB
         , :WOMEN_EMB;

    SUSPEND; 

  END
END
Результат:

Код: Выделить всё

------ Performance info ------
Prepare time = 0ms
Execute time = 12s 813ms
Avg fetch time = 400,41 ms
Current memory = 4 496 400
Max memory = 5 487 496
Memory buffers = 2 048
Reads from disk to cache = 0
Writes from cache to disk = 22
Fetches from cache = 6 150 899
Не индексных чтений: 2 817 785.

Замена внешнего запроса на неявное связывание работает чуть дольше..

>dimitr
count с case я так понял описка?

В общем результат лучше в 2 раза :D
Последний раз редактировалось Лысый 10 дек 2004, 15:22, всего редактировалось 1 раз.

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 10 дек 2004, 15:07

ты забыл про совет distinct попробовать переписать.

Лысый
Сообщения: 177
Зарегистрирован: 08 ноя 2004, 08:20

Сообщение Лысый » 10 дек 2004, 15:22

kdv писал(а):ты забыл про совет distinct попробовать переписать.
Не забыл :) попробовал:
Замена внешнего запроса на неявное связывание работает чуть дольше..

sag
Сообщения: 116
Зарегистрирован: 02 ноя 2004, 11:42

Сообщение sag » 10 дек 2004, 15:33

> Не забыл попробовал
странно, замена натурального перебора таблицы и выполнения подзапроса на каждой записи (хотя и выполняющегося по индексу)
на, казалось бы, более правильный вариант дает замедление. Хм.

Ну тогда запробуй вот такой вот так:
SELECT (CURRENT_DATE - P.DOB) / 365 AGE,
SUM(CASE WHEN P.SEX = 'м' THEN A.EMBEDDED_CNT ELSE 0 END) MEN_EMB,
SUM(CASE WHEN P.SEX = 'ж' THEN A.EMBEDDED_CNT ELSE 0 END) WOMEN_EMB,
COUNT(DISTINCT (CASE WHEN (P.SEX = 'м') THEN P.ID_PERSON ELSE NULL END) MEN,
COUNT(DISTINCT (CASE WHEN (P.SEX = 'ж') THEN P.ID_PERSON ELSE NULL END) WOMEN
FROM PERSON P, ANSWER A
WHERE A.ID_ACTION = :ID_ACTION
AND P.ID_PERSON = A.ID_PERSON
GROUP BY 1

Лысый
Сообщения: 177
Зарегистрирован: 08 ноя 2004, 08:20

Сообщение Лысый » 10 дек 2004, 16:31

Попробовал! Супер! :!:

Код: Выделить всё

Plan
PLAN SORT (JOIN (A INDEX (FK_ACTION__ANSWER),P INDEX (PK_PERSON)))

Adapted Plan
PLAN SORT (JOIN (A INDEX (FK_ACTION__ANSWER),P INDEX (PK_PERSON)))

------ Performance info ------
Prepare time = 0ms
Execute time = 1s 593ms
Avg fetch time = 63,72 ms
Current memory = 4 328 232
Max memory = 5 487 496
Memory buffers = 2 048
Reads from disk to cache = 0
Writes from cache to disk = 0
Fetches from cache = 373 439
Не индексных чтений нет вообще.
Спасибо огромное!

sag
Сообщения: 116
Зарегистрирован: 02 ноя 2004, 11:42

Сообщение sag » 10 дек 2004, 21:40

> Не индексных чтений нет вообще.
> Спасибо огромное!

Да не за что. Практикуйся в сиквэле, будет тебе щастье. От себя лично, спасибо огромное выражаю dimitr-у и ко за case, group by number, и прочие полезные вещи. Спасибо!
Эх, вот только в пробке простоял два часа и мои симпатишные сотрудницы поразбежались все.

Лысый
Сообщения: 177
Зарегистрирован: 08 ноя 2004, 08:20

Сообщение Лысый » 11 дек 2004, 09:08

Практикуюсь :)
Разъясните мне следующую конструкцию:

Код: Выделить всё

COUNT(DISTINCT (CASE WHEN (P.SEX = 'м') THEN P.ID_PERSON ELSE NULL END)
Я так понимаю, если CASE возвращает null, тогда COUNT его не считает... А вот наличие DISTINCT в COUNT для меня новость. Его во всех агрегированных функциях м. использовать?
sag писал(а):Эх, вот только в пробке простоял два часа и мои симпатишные сотрудницы поразбежались все.
Ну, это с их стороны не простительно :lol:

sag
Сообщения: 116
Зарегистрирован: 02 ноя 2004, 11:42

Сообщение sag » 14 дек 2004, 10:43

> COUNT(DISTINCT (CASE WHEN (P.SEX = 'м')
> THEN P.ID_PERSON ELSE NULL END)
> Я так понимаю, если CASE возвращает null, тогда COUNT его не считает...

да, count ведет себя именно таким макаром.

> А вот наличие DISTINCT в COUNT для меня новость.
> Его во всех агрегированных функциях м. использовать?

Стандарт позволяет использовать "квантификатор" distinct
в агрегатных функциях (count, max, min, sum, avg). Однако
практически полезным мне представляется его сочетание только
с count. В сочетании с max или min distinct вообще не влияет
на результат, возвращаемый функцией; для сочленения distinct-а с
sum или avg мне не удается придумать случая, когда оно оказалось
бы полезным.

Лысый
Сообщения: 177
Зарегистрирован: 08 ноя 2004, 08:20

Сообщение Лысый » 14 дек 2004, 16:21

Ок, спасибо!

Ответить