Оптимизация запроса DISTINCT SQL с условиями OR - PullRequest
1 голос
/ 23 ноября 2011

У меня следующий SQL-запрос:

SELECT DISTINCT business_key
FROM Memory
WHERE concept <> 'case' OR attrib <> 'status' OR value <> 'closed'

Я пытаюсь получить все уникальные бизнес-ключи, которые не имеют концепции записи = case AND attrib = status AND value = closed.Выполнение этого запроса в MySQL с 500 000 записей со всеми уникальными business_keys очень медленное: около 11 секунд.

Я поместил индексы в столбец business_key, в столбцы concept, attrib и value.Я также пытался с объединенным индексом для всех трех столбцов (concept, attrib, value), но результат тот же.

Вот скриншот команды EXPLAIN EXTENDED:

enter image description here

Интересно то, что выполнение запроса без определенного спецификатора приводит к очень быстрому выполнению.

Я также пробовал это:

SELECT DISTINCT m.business_key
FROM Memory m 
WHERE m.business_key NOT IN 
(SELECT c.business_Key 
 FROM Memory c 
 WHERE c.concept = 'case' AND c.attrib = 'status' AND c.value = 'closed')

с еще худшимрезультаты: около 25 секунд

Ответы [ 3 ]

2 голосов
/ 23 ноября 2011

Вы можете добавить составной индекс (concept, attrib, value, business_key), чтобы запрос (если MySQL решит использовать этот индекс) мог найти всю информацию в индексе, не считывая всю таблицу.

Ваш запрос эквивалентен:

SELECT DISTINCT business_key
FROM Memory
WHERE NOT (concept = 'case' AND attrib = 'status' AND value = 'closed')

и этому (что, вероятно, даст тот же план выполнения):

SELECT business_key
FROM Memory
WHERE NOT (concept = 'case' AND attrib = 'status' AND value = 'closed')
GROUP BY business_key

Так как 4все столбцы, которые нужно поместить в индекс, равны VARCHAR(255), длина индекса будет довольно большой.MyISAM не допустит более 1000 байтов, а InnoDB - не более 3072.

Одно из решений - сократить длину последней части, сделав длину индекса менее 1000: 255+255+255+230 = 995:

(concept, attrib, value, business_key(220))

Это будет работать, но на самом деле не очень хорошо иметь такую ​​большую длину индексов, с точки зрения производительности.

Другой вариант - уменьшить длину всех или некоторых из этих 4 столбцов,если это соответствует данным, которые вы ожидаете там хранить.Не нужно объявлять длину 255, если вы ожидаете, что в столбце будет максимум 100.

Другой вариант, который вы можете рассмотреть, - поместить эти 4 столбца в 4 отдельные справочные таблицы.(Или только столбцы, в которых есть повторяющиеся данные. Похоже, что business_key будет содержать повторяющиеся данные, но не так много. Поэтому не очень хорошо будет создать справочную таблицу для этого столбца.)

Пример: поместите concept значения в новую таблицу с чем-то вроде:

CREATE TABLE Concept_Ref
( concept_id INT AUTO_INCREMENT
, concept VARCHAR(255)
, PRIMARY KEY concept_id
, UNIQUE INDEX concept_idx (concept) 
) ;

INSERT INTO Concept_Ref
  ( concept )
SELECT DISTINCT
    concept
FROM
    Memory ;

, а затем измените таблицу Memory на:

ALTER TABLE Memory
ADD COLUMN concept_id INT ;

сделайте это (один раз):

UPDATE 
    Memory m
  JOIN
    Concept_Ref c
      ON c.concept = m.concept
SET m.concept_id = c.concept_id

, а затем опустите столбец Memory.concept:

ALTER TABLE Memory
DROP COLUMN concept ;

Вы также можете добавить ссылки FOREIGN KEY, если измените таблицы с MyISAM на InnoDB.

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

Конечно, для запроса потребуется 4 соединения.И любые операторы INSERT, UPDATE или DELETE в этой таблице должны быть изменены и тщательно продуманы.

Но в целом, я думаю, у вас будет лучшая производительность.С дизайном, который у вас сейчас есть, кажется, что такие значения, как 'case', 'status' и 'closed' повторяются много раз.

1 голос
/ 23 ноября 2011

Если запрос выполняется быстро без DISTINCT, вы пробовали:

SELECT DISTINCT business_key from
(SELECT business_key
 FROM Memory
 WHERE concept <> 'case' OR attrib <> 'status' OR value <> 'closed') v

?

1 голос
/ 23 ноября 2011

Это позволит использовать индекс.Для получения всех строк все равно потребуется некоторое время.

SELECT DISTINCT business_key FROM Memory 
WHERE NOT(concept = 'case' AND attrib AND 'status' AND value = 'closed')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...