mysql - 从两个大表的联接中选择不同的值

标签 mysql sql performance join distinct

我有一个animals 表,其中包含大约 300 万条记录。该表除其他几列外,还包含 idnameowner_id 列。我有一个包含大约 250 万条记录的 animal_breeds 表。该表只有 animal_idbreed 列。

我正在尝试查找与特定 owner_id 关联的不同 breed 值,但查询需要 20 秒左右的时间。这是查询:

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

这些表具有所有适当的索引。我无法通过向 animals 表添加 breed 列来对表进行非规范化,因为动物可能会被分配多个品种。我在其他一些具有一对多关系的大型表中也遇到了这个问题。

是否有更高效的方法来实现我正在寻找的目标?这似乎是一个非常简单的问题,但除了预先计算和缓存结果之外,我似乎无法找出实现此目标的最佳方法。

这是我的查询的解释输出。注意使用临时

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_breeds_animal_id_index 索引是多余的,因为表上有唯一键,但只要它不会引起问题,我们现在就可以忽略它:)

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
    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 = ?
    )

关于mysql - 从两个大表的联接中选择不同的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56487060/

相关文章:

mysql - SQL 问题 : 3 tables, 需要 JOIN、COUNT、GROUP BY

c# - 显示来自 SQL 的数据时出错

c++ - 打开文件和关闭文件语句定位: best practice,的优缺点

java - jvisualvm 线程 cpu 时间使用

php - MySql PHP 查询从现有 csv 中过滤名称

MySQL 的 feed 表的 order by 和 group by

mySql:我如何选择表中的每个第一行或第二行?

php - 获取数据库的所有表

sql - 对复合索引和 FFS 感到困惑

c# - 将 SQL 存储在变量中的最佳方式