70

我正在使用 Java EE 上的 MySql 数据库开发一个 Web 项目。我们需要一个视图来汇总 3 个表中的数据,总共超过 300 万行。每个表都是用索引创建的。但是我还没有找到一种方法来利用我们使用 [group by] 创建的视图中的条件选择语句检索中的索引。

我从人们那里得到建议,在 MySql 中使用视图不是一个好主意。因为您不能像在 oracle 中那样为 mysql 中的视图创建索引。但是在我进行的一些测试中,索引可以在视图选择语句中使用。也许我以错误的方式创建了这些视图。

我将用一个例子来描述我的问题。

我们有一个记录 NBA 比赛中高分数据的表,在列 [happend_in] 上有索引

CREATE  TABLE `highscores` (
   `tbl_id` int(11) NOT NULL auto_increment,
   `happened_in` int(4) default NULL,
   `player` int(3) default NULL,
   `score` int(3) default NULL,
   PRIMARY KEY  (`tbl_id`),
   KEY `index_happened_in` (`happened_in`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入数据(8行)

INSERT INTO highscores(happened_in, player, score)
VALUES (2006, 24, 61),(2006, 24, 44),(2006, 24, 81),
(1998, 23, 51),(1997, 23, 46),(2006, 3, 55),(2007, 24, 34), (2008, 24, 37);

然后我创建一个视图来查看科比·布莱恩特每年的最高得分

CREATE OR REPLACE VIEW v_kobe_highScores
AS
   SELECT player, max(score) AS highest_score, happened_in
   FROM highscores
   WHERE player = 24
   GROUP BY happened_in;

我写了一个条件语句,看看科比2006年得到的最高分;

select * from v_kobe_highscores where happened_in = 2006;

当我在 toad for mysql 中解释它时,我发现 mysql 已经扫描所有行以形成视图,然后在其中查找具有条件的数据,而不使用 [happened_in] 上的索引。

explain select * from v_kobe_highscores where happened_in = 2006;

解释结果

我们在项目中使用的视图是在具有数百万行的表中构建的。在每次视图数据检索中扫描表中的所有行是不可接受的。请帮忙!谢谢!

@zerkms 这是我在现实生活中测试的结果。我看不出有太大的区别。我认为@spencer7593 有正确的观点。MySQL 优化器不会在视图查询中“下推”谓词。 实际测试

4

3 回答 3

59

如何让 MySQL 使用索引进行视图查询?简短的回答是提供 MySQL 可以使用的索引。

在这种情况下,最佳索引可能是“覆盖”索引:

... ON highscores (player, happened_in, score)

MySQL 很可能会使用该索引,并且 EXPLAIN 将显示:"Using index"由于WHERE player = 24 (索引中前导列上的相等谓词。GROUP BY happened_id(索引中的第二列),可能允许 MySQL 使用索引优化该索引以避免排序操作。score在索引中包含列将允许完全从索引中满足查询,而无需访问(查找)索引所引用的数据页。

这就是快速的答案。更长的答案是 MySQL 不太可能使用具有前导列的索引来happened_id进行视图查询。


为什么视图会导致性能问题

MySQL 视图的问题之一是 MySQL 不会将谓词从外部查询“推”到视图查询中。

您的外部查询指定WHERE happened_in = 2006. MySQL 优化器在运行内部“视图查询”时不考虑谓词。该视图的查询在外部查询之前单独执行。执行该查询的结果集被“物化”;也就是说,结果存储为中间 MyISAM 表。(MySQL 将其称为“派生表”,当您了解 MySQL 执行的操作时,它们使用的名称是有意义的。)

最重要的是,当 MySQL 运行形成视图定义的查询时,您定义的索引happened_in没有被 MySQL 使用。

创建中间“派生表”后,执行外部查询,使用该“派生表”作为行源。当外部查询运行时,happened_in = 2006谓词被评估。

请注意,视图查询中的所有行都已存储,这(在您的情况下)是每个值的行happened_in,而不仅仅是您在外部查询中指定相等谓词的行。

处理视图查询的方式可能有些人“出乎意料”,这是在 MySQL 中使用“视图”会导致性能问题的原因之一,与其他关系数据库处理视图查询的方式相比。


使用合适的覆盖索引提高视图查询的性能

鉴于您的视图定义和查询,您将获得的最佳结果将是视图查询的“使用索引”访问方法。为此,您需要一个覆盖索引,例如

... ON highscores (player, happened_in, score).

对于您现有的视图定义和现有查询,这可能是最有益的索引(性能方面)。该player列是前导列,因为您在视图查询中的该列上有一个相等谓词。接下来是该happened_in列,因为您对该列进行了 GROUP BY 操作,并且 MySQL 将能够使用该索引来优化 GROUP BY 操作。我们还包括该score列,因为这是您的查询中唯一引用的其他列。这使得索引成为“覆盖”索引,因为 MySQL 可以直接从索引页面满足该查询,而无需访问基础表中的任何页面。这和我们要摆脱那个查询计划一样好:“​​使用索引”而不是“使用文件排序”。


将性能与没有派生表的独立查询进行比较

您可以将查询的执行计划与视图与等效的独立查询进行比较:

SELECT player
     , MAX(score) AS highest_score
     , happened_in
 FROM highscores
WHERE player = 24
  AND happened_in = 2006
GROUP
   BY player
    , happened_in

独立查询也可以使用覆盖索引,例如

... ON highscores (player, happened_in, score)

但无需具体化中间 MyISAM 表。


我不确定前面的任何内容是否都能直接回答您所问的问题。

问:如何让 MySQL 使用 INDEX 进行视图查询?

A:定义视图查询可以使用的合适的INDEX。

简短的回答是提供一个“覆盖索引”(索引包括视图查询中引用的所有列)。该索引中的前导列应该是用相等谓词引用的列(在您的情况下,该列player将是前导列,因为您player = 24在查询中有一个谓词。此外,GROUP BY 中引用的列应该是前导列在索引中,它允许 MySQLGROUP BY通过使用索引而不是使用排序操作来优化操作。

这里的重点是视图查询基本上是一个独立的查询;该查询的结果存储在一个中间“派生”表中(一个 MyISAM 表,在运行针对视图的查询时创建。

在 MySQL 中使用视图不一定是一个“坏主意”,但我强烈提醒那些选择在 MySQL 中使用视图的人要注意 MySQL 如何处理引用这些视图的查询。MySQL 处理视图查询的方式(显着)不同于其他数据库(例如 Oracle、SQL Server)处理视图查询的方式。

于 2012-12-19T03:27:22.300 回答
2

在这种情况下,使用(按此特定顺序)列创建复合索引player + happened_in是您可以做的最好的事情。

PS:不要在这么少的行上测试mysql优化器的行为,因为它可能更喜欢全扫描而不是索引。如果你想看看现实生活中会发生什么 - 用与现实生活相似的数据量填充它。

于 2012-12-19T03:11:15.003 回答
1

这并不能直接回答问题,但对于遇到此问题的其他人来说,这是一个直接相关的解决方法。这实现了与使用视图相同的好处,同时最大限度地减少了缺点。

我设置了一个 PHP 函数,我可以向该函数发送参数,将其推送到内部以最大化索引使用率,而不是在视图外的连接或 where 子句中使用它们。在函数中,您可以制定派生表的 SQL 语法,并返回该语法。然后在调用程序中,您可以执行以下操作:

$table = tablesyntax(parameters);
select field1, field2 from {$table} as x... + other SQL

因此,您获得了视图的封装优势,能够像调用视图一样调用它,但不受索引限制。

于 2014-02-11T06:08:17.933 回答