【问题标题】:Many-to-Many matching multiple optimization多对多匹配多重优化
【发布时间】:2015-07-08 15:42:18
【问题描述】:

我有一个包含多对多关系的应用程序。我需要从一个表中选择与另一表中变量集的所有行相关联的所有行。

例如,我需要选择与bar 实体ABCE 关联的所有foo 实体。用户可以选择 1、5、12 或 50 个bar 实体来过滤foo 实体

表格中的相关字段:(id 使用 uuid)

/* ~20k rows */
CREATE TABLE `foo` (
   `id` char(36) COLLATE utf8_unicode_ci NOT NULL,
  `title` text COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

/* ~30k rows */
CREATE TABLE `bar` (
  `id` char(36) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

/* ~150k rows */
CREATE TABLE `foo_bar` (
  `id` char(36) COLLATE utf8_unicode_ci NOT NULL,
  `foo_id` char(36) COLLATE utf8_unicode_ci DEFAULT NULL,
  `bar_id` char(36) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `foo_id_foreign` (`foo_id`),
  KEY `bar_id_foreign` (`bar_id`),
  CONSTRAINT `bar_id_foreign` FOREIGN KEY (`bar_id`) 
      REFERENCES `bar` (`id`) ON DELETE CASCADE,
  CONSTRAINT `foo_id_foreign` FOREIGN KEY (`foo_id`) 
      REFERENCES `foo` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

我尝试了从不同 SO 答案中看到的两种不同方法:多个连接和子查询。多个连接似乎工作得相当好,但它似乎不是高度可扩展的。运行子查询似乎应该可以更好地扩展,但运行数小时。

多连接。它可以工作,但是正如预期的那样,每次额外的连接都会以指数方式增加经过的时间。 3 bars 大约需要 800 毫秒,这绝对是很高的。解释看起来很合理。

select `foo`.* 
from `foo`
inner join foo_bar `fb1` on `fb1`.`foo_id` = `foo`.`id`
inner join bar `b1` on `b1`.`id` = `fb1`.`bar_id` AND `b1`.`id` = :some_uuid1
inner join foo_bar `fb2` on `fb2`.`foo_id` = `foo`.`id`
inner join bar `b2` on `b2`.`id` = `fb2`.`bar_id` AND `b2`.`id` = :some_uuid2
inner join foo_bar `fb3` on `fb3`.`foo_id` = `foo`.`id`
inner join bar `b3` on `b3`.`id` = `fb3`.`bar_id` AND `b3`.`id` = :some_uuid3
group by `foo`.`id`
order by `foo`.`title` asc 
limit 25 offset 0

子查询。无限期运行。 where in (subquery)inner join subquery 的效果相同,但最终解释看起来有点不同。

select `foo`.* 
from `foo`
inner join (
    select `foo_id` 
    from `foo_bar` 
    inner join `bar` 
        on `bar`.`id` = `foo_bar`.`bar_id`
    where `bar`.`id` in (:some_uuid1, :some_uuid2, :some_uuid3) 
    group by `foo_id` 
    having COUNT(*) = 3
) as `subset` on `foo`.`id`  = `subset`.`foo_id`
order by `foo`.`title` asc 
limit 25 offset 0

解释:

id  select_type table   type    key            key_len rows  extra
1   PRIMARY     derived ALL     NULL           NULL    6618  Using temporary; Using filesort
1   PRIMARY     foo     eq_ref  PRIMARY        108     1   
2   DERIVED     bar     const   PRIMARY        108     1     Using index; Using temporary; Using filesort
2   DERIVED     foo_bar ref     bar_id_foreign 109     16094 Using where

我的问题是我可以应用任何优化来使这种情况变得可用和可扩展吗?

【问题讨论】:

    标签: mysql database many-to-many


    【解决方案1】:

    你的标准化很好。很高兴您有一个连接表 foo_bar 来处理多对多关系。

    至于优化你的JOIN,你不需要每次要检查新的id时都添加一个新的join,你可以使用IN操作符:

    INNER JOIN foo_bar fb1 ON fb1.foo_id = foo.id AND fb1.id 
       IN (some_uuid1, some_uuid2, some_uuid3);
    

    然后,如果您想获取与所有这三个匹配的行,则整个查询将如下所示:

    SELECT foo.id, foo.title
    FROM foo
    INNER JOIN foo_bar fb ON fb.foo_id = foo.id AND fb.id IN (some_uuid1, some_uuid2, some_uuid3)
    GROUP BY foo.id
    HAVING COUNT(*) = 3
    ORDER BY foo.title
    LIMIT 25;
    

    【讨论】:

    • 噢!实现barfoo_bar 的连接是完全多余的,因为我们只关心id。
    • @SeanFraser 好点,除非您需要来自酒吧表的信息,否则它是多余的。
    • 对于任意数量的bar id,总查询时间减少到大约 600 毫秒,这样更好。该数字的一部分也在排序中,我需要对其进行优化(varchar 与文本、索引等)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-03
    • 2014-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多