Быстрее 'выбрать отличные thing_id, thing_name из table1' в оракуле - PullRequest
6 голосов
/ 01 июня 2009

У меня есть этот запрос:

select distinct id,name from table1

Для данного идентификатора имя всегда будет одинаковым. Оба поля проиндексированы. Там нет отдельной таблицы, которая отображает идентификатор на имя. Таблица очень большая (10 миллионов строк), поэтому запрос может занять некоторое время.

Этот запрос очень быстрый, так как он проиндексирован:

select distinct name from table1

Аналогично этому запросу:

select distinct id from table1

Если я не могу изменить структуру базы данных (очень безопасное предположение), как лучше структурировать первый запрос производительности?

Изменить, чтобы добавить очищенную таблицу:


Name                           Null     Type
------------------------------ -------- ----------------------------
KEY                            NOT NULL NUMBER
COL1                           NOT NULL NUMBER
COL2                           NOT NULL VARCHAR2(4000 CHAR)
COL3                           VARCHAR2(1000 CHAR)
COL4                           VARCHAR2(4000 CHAR)
COL5                           VARCHAR2(60 CHAR)
COL6                           VARCHAR2(150 CHAR)
COL7                           VARCHAR2(50 CHAR)
COL8                           VARCHAR2(3 CHAR)
COL9                           VARCHAR2(3 CHAR)
COLA                           VARCHAR2(50 CHAR)
COLB                           NOT NULL DATE
COLC                           NOT NULL DATE
COLD                           NOT NULL VARCHAR2(1 CHAR)
COLE                           NOT NULL NUMBER
COLF                           NOT NULL NUMBER
COLG                           VARCHAR2(600 CHAR)
ID                             NUMBER
NAME                           VARCHAR2(50 CHAR)
COLH                           VARCHAR2(3 CHAR)

20 rows selected

Ответы [ 11 ]

12 голосов
/ 01 июня 2009

[ПОСЛЕДНИЕ РЕДАКТИРОВАТЬ]

Мой ОРИГИНАЛЬНЫЙ ОТВЕТ относительно создания соответствующего индекса (имя, идентификатор) для замены индекса (имя) приведено ниже. (Это не был ответ на первоначальный вопрос, который запрещал любые изменения в базе данных.)

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

Если эти запросы даже возвращают наборы результатов, набор результатов будет напоминать только набор результатов из запроса OP, почти случайно , используя причудливую гарантию о данных что дон предоставил нам. Этот оператор НЕ эквивалентен исходному SQL, эти операторы предназначены для особого случая , как описано Доном.

 select m1.id
      , m2.name
   from (select min(t1.rowid) as min_rowid
              , t1.id
           from table1 t1
          where t1.id is not null
          group by t1.id
        ) m1
      , (select min(t2.rowid) as min_rowid
             , t2.name from table1 t2
         where t2.name is not null
         group by t2.name
        ) m2
  where m1.min_rowid = m2.min_rowid
  order
     by m1.id

Давайте распакуем это:

  • m1 - это встроенное представление, которое возвращает нам список различных значений идентификатора.
  • м2 - это встроенное представление, которое дает нам список различных значений имени.
  • материализуют виды м1 и м2
  • соответствует ROWID из m1 и m2 , чтобы соответствовать id с name

Кто-то еще предложил идею слияния индексов. Ранее я отклонял эту идею, планируя оптимизатор, чтобы он соответствовал десяткам миллионов строк, не исключая ни одного из них.

При достаточно низкой мощности для идентификатора и имени и с правильным планом оптимизатора:

 select m1.id
      , ( select m2.name
            from table1 m2
           where m2.id = m1.id
             and rownum = 1
        ) as name
   from (select t1.id
           from table1 t1
          where t1.id is not null
          group by t1.id
        ) m1
  order
     by m1.id

Давайте распакуем это

  • m1 - это встроенное представление, которое возвращает нам список различных значений идентификатора.
  • материализовать вид m1
  • для каждой строки в m1 , запросите таблицу1, чтобы получить значение имени из одной строки (клавиша остановки)

ВАЖНОЕ ПРИМЕЧАНИЕ

Эти операторы ФУНДАМЕНТАЛЬНО отличаются от запроса OP. Они предназначены для возврата РАЗНОГО результирующего набора, чем запрос OP. случается , чтобы вернуть желаемый набор результатов из-за странной гарантии данных. Дон сказал нам, что name определяется id. (Верно ли обратное? Определяется ли id как name? ​​Есть ли у нас ЗАЯВЛЕННАЯ ГАРАНТИЯ, не обязательно обеспечиваемая базой данных, но гарантирующая, что мы можем воспользоваться?) Для любого значения ID для каждой строки с этим значением ID будет то же значение NAME. (И мы также гарантируем, что обратное утверждение верно, что для любого значения NAME каждая строка с этим значением NAME будет иметь одинаковое значение ID?)

