我使用的是MySQL 5.6,而我的存储引擎是InnoDB。
我有一个包含100万行的表格,其中包含以下列:
这些行分开在:
使用以下索引:
我执行以下查询:
查询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索引执行非唯一键查找, 使用500K记录,总持续时间 0.6秒。
查询3(仅限复合索引)对复合索引执行索引范围扫描并使用480K 记录总持续时间 0.13秒。
我真正不理解的是:为什么查询2 (带索引)的执行速度始终低于查询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万条。
答案 0 :(得分:2)
让我感到惊讶的是查询3比查询1更快: - )
您需要25%的表格记录。因此,简单地按顺序阅读表应该是最快的方法。 (至少这是我要做的,以及大多数DBMS在那种情况下所做的事情。)
使用复合索引是可以的,因为它足以知道要选择哪些记录。但是要经历一棵树只能最终获得必须逐一访问的所有记录的25%,这似乎是一项艰巨的任务。如上所述,令人惊讶的是,这比您的全表扫描运行速度更快。也许物理记录恰好按照需要进行排序,因此您不必从一个部分到另一个部分来回,这是从索引来的时候通常会发生的事情。 (说明:假设您在磁盘上的表部分A的索引中找到匹配的记录引用,下一个匹配恰好在扇区B中,第三个再次在扇区A中,...这可能需要很长时间如果你很幸运,你会发现一个扇区中的所有记录先在另一个扇区中。通过全表扫描,你可以逐个扇区地读取,而不必从一个扇区切换到另一个扇区,然后返回。所以全表扫描保证相当快,而通过索引的acccess可能是快或慢。)
现在查询2:索引仅指向可能匹配的记录(表中只有一半匹配的记录的50%)。这意味着你必须按照描述通过树,只是为了仍然读取表的一半记录。这太过分了。
答案 1 :(得分:1)
FOREIGN KEY
是一只红鲱鱼;索引(KEYs)是相关的。
索引存储在BTree中。 BTree可以有效地查找单个项目以及扫描具有相同或连续值的项目范围。这就是您的测试用例正在做的事情。
但是,一旦在索引中找到了一个项目,查询就需要进入数据'因为您要求其他列*
(在SELECT *
中)。这意味着要覆盖数据BTree,它是根据PRIMARY KEY
订购的。
索引和数据之间的这种反复有点代价。通常,如果需要超过20%的行,则简单地扫描表("表扫描"),忽略索引更有效。 (警告:" 20%"取决于月亮的阶段;它可能是10%,或30%或其他东西。)
通常优化器将在使用索引(需要一小部分)和进行表扫描之间正确选择。所以,通常你不应该担心。
另一个问题......一个常见的问题是运行时间是“缓存”。这使得相同的查询在第二次运行时花费的时间更少。或者让其他查询运行得更快(因为它缓存了他们需要的东西)。这增加了混乱。
有时值得运行ANALYZE TABLE tbl;
来重新计算"统计数据"用于决定进行表扫描或使用索引。但我不会过分相信标准的修复&#39 ;;它也会让事情变得更糟。 (ANALYZE
做了一些随机调查来猜测统计数据。)
一千次中只有一次我看到了一个真正需要FORCE INDEX
或其中一个类似提示的查询。所以,我建议反对。
您打算在客户端使用250K或500K行做什么?这将扼杀大多数客户。它听起来不像你经常做的事情吗?
答案 2 :(得分:0)
指数非常糟糕,因为每个指数只有两个可能的值。所以我想,与完全不使用索引相比,使用索引会减少要检查的行数,并且只需检查所有条目。
组合索引至少将表分为4个部分,因此只能搜索表的四分之一,这足以补偿首先询问索引。