SQL查询未正确使用索引

时间:2013-11-18 21:05:27

标签: mysql sql join indexing

我的一个SQL查询出现问题。这是我的问题:

explain 
SELECT DISTINCT profiles.hoofdrubriek, profiles.plaats, profiles.bedrijfsnaam, profiles.gemeente, profiles.bedrijfsslogan, profiles.straatnaam, profiles.huisnummer, profiles.postcode, profiles.telefoonnummer, profiles.fax, profiles.email, profiles.website, profiles.bedrijfslogo 
FROM profiles 
LEFT JOIN profile_subrubriek ON profiles.ID=profile_subrubriek.profile_id 
LEFT JOIN rubrieken ON profile_subrubriek.subrubriek_id=rubrieken.ID  
WHERE (
    rubrieken.rubriek = 'Pedicurepraktijken' OR 
    profiles.hoofdrubriek = 'Pedicurepraktijken'
) 
ORDER BY profiles.grade DESC, profiles.bedrijfsnaam

这段查询中的'OR'运算符导致了麻烦:

rubrieken.rubriek = 'Pedicurepraktijken' OR profiles.hoofdrubriek = 'Pedicurepraktijken'

如果我取出上面两行代码中的一行,我的所有表都应用了索引。将它们与OR运算符组合会导致它崩溃并拒绝使用我在配置文件表中的'hoofdrubriek'列上应用的索引。在我的相关表格的布局下面:

CREATE TABLE `profiles` (
 `ID` varchar(255) NOT NULL DEFAULT '',
 ......
 `hoofdrubriek` varchar(255) DEFAULT NULL,
...


 `timestamp` datetime DEFAULT NULL,
 `meerderevestigingen` varchar(255) NOT NULL,
 `grade` int(5) NOT NULL,
 PRIMARY KEY (`ID`),
 KEY `IDX_TIMESTAMP` (`timestamp`),
 KEY `IDX_NIEUW` (`nieuw`),
 KEY `IDX_HOOFDRUBRIEK` (`hoofdrubriek`),
 KEY `bedrijfsnaam` (`bedrijfsnaam`),
 KEY `grade` (`grade`),
 KEY `gemeente` (`gemeente`),
 KEY `plaats` (`plaats`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8


CREATE TABLE `rubrieken` (
 `ID` mediumint(9) NOT NULL AUTO_INCREMENT,
 `rubriek` varchar(255) NOT NULL,
 PRIMARY KEY (`ID`),
 UNIQUE KEY `rubriek` (`rubriek`)
) ENGINE=MyISAM AUTO_INCREMENT=1905 DEFAULT CHARSET=utf8


CREATE TABLE `profile_subrubriek` (
 `profile_id` varchar(20) NOT NULL,
 `subrubriek_id` mediumint(9) NOT NULL,
 PRIMARY KEY (`subrubriek_id`,`profile_id`),
 KEY `profile_id` (`profile_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

当然我可以通过UNION DISTICT来解决问题,从而结合了两个不同的查询,但我认为这不应该是要走的路..

3 个答案:

答案 0 :(得分:1)

好吧,如果or导致问题,那么最简单的解决方案是将查询分成两部分并使用union将它们放在一起(在您的情况下,因为{{1} }})。使用索引来纠正distinct子句可能是不可能的,因为它引用了两个不同的列:

where

我在SELECT p.hoofdrubriek, p.plaats, p.bedrijfsnaam, p.gemeente, p.bedrijfsslogan, profiles.straatnaam, p.huisnummer, profiles.postcode, p.telefoonnummer, p.fax, p.email, p.website, p.bedrijfslogo, p.grade FROM profiles p LEFT JOIN profile_subrubriek ON p.ID=profile_subrubriek.profile_id LEFT JOIN rubrieken ON profile_subrubriek.subrubriek_id=rubrieken.ID WHERE rubrieken.rubriek = 'Pedicurepraktijken' union SELECT p.hoofdrubriek, p.plaats, p.bedrijfsnaam, p.gemeente, p.bedrijfsslogan, profiles.straatnaam, p.huisnummer, profiles.postcode, p.telefoonnummer, p.fax, p.email, p.website, p.bedrijfslogo, p.grade FROM profiles p LEFT JOIN profile_subrubriek ON p.ID=profile_subrubriek.profile_id LEFT JOIN rubrieken ON profile_subrubriek.subrubriek_id=rubrieken.ID WHERE p.hoofdrubriek = 'Pedicurepraktijken' ORDER BY grade DESC, bedrijfsnaam; 子句中添加了grade,因此select可以使用它。

答案 1 :(得分:1)

我认为Gordon使用UNION是对的,但你可以让UNION更有效率:

在下面的第一个查询中,由于您只是引用profiles表,因此可以删除连接,它们只会导致随后需要删除的重复项。然后在第二个中,您可以将JOIN从OUTER更改为INNER,因为您指的是where子句中最外层表中的一个字段,您声明必须匹配。然后通过添加一个子句来删除由union的第一部分拾取的值,您将有更少的记录来排序和删除重复项。

SELECT  profiles.hoofdrubriek, 
        profiles.plaats, 
        profiles.bedrijfsnaam, 
        profiles.gemeente, 
        profiles.bedrijfsslogan, 
        profiles.straatnaam, 
        profiles.huisnummer, 
        profiles.postcode, 
        profiles.telefoonnummer, 
        profiles.fax, 
        profiles.email, 
        profiles.website, 
        profiles.bedrijfslogo,
        profiles.grade
FROM    profiles   
WHERE   profiles.hoofdrubriek = 'Pedicurepraktijken'
UNION
SELECT  profiles.hoofdrubriek, 
        profiles.plaats, 
        profiles.bedrijfsnaam, 
        profiles.gemeente, 
        profiles.bedrijfsslogan, 
        profiles.straatnaam, 
        profiles.huisnummer, 
        profiles.postcode, 
        profiles.telefoonnummer, 
        profiles.fax, 
        profiles.email, 
        profiles.website, 
        profiles.bedrijfslogo,
        profiles.grade
FROM    profiles 
        INNER JOIN profile_subrubriek 
            ON profiles.ID=profile_subrubriek.profile_id 
        INNER JOIN rubrieken 
            ON profile_subrubriek.subrubriek_id=rubrieken.ID  
WHERE   rubrieken.rubriek = 'Pedicurepraktijken' 
AND     profiles.hoofdrubriek != 'Pedicurepraktijken'
ORDER BY grade DESC, bedrijfsnaam;

我对MySQL优化器内部工作原理的了解至少可以说是朦胧,但我的理解是,根本原因是MySQL不使用索引,因为它需要扫描整个表来检查另一个谓词(rubrieken.rubriek = 'Pedicurepraktijken')。我认为你期望优化器会隐式执行UNION所做的事情。 (我认为)由于OUTER JOIN和OR,优化器无法准确确定在profiles或配置文件中查找匹配的rubrieken行数,它无法准确确定索引搜索是否比表扫描更有效,并选择表扫描。

这不是MySQL的独特之处,所有DMBS都使用UNION代替OR更有效率并不少见。

重新排列查询可以让优化器更好地使用正确的索引(我怀疑没有USE INDEX提示,但我还没有测试过。)

答案 2 :(得分:0)

我会尝试切换到InnoDB,因为它们是索引组织的表。 使用InnoDB时,链接表profile_subrubriek的所有数据都将位于聚簇索引中。

当您从代理主键切换到自然主键rubrieken时,表rubrieken也相同。由于这是一个单列表,因此它的存在至少是有问题的。

所以我会删除表格rubrieken

我会这样做:

CREATE TABLE `profiles` (
 `ID` varchar(255) NOT NULL DEFAULT '',
 ......
 `hoofdrubriek` varchar(255) DEFAULT NULL,
...


 `timestamp` datetime DEFAULT NULL,
 `meerderevestigingen` varchar(255) NOT NULL,
 `grade` int(5) NOT NULL,
 PRIMARY KEY (`ID`),
 KEY `IDX_TIMESTAMP` (`timestamp`),
 KEY `IDX_NIEUW` (`nieuw`),
 KEY `IDX_HOOFDRUBRIEK` (`hoofdrubriek`),
 KEY `bedrijfsnaam` (`bedrijfsnaam`),
 KEY `grade` (`grade`),
 KEY `gemeente` (`gemeente`),
 KEY `plaats` (`plaats`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `profile_rubriek` (
 `profile_id` varchar(20) NOT NULL,
 `rubriek` varchar(255) NOT NULL,
 PRIMARY KEY (`profile_id`,`rubriek`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

查询将是:

SELECT DISTINCT profiles.hoofdrubriek, profiles.plaats, profiles.bedrijfsnaam, profiles.gemeente, profiles.bedrijfsslogan, profiles.straatnaam, profiles.huisnummer, profiles.postcode, profiles.telefoonnummer, profiles.fax, profiles.email, profiles.website, profiles.bedrijfslogo 
FROM profiles 
LEFT JOIN profile_rubriek ON profiles.ID=profile_rubriek.profile_id 
WHERE (
    profile_rubriek.rubriek = 'Pedicurepraktijken' OR 
    profiles.hoofdrubriek = 'Pedicurepraktijken'
) 
ORDER BY profiles.grade DESC, profiles.bedrijfsnaam