Если это так, возможно, мы сможем использовать эту информацию. Если ID и NAME появляются в разных парах, нам нужно найти только одну конкретную строку. «Пара» будет иметь соответствующий ROWID, который, как правило, доступен для каждого из существующих индексов. Что если мы получим минимальный ROWID для каждого ID, и получим минимальный ROWID для каждого NAME. Разве мы не можем тогда сопоставить ID с NAME на основе ROWID, который содержит пару? Я думаю, что это может сработать, учитывая достаточно низкий уровень мощности. (То есть, если мы имеем дело только с сотнями ROWID, а не с десятками миллионов.)

[/ ПОСЛЕДНИЕ РЕДАКТИРОВАТЬ]

[EDIT]

Вопрос теперь дополнен информацией, касающейся таблицы, которая показывает, что столбцы ID и столбец NAME допускают значения NULL. Если Дон может жить без значений NULL, возвращаемых в наборе результатов, то добавление предиката IS NOT NULL в оба этих столбца может позволить использовать индекс. (ПРИМЕЧАНИЕ: в индексе Oracle (B-Tree) значения NULL НЕ отображаются в индексе.)

[/ EDIT]

ОРИГИНАЛЬНЫЙ ОТВЕТ:

создать соответствующий индекс

create index table1_ix3 on table_1 (name,id) ... ;

Хорошо, это , а не ответ на вопрос, который вы задали , но это правильный ответ для исправления проблемы с производительностью. (Вы указали отсутствие изменений в базе данных, но в этом случае изменение базы данных является правильным ответом.)

Обратите внимание, что если у вас есть индекс, определенный для (name,id), то вам (весьма вероятно) не нужен индекс для (name), так как оптимизатор будет рассматривать ведущий столбец name в другом индексе.

(ОБНОВЛЕНИЕ: как кто-то более проницательный, чем я указывал, я даже не рассматривал возможность того, что существующие индексы являются растровыми индексами, а не индексами B-дерева ...)


Пересмотрите свою потребность в наборе результатов ... Вам нужно вернуть id, или будет достаточно возврата name.

select distinct name from table1 order by name;

Для определенного имени вы можете отправить второй запрос, чтобы получить связанный id, если и когда вам это нужно ...

select id from table1 where name = :b1 and rownum = 1;

Если вы действительно нуждаетесь в указанном наборе результатов, вы можете попробовать некоторые альтернативные варианты, чтобы посмотреть, улучшится ли производительность. Я не очень надеюсь на что-либо из этого:

select /*+ FIRST_ROWS */ DISTINCT id, name from table1 order by id;

или

select /*+ FIRST_ROWS */ id, name from table1 group by id, name order by name;

или

select /*+ INDEX(table1) */ id, min(name) from table1 group by id order by id;

ОБНОВЛЕНИЕ: как проницательно отмечали другие, с помощью этого подхода мы тестируем и сравниваем производительность альтернативных запросов, что является своего рода подходом типа «попал в ловушку». (Я не согласен, что это случайно, но я согласен, что это удар или мисс.)

ОБНОВЛЕНИЕ: Том предлагает подсказку ALL_ROWS. Я не учел это, потому что был действительно сосредоточен на получении плана запроса с использованием INDEX. Я подозреваю, что запрос OP выполняет полное сканирование таблицы, и, вероятно, время занимает не сканирование, а уникальная операция сортировки (<10g) или операция хеширования (10gR2 +), которая занимает время. (Отсутствует временная статистика и трассировка события 10046, я просто догадываюсь здесь.) Но опять же, может быть, это сканирование, которое знает, что высокая отметка на столе может быть выходом из огромного пространства пустых блоков. </p>

Практически само собой разумеется, что статистика в таблице должна быть актуальной, и мы должны использовать SQL * Plus AUTOTRACE или, по крайней мере, EXPLAIN PLAN для просмотра планов запросов.

Но ни один из предложенных альтернативных запросов действительно не решает проблему производительности.

Возможно, что подсказки повлияют на оптимизатор, который выберет другой план, в основном удовлетворяя ORDER BY из индекса, но я не очень надеюсь на это. (Я не думаю, что подсказка FIRST_ROWS работает с GROUP BY, подсказка INDEX может.) Я вижу потенциал для такого подхода в сценарии, где есть блоки блоков данных, которые являются пустыми и редко заполненными, и ny получает доступ к данным. блоков через индекс, фактически может быть значительно меньше блоков данных, извлеченных в память ... но этот сценарий будет скорее исключением, чем нормой.


