Выбор отдельных значений из объединения двух больших таблиц - PullRequest
2 голосов
/ 07 июня 2019

У меня есть таблица animals с 3 миллионами записей. В таблице, среди нескольких других столбцов, есть столбцы id, name и owner_id. У меня есть таблица animal_breeds с 2,5 миллионами записей. В таблице есть только столбцы animal_id и breed.

Я пытаюсь найти различные breed значения, которые связаны с конкретным owner_id, но запрос занимает около 20 секунд. Вот запрос:

SELECT DISTINCT `breed`
FROM `animal_breeds` 
INNER JOIN `animals` ON `animals`.`id` = `animal_breeds`.`animal_id` 
WHERE `animals`.`owner_id` = ? ;

Таблицы имеют все соответствующие индексы. Я не могу денормализовать таблицу, добавив столбец breed в таблицу animals, поскольку для животных можно назначить несколько пород. У меня также есть эта проблема с несколькими другими большими таблицами, которые имеют отношения один ко многим.

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

Вот вывод объяснения из моего запроса. Обратите внимание на Using temporary

id  select_type table   partitions  type    possible_keys   key key_len ref rows    filtered    Extra
1   "SIMPLE"    "a" NULL    "ref"   "PRIMARY,animals_animal_id_index"   "animals_animal_id_index"   "153"   "const" 1126303 100.00  "Using index; Using temporary"
1   "SIMPLE"    "ab"    NULL    "ref"   "animal_breeds_animal_id_breed_unique,animal_breeds_animal_id_index,animal_breeds_breed_index"  "animal_breeds_animal_id_breed_unique"  "5" "pedigreeonline.a.id"   1   100.00  "Using index"

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

CREATE TABLE `animals` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(150) COLLATE utf8_unicode_ci NOT NULL,
  `owner_id` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `animals_animal_id_index` (`owner_id`,`id`),
  KEY `animals_name_index` (`name`),
) ENGINE=InnoDB AUTO_INCREMENT=2470843 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci


CREATE TABLE `animal_breeds` (
  `animal_id` int(10) unsigned DEFAULT NULL,
  `breed` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  UNIQUE KEY `animal_breeds_animal_id_breed_unique` (`animal_id`,`breed`),
  KEY `animal_breeds_animal_id_index` (`animal_id`),
  KEY `animal_breeds_breed_index` (`breed`),
  CONSTRAINT `animal_breeds_animal_id_foreign` FOREIGN KEY (`animal_id`) REFERENCES `animals` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

Любая помощь будет оценена. Спасибо!

Ответы [ 2 ]

1 голос
/ 10 июня 2019

Обладая знаниями о ваших данных, вы можете попробовать что-то вроде этого:

SELECT
    b.*
FROM
    (
        SELECT
            DISTINCT `breed`
        FROM
            `animal_breeds`
    ) AS b
WHERE
    EXISTS (
        SELECT
            *
        FROM
            animal_breeds AS ab
            INNER JOIN animals AS a ON ab.animal_id = a.id
        WHERE
            b.breed = ab.breed
            AND a.owner_id = ?
    )
;

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

Это будет работать, только если ваш отдельный список довольно короткий.

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

id  select_type table   partitions  type    possible_keys   key key_len ref rows    filtered    Extra
1   PRIMARY <derived2>      ALL                 2   100.00  
3   SUBQUERY    a       ref PRIMARY,animals_animal_id_index animals_animal_id_index 153 const   1011    100.00  Using index
3   SUBQUERY    ab      ref animal_breeds_animal_id_breed_unique,`animal_breeds_animal_id_index`,animal_breeds_animal_id_index  `animal_breeds_animal_id_index` 5   test.a.id   2   100.00  Using index
2   DERIVED animal_breeds       range   animal_breeds_animal_id_breed_unique,`animal_breeds_breed_index`,animal_breeds_breed_index  `animal_breeds_breed_index` 1022        2   100.00  Using index for group-by

В качестве альтернативы, вы можете попробовать создать предложение WHERE следующим образом:

...
WHERE
    b.breed IN (
        SELECT
            ab.breed
        FROM
            animal_breeds AS ab
            INNER JOIN animals AS a ON ab.animal_id = a.id
        WHERE
            a.owner_id = ?
    )
1 голос
/ 07 июня 2019

Для этого запроса:

SELECT DISTINCT ab.`breed`
FROM `animal_breeds` ab INNER JOIN
     `animals` a
      ON a.`id` = ab.`animal_id` 
WHERE a.`owner_id` = ? ;

Требуются индексы для animals(owner_id, id) и animal_breeds(animal_id, breed).Порядок столбцов в составном индексе важен.

При правильном индексе я думаю, что это будет очень быстро.

РЕДАКТИРОВАТЬ:

Согласно объяснениюЕсть 1126,303 совпадений для значений, которые вы используете.Время связано с удалением дубликатов.Учитывая размеры таблиц, удивительно, что было бы так много совпадений с одним значением.

...