【问题标题】:Query with Indexes slower than without Indexes有索引的查询比没有索引的要慢
【发布时间】:2016-08-26 11:53:17
【问题描述】:

我使用的是 MySQL 5.6,我的存储引擎是 InnoDB。

我有一个包含 100 万行的表格:

  • ID(主键)
  • 名字
  • 姓氏
  • foreign_key_id(外键,NOT NULL)
  • foreign_key_id2(另一个外键,默认为NULL)

这些行被分隔在:

  • 25%,foreign_key_id 值为 1 且 foreign_key_id2 NULL
  • 25%,foreign_key_id 值为 1 且 foreign_key_id2 非空
  • 25%,foreign_key_id 值为 2 且 foreign_key_id2 NULL
  • 25%,foreign_key_id 值为 2 且 foreign_key_id2 非空

具有以下索引:

  • foreign_key_id 上的索引 foreign_key_idx
  • 索引 foreign_key_2_idxforeign_key_id2
  • (foreign_key_idx, foreign_key_2_idx) 上的复合索引 foreign_key_comp_idx

我执行以下查询:

查询 1 - 没有索引:

SELECT *
FROM table tbl
IGNORE INDEX(foreign_key_idx, foreign_key_2_idx, foreign_key_comp_idx)
WHERE tbl.foreign_key_id = 1 AND tbl.foreign_key_id2 IS NOT NULL

查询 2 - 有索引(无复合索引):

SELECT *
FROM table tbl
IGNORE INDEX(foreign_key_comp_idx)
WHERE tbl.foreign_key_id = 1 AND tbl.foreign_key_id2 IS NOT NULL

查询 3 - 使用复合索引(没有其他索引):

SELECT *
FROM table tbl
IGNORE INDEX(foreign_key_idx, foreign_key_2_idx)
WHERE tbl.foreign_key_id = 1 AND tbl.foreign_key_id2 IS NOT NULL

结果:

查询 1(无索引)执行 全表扫描,并使用 100 万条记录 总时长 0.37 秒。

查询 2(索引,无复合索引)对 foreign_key_idx 索引执行非唯一键查找,并且 使用 50 万条记录,总持续时间为 0.6 秒

查询 3(仅限复合索引)对 复合索引 执行 索引范围扫描,并使用 480K 总时长0.13秒的记录。

我真正不明白的是:为什么 query 2(有索引)总是比 query 1(没有索引)执行得慢?我真的很困,需要一些帮助......

我已经用不同的行数测试了上面的查询,例如 1k、10k、20k、50k、100k、200k、250k、500k、1M 等,总是以相同的比率 (25%),结果相同(查询 2 总是执行缓慢)

提前感谢您,非常感谢您的任何意见!

编辑(2016 年 5 月 2 日)

显示创建表命令:

CREATE TABLE `table` (
   `ID` int(11) NOT NULL AUTO_INCREMENT,
   `FirstName` varchar(255) NOT NULL,
   `LastName` varchar(255) NOT NULL,
   `foreign_key_id` int(11) NOT NULL,
   `foreign_key_id2` int(11) DEFAULT NULL,

   PRIMARY KEY (`ID`),
   KEY `foreign_key_idx` (`foreign_key_id`),
   KEY `foreign_key_2_idx` (`foreign_key_id2`),
   KEY `foreign_key_comp_idx ` (`foreign_key_id`,`foreign_key_id2`),

   CONSTRAINT `foreign_key_idx` FOREIGN KEY (`foreign_key_id`) REFERENCES `table2` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
   CONSTRAINT `foreign_key_2_idx` FOREIGN KEY (`foreign_key_id2`) REFERENCES `table3` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
 ) ENGINE=InnoDB AUTO_INCREMENT=1515998 DEFAULT CHARSET=latin1

解释计划:

不确定是否重要,但 table2 有 20 条记录,table3 也有 100 万条记录。

【问题讨论】:

  • 在我深入研究之前请提供SHOW CREATE TABLE。还为每个提供EXPLAIN SELECT ... 输出。
  • @RickJames 谢谢。添加了 CREATE TABLE 和 EXPLAINS!

