Проблема производительности запросов Oracle - PullRequest
1 голос
/ 31 августа 2011

У нас есть следующая схема базы данных в Oracle 10g Express Edition: Изображение

Один из наших запросов выглядит так:

    select
        *
    from
        torder_item oi_0
    where
        oi_0.id in
        (
            select
                max(oi_1.id)
            from
                torder_item oi_1, torder o
            where
                oi_1.torder_id = o.id
            group by
                oi_1.tproduct_id
        )
        or oi_0.id in
        (
            select
                max(oi_2.id)
            from
                torder_item oi_2, tproduct p
            where
                oi_2.tproduct_id = p.id
            group
                by p.group_id
        );

Проблема в том, что запрос выполняется очень медленно. В настоящее время в каждой таблице меньше 4000 строк, но время выполнения запроса на моем компьютере превышает 6 секунд. И это упрощенная версия. Если я поменяю 'или in' на 'union':

    select
        *
    from
        torder_item oi_0
    where
        oi_0.id in
        ((
            select
                max(oi_1.id)
            from
                torder_item oi_1, torder o
            where
                oi_1.torder_id = o.id
            group by
                oi_1.tproduct_id
        )
        union
        (
            select
                max(oi_2.id)
            from
                torder_item oi_2, tproduct p
            where
                oi_2.tproduct_id = p.id
            group
                by p.group_id
        ));

возвращает те же результаты, но выполняется мгновенно. К сожалению, мы используем Hibernate, который, похоже, не поддерживает union, поэтому я не могу просто изменить запрос следующим образом. Это след исходного запроса:

    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        1      0.04       0.14          0         10          0           0
    Execute      1      0.00       0.00          0          0          0           0
    Fetch        8      6.19       6.19          0      31136          0          96
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total       10      6.24       6.34          0      31146          0          96

    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
    Parsing user id: 5  

    Rows     Row Source Operation
    -------  ---------------------------------------------------
         96  FILTER  (cr=31136 pr=0 pw=0 time=14041 us)
       1111   TABLE ACCESS FULL TORDER_ITEM (cr=14 pr=0 pw=0 time=3349 us)
         96   FILTER  (cr=7777 pr=0 pw=0 time=1799577 us)
     102096    HASH GROUP BY (cr=7777 pr=0 pw=0 time=1584153 us)
    1234321     TABLE ACCESS FULL TORDER_ITEM (cr=7777 pr=0 pw=0 time=35809 us)
          0   FILTER  (cr=23345 pr=0 pw=0 time=4354068 us)
       5075    HASH GROUP BY (cr=23345 pr=0 pw=0 time=4250913 us)
    1127665     HASH JOIN  (cr=23345 pr=0 pw=0 time=2716544 us)
    1127665      TABLE ACCESS FULL TORDER_ITEM (cr=7105 pr=0 pw=0 time=38500 us)
    3818430      TABLE ACCESS FULL TPRODUCT (cr=16240 pr=0 pw=0 time=22423 us)

Я пытался добавить индексы и выполнить анализ таблиц, но это не помогло.

У кого-нибудь есть идея, почему он такой медленный и как его улучшить?

Вот данные теста, если кто-то хочет воспроизвести проблему.

Ответы [ 4 ]

5 голосов
/ 31 августа 2011

Вы уже нашли решение вашей проблемы с производительностью.Вы можете использовать представление и запросить это представление из спящего режима.

0 голосов
/ 01 сентября 2011

Почему это так медленно?

Потому что для каждой строки TORDER_ITEM Oracle выполняет первый подзапрос, а затем - если oi_0.id не встречается в результатах подзапроса - второй подзапрос. Вот почему вы видите такие большие числа в столбце «Строки» выходных данных плана (например, 3818430 означает, что таблица TPRODUCT, содержащая 3762 строки, была полностью отсканирована 1015 раз)

В случае объединения план выполнения отличается: сначала выполняются оба подзапроса, и результат (96 уникальных идентификаторов) сохраняется в памяти, и с этим результатом Oracle посещает каждую строку TORDER_ITEM - так что фактически каждый подзапрос был выполнен один раз вместо 1000.

Не спрашивайте меня, почему оптимизатор не настолько умен, чтобы делать что-то подобное в случае первого запроса.

Я надеюсь, что Hibernate поддерживает внешние объединения. Мое предложение здесь состоит в том, чтобы слева присоединиться к TORDER_ITEM с первым подзапросом, а затем со вторым подзапросом и отфильтровать те строки, для которых было что-то в первом или втором подзапросе. Я имею в виду

SELECT oi_0.*
  FROM torder_item oi_0
  LEFT JOIN (SELECT MAX(oi_1.id) id
               FROM torder_item oi_1
          /* you don't need the join with torder here, it isn't used anyway */
              GROUP BY oi_1.tproduct_id
            ) subquery1 ON subquery1.id = oi_0.id
  LEFT JOIN  (SELECT MAX(oi_2.id) id
                FROM torder_item oi_2,
                     tproduct p
               WHERE oi_2.tproduct_id = p.id
               GROUP BY p.group_id
             ) subquery2 ON subquery2.id = oi_0.id
 WHERE subquery1.id IS NOT NULL OR subquery2.id IS NOT NULL
0 голосов
/ 31 августа 2011

Согласно моему комментарию под вашим вопросом, я думаю, что оба запроса эквивалентны:

select
    *
from
    torder_item oi_0
where
    oi_0.id in
    (
        select
            max(oi_1.id)
        from
            torder_item oi_1
        group by
            oi_1.tproduct_id
    )

Однако я понимаю, что запрос, приведенный в вопросе, является упрощенным, и это может не соответствовать действительностизапрос.

0 голосов
/ 31 августа 2011

Я не знаю, поддерживает ли Hibernate этот тип запросов EXISTS, но вот как это можно написать:

select
    *
from
    torder_item oi_0
where
    EXISTS
    (
        select
            *
        from
            torder_item oi_1, torder o
        where
            oi_1.torder_id = o.id
        group by
            oi_1.tproduct_id
        having
            oi_0.id = max(oi_1.id)
    )
    or EXISTS
    (
        select
            *
        from
            torder_item oi_2, tproduct p
        where
            oi_2.tproduct_id = p.id
        group
            by p.group_id
        having
            oi_0.id = max(oi_2.id)
    );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...