从MySQL的5.6版本开始,一个非常简单的长查询比5.4版本要长几个订单。
模式:三个表,一个包含元素,一个包含类别,另一个包含M:N表。创建语句:
CREATE TABLE element (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=4257455 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE category (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE elements_categories (
id int(11) NOT NULL AUTO_INCREMENT,
element_id int(11) NOT NULL,
category_id int(11) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY element_id (element_id,category_id),
KEY elements_categories_element_id (element_id),
KEY elements_categories_category_id (category_id),
CONSTRAINT D7d489b06a407a0c1c70f108712c815e FOREIGN KEY (category_id) REFERENCES category (id),
CONSTRAINT co_element_id_57f4f2ec0db9441c_fk_element_id FOREIGN KEY (element_id) REFERENCES element (id)
) ENGINE=InnoDB AUTO_INCREMENT=88131737 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
查询:
SELECT elements_categories.element_id, category.id, category.name
FROM category
INNER JOIN elements_categories
ON category.id = elements_categories.category_id
WHERE elements_categories.element_id IN (1, 2, 3, ...)
因此,元素表甚至没有在此查询中发挥作用,我已经从之前的查询获得了一堆ID。 (免责声明:我使用ORM并且内联第一个查询并没有使事情变得更快。)在我的示例14240中,IN子句中的值的数量会变得非常大。这不是问题,需要十分之一秒左右。这是执行计划:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+--------+---------------------------------------------------------------------------+------------+---------+---------------------------------+-------+--------------------------+
| 1 | SIMPLE | elements_categories | range | element_id,elements_categories_element_id,elements_categories.category_id | element_id | 4 | NULL | 42720 | Using where; Using index |
| 1 | SIMPLE | category | eq_ref | PRIMARY | PRIMARY | 4 | elements_categories.category_id | 1 | NULL |
当我添加一个更多元素时,执行时间会爆炸到60秒加上200秒的提取时间。执行计划也改为:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+------+---------------------------------------------------------------------------+---------------------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | category | ALL | PRIMARY | NULL | NULL | NULL | 75 | NULL |
| 1 | SIMPLE | elements_categories | ref | element_id,elements_categories_element_id,elements_categories_category_id | elements_categories_category_id | 4 | category.id | 760 | Using where |
范围和 eq_ref 查询交换 ALL 和 ref ,切换表的顺序,而不是使用 elements_categories.category_id 作为ref,尽管它是这两个表之间的外键。我不明白为什么计划会像这样改变。
共有75个类别和4,300,000个元素以及1,600,000个作业。
我的猜测是我在这里超出了一些尺寸限制,但无法弄清楚哪一个。此外,我没有改变MySQL 5.5安装中的任何内容,这些安装始终坚持以前的执行计划。
答案 0 :(得分:0)
当你有不同的参数时,优化器选择另一个计划的原因是它查看表中的统计信息并猜测哪个索引将删除最多的行,但这是一个 guess 并且通常可以是错的。 如果你知道的更好,你需要告诉优化器如何处理索引提示,如@Vatev给出的第一个例子。
关于优化器的一个有趣的事情是,由于索引添加了额外的间接层,因此可能需要更多读取,因此必须删除 more 而不是优化器认为有用的表的一半。 (我不记得超过一半......)
优化器的另一个有趣特性是,如果索引包含表中所需的所有信息,它可以避免查找实际行,因此根据您的情况,可能会从添加额外的列中受益指数。此优化用于第一个查询计划"使用索引",而不是第二个。因此添加" element_id"到您的索引" elements_categories_category_id"可能会加快速度。见http://dev.mysql.com/doc/refman/5.6/en/explain-output.html