0

我目前正在尝试优化 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 |
+-------+-----------------------+-----------------------+------+-------------+

在我看来它更好,但是强制索引似乎并不常见,所以我想知道它对于这样一个简单的查询是否真的有效。

有谁知道执行此查询的更好方法是什么?

谢谢!

4

3 回答 3

4

如果您的查询确实是:

SELECT *
FROM publication
WHERE type IN ('article', 'event', 'work')
ORDER BY global_order DESC

...并且所有条目(或几乎所有条目)都将匹配 IN 子句,实际上完全没有索引会更好。如果您在限制子句中折腾,那么您想要的索引实际上是 on 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 查询计划器将根据数据的碎片程度来决定是否使用索引。如果按顺序获取行意味着在表格中来回缩放,那么优秀的计划者可能会认为不值得使用索引。相反,如果使用相同的索引对表进行集群,则可以保证行是有序的,从而增加了它被使用的可能性。

但是,如果您将同一个查询与另一个表连接起来,并且该表有一个非常有选择性的 where 子句可以使用一个小索引,那么规划器可能会决定实际上更好,例如获取标记为的行的所有 ID foo,散列将它们与出版物一起加入,并在内存中对它们进行堆排序。

于 2013-04-25T07:43:39.160 回答
1

MySQL 尝试确定运行给定查询的最佳方式,并根据它认为最好的方式决定是否使用索引。

它并不总是正确的。有时手动强制查询使用索引更快,有时则不然。

如果您在特定情况下使用样本数据进行一些测试,您应该能够看到哪种方法执行得更快,并坚持使用该方法。

确保考虑到查询缓存以获得准确的性能基准。

于 2013-04-24T15:06:37.083 回答
0

强制使用索引很少是最好的答案。一般来说,最好创建和/或优化索引(索引),以便 MySQL 选择使用它们。(优化查询会更好,但我知道你不能在这里这样做。)

当您使用诸如 Doctrine 之类的东西时,您无法优化查询并且索引也无济于事,最好的选择是专注于查询缓存。:-)

于 2013-04-28T00:44:53.980 回答