【问题标题】:SQL query no using indexes properlySQL查询没有正确使用索引
【发布时间】:2013-12-02 04:46:56
【问题描述】:

我的一个 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 解决问题,从而将两个不同的查询结合起来,但我认为这不应该是正确的方法..

【问题讨论】:

  • 是否存在性能问题?有时 SQL 不使用索引,因为它不必
  • 您使用的是什么数据库? MYSQL?
  • 我确实在使用 MYSQL。不使用索引意味着它会扫描大约 600000+ 行,这会使其速度慢得令人讨厌,并且可能会占用太多资源。
  • 您是否尝试过使用USE INDEX 提示?我仍然怀疑 UNION 是要走的路。
  • @GarethD,是的,我做到了。没有错误消息,所以提示应该没问题,但仍然没有使用索引。工会会解决问题,但不应该有更清洁的解决方案吗?我在这里失踪的根本原因是什么..

标签: mysql sql join indexing


【解决方案1】:

我会尝试切换到 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

【讨论】:

    【解决方案2】:

    我认为 Gordon 使用 UNION 是正确的,但您可以让 UNION 更高效:

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

    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 OR 中找到匹配项,它无法准确确定如果索引查找比表扫描更有效,则选择表扫描。

    这不是 MySQL 独有的,在所有 DMBS 中使用UNION 而不是OR 更有效的情况并不少见。

    按照我的方式重新排列查询使优化器有更好的机会使用正确的索引(我怀疑也没有USE INDEX 提示,但我还没有测试过)。

    【讨论】:

      【解决方案3】:

      好吧,如果or 引起了问题,那么最简单的解决方案是将查询分成两部分并使用union 将它们放在一起(在您的情况下,因为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;
      

      我在select 子句中添加了grade,以便order by 可以使用它。

      【讨论】:

      • 我认为 OP 是在询问除了UNION 之外是否还有其他方法。
      • 感谢您的时间和效应器 Gordon,但是,Vatev 是对的。在表格的“创建代码”下,我说应该有比 UNION 更好的方法,不是吗?
      • @Gordon,经过一段时间的搜索和反复试验,我决定选择 UNION。我希望找到一种更清洁的方法,但它很有效!感谢您的帮助。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-07-09
      • 1970-01-01
      • 1970-01-01
      • 2014-07-20
      • 1970-01-01
      • 2021-11-25
      • 1970-01-01
      相关资源
      最近更新 更多