mysql - 如何在 LEFT OUTER JOIN 上使用索引

标签 mysql sql database join

这是一组描述音乐 Composer 的表格:

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

但是,这个查询有巨大的性能问题,因为它不使用索引,或者至少它似乎扫描了所有表,并且对于 24000 个 Composer ,该查询大约需要 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  

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

最佳答案

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

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

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

关于mysql - 如何在 LEFT OUTER JOIN 上使用索引,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29544372/

相关文章:

mysql - PHPMyAdmin - 内置编辑查询失败

mysql - 从mysql中的另一个表加入一行

php - 从数据库中读取单个值并将其保存在变量中

mysql - 使用 Eclipse 和 MySql 进行 Hibernate 逆向工程

php - SQL更新如果已经存在,否则插入

php - 在 Codeigniter 中比较表并获取数据

php - 如何使用在线 cPanel 数据库在 php 中连接动态数据库

php - 无法选择当前数据库名称

javascript - 使用javascript输出图像但不下载

PHP 取消链接总是失败