标签: mysql optimization indexing innodb mysql-workbench


【解决方案1】:

让我吃惊的是查询 3 比查询 1 快:-)

您需要 25% 的表记录。所以简单地按顺序读取表格应该是最快的方法。 (至少这是我会做的,以及大多数 DBMS 在这种情况下会做的事情。)

使用复合索引是可以的,因为知道选择哪些记录就足够了。但是要遍历一棵树,最终得到 25% 的必须逐个访问的记录,这似乎是一项艰巨的任务。如前所述,令人惊讶的是,它比全表扫描运行得更快。也许物理记录恰好按需要排序,因此您不必从一个部分到另一个部分来回走动,这通常是来自索引时发生的情况。 (解释:假设您在磁盘上的表 A 部分的索引中找到匹配的记录引用,下一个匹配恰好在扇区 B 中,第三个又在扇区 A 中,......这可能需要很长时间。如果你是但是幸运的是,您首先在一个扇区中找到所有记录,然后在另一个扇区中。通过全表扫描,您可以逐个扇区读取扇区,而无需从一个扇区切换到另一个扇区并返回。因此,保证全表扫描相当快,而通过索引访问可能快也可能慢。)

现在查询 2:索引只指向可能匹配的记录(表的 50% 记录,其中只有一半是匹配的)。这意味着您必须按照所述遍历树,只是为了仍然读取表的一半记录。这工作量太大了。

【讨论】:

  • 感谢您的回答。我如何事先知道索引此表/列会产生负面影响?
【解决方案2】:

FOREIGN KEY 是红鲱鱼;索引 (KEY) 是相关的。

索引存储在 BTree 中。 BTrees 对于查找单个项目和扫描具有相同或顺序值的一系列项目非常有效。这就是您的测试用例正在做的事情。

但是,一旦在索引中找到一个项目,查询需要进入“数据”,因为您要求其他列*(在SELECT *)。这意味着要进入数据 BTree,它是根据 PRIMARY KEY 排序的。

索引和数据之间的这种反复来回有些昂贵。一般来说,如果需要超过 20% 的行,那么简单地扫描表(“表扫描”)会更有效,而忽略索引。 (警告:“20%”取决于月相;可能是 10%、30% 或其他。)

通常优化器会在使用索引(当需要很小的百分比时)和进行表扫描之间正确选择。所以,通常你不必担心。

另一个问题... 一个常见的问题是运行计时是“缓存”,这使得相同的查询在第二次运行时花费的时间更少。或者使其他查询运行得更快(因为它缓存了他们需要的东西)。这增加了混乱。

有时值得运行ANALYZE TABLE tbl; 来重新计算用于决定进行表扫描或使用索引的“统计信息”。但我不会太相信标准的“修复”;它也可能使事情变得更糟。 (ANALYZE 做了一些“随机”探测来推测统计数据。)

我只有千分之一次看到真正需要FORCE INDEX 或类似提示之一的查询。所以,我建议不要这样做。

您打算如何处理客户端中的 250K 或 500K 行?这会让大多数客户感到窒息。而且这听起来不像是您经常做的事情?

【讨论】:

  • 感谢您的回答。我如何事先知道索引此表/列会产生负面影响?
  • 通常添加索引应该从不对阅读查询产生负面影响。 DBMS 应该总是 找到最佳执行计划,因此只有在执行计划更好 时才使用索引,而不是没有索引的访问。但似乎优化器会出错,所以如果查询因为这样的错误而变得太慢,你可以通过应用IGNORE INDEX 来帮助它。 (通常优化器会越来越好,所以在将 DBMS 更新到新版本时,您通常可以摆脱这种变通方法。)
【解决方案3】:

索引真的很糟糕,因为每个索引只有两个可能的值。所以我想,与根本不使用索引并简单地检查所有条目相比,使用索引可以减少检查的行数不够显着。

组合索引至少将表分为4部分,因此只需搜索表的四分之一,这足以补偿先询问索引。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-19
    • 1970-01-01
    • 2015-10-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多