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

rvpgvaaj  于 2023-03-17  发布在  Mysql
关注(0)|答案(3)|浏览(172)

我正在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);

然后我创建一个视图来查看科比Bryant每年的最高得分

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优化器不会在视图查询中“下推”该 predicate 。

9lowa7mx

9lowa7mx1#

我们如何让MySQL(或MariaDB)使用索引进行视图查询?简单地说,我们提供了一个MySQL可以使用的索引。
在这种情况下,最佳指数可能是“覆盖”指数:很可能,最佳指数将变成“覆盖指数”,例如:
CREATE INDEX高分_IX3 ON高分(球员,发生时间,得分)
我们希望MySQL优化器能够使用SELECT的索引。我们首先只测试SELECT(没有CREATE VIEW),EXPLAIN将显示:"Using index",原因是WHERE player = 24(在数据库术语中,这是索引中第一列上的等式 predicate 。GROUP BY happened_id(索引中的第二列),可能允许MySQL避免(可能昂贵的)排序操作,允许按顺序从索引返回行。
在索引中包含score列将允许查询完全从索引页满足,而不必访问(查找)索引引用的数据页。
这是一个快速的答案,更详细的答案是MySQL不太可能使用以happened_id为前导列的索引来进行视图查询。

视图导致性能问题的原因

MySQL视图的一个问题是,MySQL不会将 predicate 从外部查询“推送”到视图查询中。
您的外部查询指定WHERE happened_in = 2006。MySQL优化器在运行内部“视图查询”时不考虑 predicate 。视图的查询在外部查询之前单独执行。执行该查询的结果集得到“物化”;也就是说,结果存储为中间MyISAM表(MySQL称之为“派生表”,当您理解MySQL执行的操作时,他们使用的名称是有意义的)。
底线是,当MySQL执行形成视图定义的查询时,它不会使用您在happened_in上定义的索引。
在创建了中间的“派生表”之后,THEN将执行外部查询,并使用该“派生表”作为行源。当外部查询运行时,将计算happened_in = 2006 predicate 。
注意,视图查询中的所有行都被存储,(在您的示例中)是happened_in的每个值的行,而不仅仅是您在外部查询中指定了等式 predicate 的行。
视图查询的处理方式可能是某些人“意料之外”的,这也是与其他关系数据库处理视图查询的方式相比,在MySQL中使用“视图”会导致性能问题的原因之一。

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

给定你的视图定义和查询,你能得到的最好的方法是视图查询的“Using index”访问方法,为了得到它,你需要一个覆盖索引,例如:

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

这可能是最有利的指标(性能方面)。player列是前导列,因为在视图查询中对该列有一个等式 predicate 。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进行视图查询?
答:定义视图查询可以使用的适当INDEX。

简短的回答是提供一个“覆盖指数”(索引包括视图查询中引用的所有列)。该索引中的前导列应该是使用等式 predicate 引用的列(在您的示例中,列player将是前导列,因为查询中有player = 24 predicate 。此外,GROUP BY中引用的列应该是索引中的前导列,这允许MySQL通过使用索引而不是使用排序操作来优化GROUP BY操作。
这里的关键点是视图查询基本上是一个独立的查询;该查询的结果被存储在中间的“派生”表(MyISAM表,其在针对视图的查询运行时被创建。

在MySQL中使用视图并不一定是一个“坏主意”,但我强烈警告那些选择在MySQL中使用视图的人要知道MySQL是如何处理引用这些视图的查询的。而且MySQL处理视图查询的方式与其他数据库(例如Oracle、SQL Server)处理视图查询的方式(明显)不同。

kuhbmx9i

kuhbmx9i2#

在这种情况下,最好的方法是创建具有player + happened_in列(按此特定顺序)的复合索引。
附言:不要在这么少的行上测试mysql优化器的行为,因为它可能更喜欢fullscan而不是索引。如果你想看看在真实的生活中会发生什么--用现实生活中类似的数据量填充它。

niwlg2el

niwlg2el3#

这并没有直接回答这个问题,但是对于其他遇到这个问题的人来说,这是一个直接相关的解决方案,它可以获得使用视图的相同好处,同时将缺点降到最低。
我设置了一个PHP函数,可以向它发送参数,这些参数是要推送到内部以最大化索引使用率的东西,而不是在视图外部的join或where子句中使用它们。在函数中,您可以为派生表制定SQL语法,并返回该语法。然后,在调用程序中,您可以执行以下操作:

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

这样,您就获得了视图的封装优势,能够像调用视图一样调用视图,但没有索引限制。

相关问题