ОБНОВЛЕНИЕ: Как указывает Роб ван Вийк, использование средства трассировки Oracle является наиболее эффективным подходом к выявлению и решению проблем с производительностью.

Без вывода EXPLAIN PLAN или SQL * Plus AUTOTRACE, я просто догадываюсь здесь.

Я подозреваю, что проблема с производительностью, которая у вас есть сейчас, заключается в том, что на блоки данных таблицы необходимо ссылаться, чтобы получить указанный набор результатов.

Не обойтись, запрос не может быть удовлетворен только из индекса, так как нет индекса, содержащего столбцы NAME и ID, с ID или NAME столбец как ведущий столбец. Два других «быстрых» OP-запроса могут быть выполнены из индекса без необходимости ссылаться на строку (блоки данных).

Даже если план оптимизатора для запроса должен был использовать один из индексов, он все равно должен извлечь соответствующую строку из блока данных, чтобы получить значение для другого столбца. А без предиката (без предложения WHERE) оптимизатор, скорее всего, выберет полное сканирование таблицы и, вероятно, выполнит операцию сортировки (<10g). (Опять же, EXPLAIN PLAN покажет план оптимизатора, как и AUTOTRACE.) </p>

Я также предполагаю (большое предположение), что оба столбца определены как NOT NULL.

Вы также можете рассмотреть определение таблицы как таблицы с индексами (IOT), особенно если это только два столбца в таблице. (IOT - не панацея, у него свой собственный набор проблем с производительностью.)


Вы можете попробовать переписать запрос (если только это не изменение базы данных, которое также является вербальным). В наших средах баз данных мы считаем запрос такой же частью базы данных, как таблицы и индексы.)

Опять же, без предиката оптимизатор, скорее всего, не будет использовать индекс. Существует вероятность того, что план запроса может использовать один из существующих индексов для быстрого возврата первых строк, добавив подсказку, протестировав комбинацию из:

select /*+ INDEX(table1) */ ...
select /*+ FIRST_ROWS */ ...
select /*+ ALL_ROWS */ ...

  distinct id, name from table1;
  distinct id, name from table1 order by id;
  distinct id, name from table1 order by name;
  id, name from table1 group by id, name order by id;
  id, min(name) from table1 group by id order by id;
  min(id), name from table1 group by name order by name;

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

(ОБНОВЛЕНИЕ: кто-то еще указал, что оптимизатор может выбрать объединение двух индексов на основе ROWID. Это возможно, но без предиката для удаления некоторых строк, это, вероятно, будет гораздо более дорогим подходом (при сопоставлении 10 с миллионов ROWID) из двух индексов, особенно когда ни одна из строк не будет исключена на основе совпадения.)

Но все эти теоретизирования не сводятся к приседу без некоторой статистики производительности.


В отсутствие изменения чего-либо еще в базе данных, единственная надежда (я могу думать) о том, что вы ускорите запрос, состоит в том, чтобы убедиться, что операция сортировки настроена так, чтобы (требуемая) операция сортировки могла выполняться в памяти, а не на диске. Но это не совсем правильный ответ. Оптимизатор может вообще не выполнять операцию сортировки, вместо этого он может выполнять операцию хеширования (10gR2 +), и в этом случае это должно быть настроено. Операция сортировки с моей стороны - всего лишь предположение, основанное на прошлом опыте работы с Oracle 7.3, 8, 8i, 9i.)

Серьезный администратор БД будет иметь больше проблем с тем, что вы будете использовать параметры SORT_AREA_SIZE и / или HASH_AREA_SIZE для ваших сеансов, чем он будет создавать правильные индексы. (И эти параметры сеанса являются «старой школой» для версий до 10g автоматического управления памятью.)

Покажите вашему администратору базы данных спецификацию для набора результатов, позвольте администратору настроить его.

2 голосов
/ 02 июня 2009

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

Вы, мы или оптимизатор должны знать статистику о ваших данных. И тогда вы можете измерять с помощью таких инструментов, как EXPLAIN PLAN или SQL Trace / tkprof или даже простым инструментом автоматической трассировки из SQL Plus.

Можете ли вы показать нам результат этого:

set serveroutput off
select /*+ gather_plan_statistics */ distinct id,name from table1;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

А как выглядит вся ваша таблица1? Пожалуйста, покажите выходные данные описания.

