【问题标题】:How to make use of index on LEFT OUTER JOIN如何在 LEFT OUTER JOIN 上使用索引
【发布时间】:2015-04-09 16:47:28
【问题描述】:

这是一组描述音乐作曲家的表格:

CREATE TABLE IF NOT EXISTS `compositors` (
`id` int(11) NOT NULL,
  `name` varchar(45) NOT NULL COMMENT 'Nom et Prenom',
  `birth_date` varchar(45) DEFAULT NULL,
  `death_date` varchar(45) DEFAULT NULL,
  `birth_place` varchar(45) DEFAULT NULL,
  `death_place` varchar(45) DEFAULT NULL,
  `gender` enum('M','F') DEFAULT NULL,
  `century` varchar(45) DEFAULT NULL,
  `country` int(11) DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=28741 DEFAULT CHARSET=latin1;


CREATE TABLE IF NOT EXISTS `compositor_biography` (
`index` int(11) NOT NULL,
  `compositor_id` int(11) NOT NULL,
  `url` varchar(255) DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=15325 DEFAULT CHARSET=latin1;


CREATE TABLE IF NOT EXISTS `compositor_comments` (
  `compositor_id` int(11) NOT NULL,
  `comment` text NOT NULL,
  `public` enum('Publique','Privé') NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


CREATE TABLE IF NOT EXISTS `compositor_country` (
  `compositor_id` int(11) NOT NULL,
  `country_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

这是我的索引:

--
-- Index pour la table `compositors`
--
ALTER TABLE `compositors` ADD PRIMARY KEY (`id`), ADD KEY `countries` (`country`);
ALTER TABLE `compositor_biography` ADD PRIMARY KEY (`index`), ADD KEY `index` (`compositor_id`);
ALTER TABLE `compositor_comments` ADD KEY `c_compositor_idx` (`compositor_id`);

最后是样本数据:

INSERT INTO `compositors` (`id`, `name`, `birth_date`, `death_date`, `birth_place`, `death_place`, `gender`, `century`, `country`) VALUES
(1, 'Dummy Compositor', '1606', '1676', 'Bruxellesss', NULL, 'F', '17', 11);

INSERT INTO `compositor_biography` (`index`, `compositor_id`, `url`) VALUES
(15322, 1, 'Dummy Link 1'),
(15323, 1, 'Dummy Link 2'),
(15324, 1, 'Dummy Link 3');

INSERT INTO `compositor_comments` (`compositor_id`, `comment`, `public`) VALUES
(1, 'Dummy Comment', 'Privé');

这是我的 PHP 脚本生成的示例查询:

SELECT DISTINCT compositors.id, compositors.name, compositors.birth_date, compositors.death_date, compositors.birth_place, compositors.death_place, compositors.gender, compositors.century, compositors.country,  
GROUP_CONCAT( compositor_biography.url SEPARATOR ';') AS concat_compositor_biography_url, 
GROUP_CONCAT( compositor_comments.comment SEPARATOR ';') AS concat_compositor_comments_comment, 
GROUP_CONCAT( compositor_comments.public + 0 SEPARATOR ';') AS concat_compositor_comments_public 
FROM compositors 
LEFT JOIN compositor_biography ON compositors.id = compositor_biography.compositor_id 
LEFT JOIN compositor_comments ON compositors.id = compositor_comments.compositor_id 
GROUP BY compositors.id

但是,这个有问题,如果你执行这个查询,你可以看到在concat_compositor_comments_comment列,你有那个结果:

Dummy Comment;Dummy Comment;Dummy Comment

但实际上只有一条评论。

我真的不明白那里有什么问题,但似乎是 GROUP BY。每个 JOIN 应该有一个 GROUP BY - 根据Multiple GROUP_CONCAT on different fields using MySQL 的第二个答案 - 所以我做到了,并且它通过这个查询起作用了:

SELECT DISTINCT compositors.id,
    compositors.NAME,
    compositors.birth_date,
    compositors.death_date,
    compositors.birth_place,
    compositors.death_place,
    compositors.gender,
    compositors.century,
    compositors.country,
    concat_compositor_biography_url,
    concat_compositor_comments_comment,
    concat_compositor_comments_public
FROM compositors
LEFT JOIN (
    SELECT compositor_id,
        GROUP_CONCAT(compositor_biography.url SEPARATOR ';') AS concat_compositor_biography_url
    FROM compositor_biography
    GROUP BY compositor_biography.compositor_id
    ) compositor_biography ON compositors.id = compositor_biography.compositor_id
LEFT JOIN (
    SELECT compositor_id,
        GROUP_CONCAT(compositor_comments.comment SEPARATOR ';') AS concat_compositor_comments_comment,
        GROUP_CONCAT(compositor_comments.PUBLIC + 0 SEPARATOR ';') AS concat_compositor_comments_public
    FROM compositor_comments
    GROUP BY compositor_comments.compositor_id
    ) compositor_comments ON compositors.id = compositor_comments.compositor_id

然而,这个查询有很大的性能问题,因为它不使用 INDEXES,或者至少它似乎扫描了所有的表,并且有 24000 个作曲家,这个查询大约需要 420 秒,而另一个(在 GROUP BY 上给出错误的结果)需要 1 秒。

如何更改第二个查询,使其正确使用索引并且不扫描所有表?

这里是一个 SQL-Fiddle 数据库架构的链接:http://sqlfiddle.com/#!2/6b0132


更新

根据@phil_w 的说法,经过进一步测试,这个查询似乎具有非常好的性能:

SELECT a.id,
    a.name,
    a.concat_compositor_biography_url,
    b.concat_compositor_aliases_data,
    GROUP_CONCAT(compositor_comments.comment SEPARATOR ';') as concat_compositor_comments_comment,
    GROUP_CONCAT(compositor_comments.public + 0 SEPARATOR ';') as concat_compositor_comments_public
FROM (
    SELECT b.id,
    b.name,
    b.concat_compositor_biography_url,
    GROUP_CONCAT(compositor_aliases.data SEPARATOR ';') as concat_compositor_aliases_data
    FROM (
        SELECT compositors.id,
            compositors.name,
            GROUP_CONCAT(compositor_biography.url SEPARATOR ';') AS concat_compositor_biography_url
        FROM compositors
        LEFT JOIN compositor_biography ON compositors.id = compositor_biography.compositor_id
        GROUP BY compositors.id
    ) b
    LEFT JOIN compositor_aliases ON b.id = compositor_aliases.compositor_id
    GROUP BY b.id
) a
LEFT JOIN compositor_comments ON a.id = compositor_comments.compositor_id
GROUP BY a.id  

但是,如何在更紧凑的查询中获得相同的结果? (顺便说一句,我应该为此创建一个新问题并解决这个问题吗?)

【问题讨论】:

    标签: mysql sql database join


    【解决方案1】:

    这个问题与“索引”无关。问题是您有两个连接,并且将返回每个行组合(即,您在另一个连接中有 3 个匹配行到 compositor_biography)。

    修复很简单 - 只需将 DISTINCT 添加到 GROUP_CONCAT() 函数:

    ...
    GROUP_CONCAT( DISTINCT compositor_comments.comment SEPARATOR ';') AS concat_compositor_comments_comment, 
    ...
    

    【讨论】:

    • 不起作用,因为例如,compositor_comments.public 是一个枚举,所以如果我有 3 个公开的 cmets,我的列 concat_compositor_comments_public 中只有一个值,而正确的响应会是“1;1;1”,由于 DISTINCT 关键字,这是不可能的
    【解决方案2】:

    输入 3 次是正常的,因为在 compositor_biography 中有 3 行...

    也许你可以一步一步走, 首先只收集简历:

      SELECT compositors.id, compositors.name, 
        GROUP_CONCAT( compositor_biography.url SEPARATOR ';') AS   concat_compositor_biography_url
        FROM compositors 
        LEFT JOIN compositor_biography ON compositors.id =   compositor_biography.compositor_id  
        GROUP BY compositors.id
    

    然后加入其余部分

    select t.id, t.name,t.concat_compositor_biography_url,
    GROUP_CONCAT( compositor_comments.comment SEPARATOR ';') AS concat_compositor_comments_comment
    from (
      SELECT compositors.id, compositors.name, 
      GROUP_CONCAT( compositor_biography.url SEPARATOR ';') AS concat_compositor_biography_url
      FROM compositors 
      LEFT JOIN compositor_biography ON compositors.id = compositor_biography.compositor_id 
      GROUP BY compositors.id
    ) t
    LEFT JOIN compositor_comments ON t.id = compositor_comments.compositor_id  
    

    等等……

    我不明白为什么它不会使用索引,除非表很小。 尝试“解释选择...”来确认。

    【讨论】:

    • 它最后缺少一个 GROUP BY 指令,在 LEFT JOIN 之后,它需要一个 GROUP BY t.id 否则它不起作用。
    猜你喜欢
    • 2022-09-29
    • 1970-01-01
    • 2014-08-10
    • 2014-01-07
    • 2011-12-22
    • 2013-03-10
    • 2011-02-18
    • 2010-09-29
    相关资源
    最近更新 更多