我目前正在尝试优化Doctrine 2在此表上生成的查询:
CREATE TABLE `publication` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`global_order` int(11) NOT NULL,
`title` varchar(63) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(63) COLLATE utf8_unicode_ci NOT NULL,
`type` varchar(7) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_AF3C6779B12CE9DB` (`global_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
查询
SELECT *
FROM publication
WHERE type IN ('article', 'event', 'work')
ORDER BY global_order DESC
type
是由Doctrine添加的鉴别器列。虽然WHERE
子句无用,因为type
始终是IN
值之一,但我无法将其删除。
EXPLAIN
告诉我
+------+---------------+------+------+-----------------------------+
| type | possible_keys | key | rows | Extra |
+------+---------------+------+------+-----------------------------+
| ALL | NULL | NULL | 562 | Using where; Using filesort |
+------+---------------+------+------+-----------------------------+
(每次执行查询时rows
都不同)
经过一番阅读后,我发现我可以强制使用这样的索引:
ALTER TABLE `publication` DROP INDEX `UNIQ_AF3C6779B12CE9DB` ,
ADD UNIQUE `UNIQ_AF3C6779B12CE9DB` ( `global_order` , `type` )
和
SELECT *
FROM publication
FORCE INDEX(UNIQ_AF3C6779B12CE9DB)
WHERE global_order > 0
AND type IN ('article', 'event', 'work')
ORDER BY global_order DESC
WHERE
子句总是没用,但这次EXPLAIN
向我显示
+-------+-----------------------+-----------------------+------+-------------+
| type | possible_keys | key | rows | Extra |
+-------+-----------------------+-----------------------+------+-------------+
| range | UNIQ_AF3C6779B12CE9DB | UNIQ_AF3C6779B12CE9DB | 499 | Using where |
+-------+-----------------------+-----------------------+------+-------------+
在我看来它更好,但似乎强制索引也不常见,所以我想知道这对于这样一个简单的查询是否真的有效。
有谁知道执行此查询的更好方法是什么?
谢谢!
答案 0 :(得分:4)
如果您的查询确实是:
SELECT *
FROM publication
WHERE type IN ('article', 'event', 'work')
ORDER BY global_order DESC
...并且所有条目(或几乎所有条目)都匹配IN子句,实际上你没有索引就更好了。如果您输入限制条款,那么您需要的索引实际上位于global_order
,而没有type
字段。原因是,读取索引实际上需要花费一些东西。
如果您要使用整个表格,按顺序读取表格并在内存中对其行进行排序将是您最便宜的计划。如果你只需要几行而且大多数都会匹配where子句,那么选择最小的索引就可以了。
要了解原因,请记录所涉及的磁盘IO。
假设您希望整个表没有索引。要执行此操作,请阅读data_page1,data_page2,data_page3等,访问顺序中涉及的各种磁盘页,直到到达表的末尾。然后你排序并返回。
如果你想要没有索引的前5行,你就像以前一样顺序读取整个表,同时对前5行进行堆排序。不可否认,对于少量行来说,这是很多阅读和排序。
现在假设您希望整个表都带有索引。为此,请按顺序读取index_page1,index_page2等。然后,这将导致您以完全随机的顺序(排序的行出现在数据中)访问data_page3,然后再访问data_page1,然后再访问data_page3,然后再访问data_page2等。所涉及的IO使得按顺序读取整个混乱并将抓取袋分类到内存中会更便宜。
如果您只想要索引表的前5行,相反,使用索引将成为正确的策略。在最坏的情况下,您在内存中加载5个数据页并继续前进。
一个好的SQL查询计划程序btw将根据数据的碎片程度决定是否使用索引。如果按顺序获取行意味着在表格中来回缩放,那么一个好的计划者可能会认为使用索引是不值得的。相反,如果使用相同的索引对表进行聚类,则保证行按顺序排列,从而增加了它的使用可能性。
但是,如果你用另一个表加入相同的查询,而另一个表有一个非常有选择性的where子句可以使用一个小索引,那么规划者可能会认为它实际上更好,例如获取标记为foo
的所有行的ID,使用发布散列它们,并在内存中对它们进行堆排序。
答案 1 :(得分:1)
MySQL尝试确定运行给定查询的最佳方式,并根据其认为最佳的方式决定是否使用索引。
这并不总是正确的。有时手动强制查询使用索引更快,有时则不然。
如果您在特定情况下使用示例数据运行某些测试,您应该能够看到哪种方法执行得更快,并坚持使用该方法。
确保您考虑查询缓存以获得准确的效果基准。
答案 2 :(得分:0)
强制使用索引很少是最好的答案。通常,最好创建和/或优化索引(索引),以便MySQL选择使用它们。 (优化查询更好,但我知道你不能在这里做到这一点。)
当您使用像Doctrine这样无法优化查询且索引无效的内容时,最好的办法是专注于查询缓存。 : - )