从两个大表的连接中选择不同的值
Selecting distinct values from a join of two large tables
我有一个 animals
table 大约有 300 万条记录。 table 在其他几个列中有一个 id
、name
和 owner_id
列。我有一个 animal_breeds
table 大约有 250 万条记录。 table 只有 animal_id
和 breed
列。
我正在尝试查找与特定 owner_id
关联的不同 breed
值,但查询需要 20 秒左右的时间。这是查询:
SELECT DISTINCT `breed`
FROM `animal_breeds`
INNER JOIN `animals` ON `animals`.`id` = `animal_breeds`.`animal_id`
WHERE `animals`.`owner_id` = ? ;
table 具有所有适当的索引。我无法通过向 animals
table 添加 breed
列来使 table 非规范化,因为动物可能被分配到多个品种。我也遇到了其他一些具有一对多关系的大型 table 的问题。
有没有更高效的方法来实现我正在寻找的东西?这似乎是一个非常简单的问题,但除了预先计算和缓存结果之外,我似乎无法找到实现此目标的最佳方法。
这是我的查询的解释输出。注意 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"
根据要求,这里是创建 table 语句(我从 animals
table 中删除了一些不相关的列和索引)。我相信 animal_breeds
table 上的 animal_breeds_animal_id_index
索引是多余的,因为 table 上的唯一键,但我们现在可以忽略它,只要它不会导致问题:)
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
如有任何帮助,我们将不胜感激。谢谢!
对于此查询:
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)
上的索引。复合索引中列的顺序很重要。
有了正确的索引,我想这会很快。
编辑:
根据说明,您使用的值有 1,126,303 个匹配项。时间是由于删除重复项。鉴于表格的大小,令人惊讶的是会有这么多匹配的值。
了解您的数据后,您可以尝试这样的操作:
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 = ?
)
;
想法是在不进行任何过滤的情况下获得不同品种的短列表(对于小列表,它会非常快),然后使用相关子查询进一步过滤列表。由于列表很短,因此只会执行很少的子查询,并且它们只会检查比任何分组(distinct == grouping)快得多的存在。
这仅在您的非重复列表很短时才有效。
根据您的回答随机生成的数据,上述查询为我提供了以下执行计划:
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 = ?
)
我有一个 animals
table 大约有 300 万条记录。 table 在其他几个列中有一个 id
、name
和 owner_id
列。我有一个 animal_breeds
table 大约有 250 万条记录。 table 只有 animal_id
和 breed
列。
我正在尝试查找与特定 owner_id
关联的不同 breed
值,但查询需要 20 秒左右的时间。这是查询:
SELECT DISTINCT `breed`
FROM `animal_breeds`
INNER JOIN `animals` ON `animals`.`id` = `animal_breeds`.`animal_id`
WHERE `animals`.`owner_id` = ? ;
table 具有所有适当的索引。我无法通过向 animals
table 添加 breed
列来使 table 非规范化,因为动物可能被分配到多个品种。我也遇到了其他一些具有一对多关系的大型 table 的问题。
有没有更高效的方法来实现我正在寻找的东西?这似乎是一个非常简单的问题,但除了预先计算和缓存结果之外,我似乎无法找到实现此目标的最佳方法。
这是我的查询的解释输出。注意 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"
根据要求,这里是创建 table 语句(我从 animals
table 中删除了一些不相关的列和索引)。我相信 animal_breeds
table 上的 animal_breeds_animal_id_index
索引是多余的,因为 table 上的唯一键,但我们现在可以忽略它,只要它不会导致问题:)
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
如有任何帮助,我们将不胜感激。谢谢!
对于此查询:
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)
上的索引。复合索引中列的顺序很重要。
有了正确的索引,我想这会很快。
编辑:
根据说明,您使用的值有 1,126,303 个匹配项。时间是由于删除重复项。鉴于表格的大小,令人惊讶的是会有这么多匹配的值。
了解您的数据后,您可以尝试这样的操作:
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 = ?
)
;
想法是在不进行任何过滤的情况下获得不同品种的短列表(对于小列表,它会非常快),然后使用相关子查询进一步过滤列表。由于列表很短,因此只会执行很少的子查询,并且它们只会检查比任何分组(distinct == grouping)快得多的存在。
这仅在您的非重复列表很短时才有效。
根据您的回答随机生成的数据,上述查询为我提供了以下执行计划:
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 = ?
)