在MySQL中严重索引表,但使用了错误的索引导致执行时间过长

时间:2017-06-17 18:30:40

标签: mysql mysqli

之前可能已经提出过这个问题,但我没有找到一个涉及我特定问题的帖子。所以我有一个重度索引的表,其中包含超过2500万行,速度非常重要。我一直在努力尽可能地优化结果,但一些奇怪的事情不断发生。

我有这张桌子:

CREATE TABLE IF NOT EXISTS `externallinks_global` (
                              `url_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
                              `paywall_id` INT UNSIGNED NOT NULL,
                              `url` VARCHAR(767) NOT NULL,
                              `archive_url` BLOB NULL,
                              `has_archive` TINYINT UNSIGNED NOT NULL DEFAULT '0',
                              `live_state` TINYINT UNSIGNED NOT NULL DEFAULT '4',
                              `last_deadCheck` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
                              `archivable` TINYINT UNSIGNED NOT NULL DEFAULT '1',
                              `archived` TINYINT UNSIGNED NOT NULL DEFAULT '2',
                              `archive_failure` BLOB NULL DEFAULT NULL,
                              `access_time` TIMESTAMP NOT NULL,
                              `archive_time` TIMESTAMP NULL DEFAULT NULL,
                              `reviewed` TINYINT UNSIGNED NOT NULL DEFAULT '0',
                              PRIMARY KEY (`url_id` ASC),
                              UNIQUE INDEX `url_UNIQUE` (`url` ASC),
                              INDEX `LIVE_STATE` (`live_state` ASC),
                              INDEX `LAST_DEADCHECK` (`last_deadCheck` ASC),
                              INDEX `PAYWALLID` (`paywall_id` ASC),
                              INDEX `REVIEWED` (`reviewed` ASC),
                              INDEX `HASARCHIVE` (`has_archive` ASC),
                              INDEX `ISARCHIVED` (`archived` ASC),
                              INDEX `APIINDEX1` (`has_archive` ASC, `live_state` ASC, `paywall_id` ASC),
                              INDEX `APIINDEX2` (`live_state` ASC, `paywall_id` ASC),
                              INDEX `APIINDEX3` (`live_state` ASC, `paywall_id` ASC, `archived` ASC),
                              INDEX `APIINDEX4` (`live_state` ASC, `archived` ASC),
                              INDEX `APIINDEX5` (`live_state` ASC, `paywall_id` ASC, `reviewed` ASC),
                              INDEX `APIINDEX6` (`live_state` ASC, `reviewed` ASC),
                              INDEX `APIINDEX7` (`has_archive` ASC, `live_state` ASC, `paywall_id` ASC, `archived` ASC, `reviewed` ASC),
                              INDEX `APIINDEX8` (`has_archive` ASC, `live_state` ASC, `archived` ASC, `reviewed` ASC));

当在WHERE子句中只使用一列进行查询时,它可以正常工作,但是,当我这样做时         EXPLAIN EXTENDED SELECT * FROM externallinks_global FORCE INDEX (APIINDEX1,APIINDEX2,APIINDEX3,APIINDEX4,APIINDEX5,APIINDEX6,APIINDEX7,APIINDEX8) LEFT JOIN externallinks_paywall ON externallinks_global.paywall_id=externallinks_paywall.paywall_id WHERE ( {live_state {1}} {paywall_status {1}}查看IN (0, 7) OR externallinks_global.paywall_id IN (SELECT paywall_id FROM externallinks_paywall WHERE

由于某种原因,它尝试使用REVIEWED索引而不是APIINDEX5。我试图强迫它使用它,但后来它决定不使用索引。我有点失落。我显然做错了什么,但我不知道是什么。

以下是上述查询中的EXPLAIN:

IN (3)) ) AND

1 个答案:

答案 0 :(得分:1)

问题出在你的WHERE条款中:

WHERE (live_state IN (0, 7) OR externallinks_global.paywall_id IN (SELECT paywall_id FROM externallinks_paywall WHEREpaywall_statusIN (3)) ) AND reviewed=0


您拥有的综合指数:

INDEX `APIINDEX5` (`live_state` ASC, `paywall_id` ASC, `reviewed` ASC)

可用于通过任何最左边的前缀(列的组合)进行查询:

  

如果表具有多列索引,则优化程序可以使用索引的任何最左前缀来查找行。

但您要做的是按live_state paywall_id查询,使其独立于live_state并让Mysql无法使用索引问题

Mysql documentation说明了这一点:

  

假设某个表具有以下规范:

CREATE TABLE test (
    id         INT NOT NULL,
    last_name  CHAR(30) NOT NULL,
    first_name CHAR(30) NOT NULL,
    PRIMARY KEY (id),
    INDEX name (last_name,first_name)
);
  

...

     

但是,在以下查询中,名称索引未使用用于查找:

SELECT * FROM test WHERE first_name='Michael';

SELECT * FROM test
   WHERE last_name='Widenius' OR first_name='Michael';

您可以尝试做的就是将查询拆分为两部分, 与UNION合并。

第一部分将通过以下方式查询:

live_state IN (0, 7) AND reviewed=0

(应使用INDEX APIINDEX6

,第二部分将通过以下方式查询:

paywall_id IN (...) AND reviewed=0

(使用INDEX PAYWALLID除非您添加新的paywall_id +已审核索引)。