SQL заявление занимает много времени - PullRequest
2 голосов
/ 13 января 2020

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

Мне уже удалось выполнить эту работу, но запрос принимает 2-3 секунды Я хочу сделать это быстрее, но так как я не знаком с SQL, мне не удается заставить его работать.

Конкретный запрос:

/**
* Only find sentences that are allowed to say back.
* 1. WHERE NOT EXISTS sentences said by this bot said in the past 2 weeks
* 2. WHERE NOT EXISTS sentences said in the last 8 minutes
* 3. WHERE NOT EXISTS sentences said to this customer
* 4. WHERE EXIST sentences with [$translation] translation
*/
 select * from `sentences` where `keyword_id` = 396 
and not exists (select id from `customer_sentences` where sentences.id = customer_sentences.sentence_id and customer_sentences.bot_id = 1 and customer_sentences.created_at >= "2019-12-30 13:25:58") 
and not exists (select id from `customer_sentences` where sentences.id = customer_sentences.sentence_id and customer_sentences.created_at >= "2020-01-13 13:17:58") 
and not exists (select id from `customer_sentences` where sentences.id = customer_sentences.sentence_id and customer_sentences.customer_id = 153375) 
and exists (select id from `sentence_translations` where sentence_translations.sentence_id = sentences.id and sentence_translations.language_id = 1)

Удаляя все утверждение один за другим, я обнаружил ниже, где несуществующий оператор медленный:

and not exists (select id from `customer_sentences` where sentences.id = customer_sentences.sentence_id and customer_sentences.created_at >= "2020-01-13 13:17:58") 

Мне удалось повысить скорость с 30 с до 2-3 с, создав индексы в моей таблице customer_sentences:

$table->index(['created_at', 'bot_id']);
$table->index(['bot_id']);
$table->index(['customer_id']);
$table->index(['bot_id', 'created_at', 'sentence_id']);

Я читал об использовании левого соединения вместо оператора, где не существует, но я не смог заставить это работать.

Ответы [ 2 ]

2 голосов
/ 13 января 2020

Коррелированные подзапросы могут быть неэффективными.

Вы хотите использовать LEFT JOIN вместе с условием, что какой-то обязательный (NOT NULL) столбец в правой таблице IS NULL.

Попробуйте это :

SELECT
* 
FROM sentences AS s
LEFT JOIN customer_sentences AS cs ON s.id = cs.sentence_id AND cs.bot_id = a AND cs.created_at >= '2019-12-30 13:25:58'
LEFT JOIN customer_sentences AS cs2 ON s.id = cs2.sentence_id AND cs.created_at >= '2020-01-13 13:17:58'
LEFT JOIN customer_sentences AS cs3 ON s.id = cs3.sentence_id AND cs.customer_id = 153375
JOIN sentence_translations AS st ON s.id = st.sentence_id AND st.language_id = 1
WHERE cs.id IS NULL
AND cs2.id IS NULL
AND cs3.id IS NULL

Я также заметил, что ваши индексы неэффективны. Их исправление должно иметь большее влияние, чем оптимизация запроса. Ваш первый подзапрос будет использовать составной индекс на ['bot_id', 'created_at', 'sentence_id']. Второй подзапрос будет использовать только часть created_at из ['created_at', 'bot_id']. Ваш третий подзапрос будет использовать индекс ['customer_id']. Ваш четвертый подзапрос, вероятно, не будет использовать какой-либо индекс. Может быть, это крошечная таблица или у вас есть индексы для этой таблицы, которые вы не включили в вопрос.

Если вы создаете составной индекс в customer_sentences, состоящий из первого sentence_id, за которым следует created_at, он может использоваться всеми подзапросами (или объединениями) в этой таблице. Подзапрос, пропущенный created_at, все равно сможет использовать первую часть этого нового индекса. Может быть лучше индексировать только столбец sentence_id. Ваши существующие индексы, вероятно, должны быть удалены. Индексы увеличивают время записи в таблицу, поскольку при каждой записи также необходимо обновить все индексы. Они также увеличивают размер диска вашей таблицы и, возможно, потребление памяти.

Вероятно, вам также следует создать индекс в sentence_translations для sentence_id.

1 голос
/ 13 января 2020

Для этого предложения (которое эквивалентно вашему):

not exists (select 1
            from customer_sentences cs
            where sentences.id = cs.sentence_id and
                  cs.created_at >= '2020-01-13 13:17:58'
           ) 

Вы хотите индекс для customer_sentences(sentence_id, created_at). Порядок важен, и вам нужны оба ключа.

...