С уважением, Роб.

0 голосов
/ 02 июня 2009

Это может работать лучше. Предполагается, что, как вы сказали, имя всегда одинаково для данного идентификатора.

WITH id_list AS (SELECT DISTINCT id FROM table1)
SELECT id_list.id, (SELECT name FROM table1 WHERE table1.id = id_list.id AND rownum = 1)
  FROM id_list;
0 голосов
/ 02 июня 2009

Если для данного id всегда возвращается один и тот же name, вы можете выполнить следующее:

SELECT  (
        SELECT  name
        FROM    table1
        WHERE   id = did
                AND rownum = 1
        )
FROM    (
        SELECT  DISTINCT id AS did
        FROM    table1
        WHERE   id IS NOT NULL
        )

Оба запроса будут использовать индекс на id.

Если вам все еще нужны значения NULL, запустите:

SELECT  (
        SELECT  name
        FROM    table1
        WHERE   id = did
                AND rownum = 1
        )
FROM    (
        SELECT  DISTINCT id AS did
        FROM    table1
        WHERE   id IS NOT NULL
        )
UNION   ALL
SELECT  NULL, name
FROM    table1
WHERE   id IS NULL
        AND rownum = 1

Это будет менее эффективно, поскольку второй запрос не использует индексы, но остановится на первом NULL, с которым он сталкивается: если он находится близко к началу таблиц, то вам повезло. *

Подробнее о производительности смотрите в моем блоге:

0 голосов
/ 02 июня 2009

Действительно попробуй что-нибудь с администраторами. В самом деле. Попытайтесь сообщить о преимуществах и ослабьте их страхи перед ухудшением производительности.

У вас есть среда разработки / база данных для тестирования этого материала?

Насколько своевременными должны быть данные?

Как насчет копии таблицы, уже сгруппированной по идентификатору и имени с правильной индексацией? Пакетное задание может быть настроено на обновление новой таблицы раз в ночь.

Но если это не сработает ...

Как насчет экспорта всех пар идентификаторов и имен в альтернативную базу данных, где вы можете группировать и индексировать в свою пользу и оставлять администраторов баз данных со всей их самодовольной жесткостью?

0 голосов
/ 02 июня 2009

"Таблица очень большая (10 миллионов строк)" Если вы не можете изменить базу данных (добавить индекс и т. Д.). Тогда у вашего запроса не будет выбора, кроме как прочитать всю таблицу. Итак, во-первых, определите, сколько времени это займет (т.е. время SELECT ID, NAME FROM TABLE1). Вы не получите это быстрее, чем это. Второй шаг, который он должен сделать, - это ОТЛИЧИЕ. В 10g + это должно использовать HASH GROUP BY. До этого это операция SORT. Первый быстрее. Если ваша база данных 9i, вы МОЖЕТЕ получить улучшение, скопировав 10 миллионов строк в базу данных 10g и сделав это там. В качестве альтернативы, выделите объемы памяти (Google ALTER SESSION SET SORT_AREA_SIZE). Это может нанести вред другим процессам в базе данных, но тогда ваши администраторы базы данных не предоставят вам больших возможностей.

0 голосов
/ 02 июня 2009

Вы можете попробовать что-то вроде

Select Distinct t1.id, t2.name
FROM (Select Distinct ID From Table) As T1
INNER JOIN table t2 on t1.id=t2.id

Select distinct t1.id, t2.name from table t1
inner Join table t2 on t1.id=t2.id

Не уверен, будет ли это работать медленнее или быстрее, чем оригинал, поскольку я не совсем понимаю, как настроена ваша таблица. Если у каждого идентификатора всегда будет одно и то же имя, а идентификатор уникален, я не вижу смысла в отличительном.

0 голосов
/ 02 июня 2009

Идентификатор уникален? Если это так, вы можете удалить DISTINCT из запроса. Если нет - может, ему нужно новое имя? Да, я знаю, не могу изменить схему ...

0 голосов
/ 01 июня 2009

Зачем вам даже иметь «имя» в предложении, если имя всегда одинаково для данного идентификатора? (нм ... вы хотите имя, которое вы не просто проверяете на существование)

SELECT name, id FROM table WHERE id in (SELECT DISTINCT id FROM table)?

Не знаю, поможет ли это ...

0 голосов
/ 01 июня 2009

Не желая потворствовать практике бросания вещей в стену, пока что-то не прилипнет, попробуйте это:

select id, name from table1 group by id, name

У меня смутные воспоминания о том, что GROUP BY необъяснимо быстрее, чем DISTINCT.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...