web-dev-qa-db-fra.com

Comment faire en sorte que MySQL utilise un INDEX pour une requête d'affichage?

Je travaille sur un projet Web avec la base de données MySql sur Java EE. Nous avions besoin d’une vue pour résumer les données de 3 tables avec plus de 3 millions de lignes au total. Chaque table a été créée avec un index. Mais j’ai Je n'ai pas trouvé le moyen de tirer parti des index de la récupération d'instruction select conditionnelle à partir de la vue créée avec [group by].

J'ai des suggestions de personnes qui tiliser des vues dans MySql n'est pas une bonne idée. Parce que vous ne pouvez pas créer d’index pour les vues dans mysql comme dans Oracle. Mais dans certains tests que j'ai pris, les index peuvent être utilisés dans l'instruction view select. J'ai peut-être créé ces vues de manière erronée.

Je vais utiliser un exemple pour décrire mon problème.

Nous avons une table qui enregistre les données pour les meilleurs scores dans les jeux NBA, avec l'index sur la colonne [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;

insérer des données (8 lignes)

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);

alors je crée une vue pour voir le score le plus élevé que Kobe Bryant a obtenu chaque année

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;

J'ai écrit une déclaration conditionnelle pour voir le score le plus élevé obtenu par kobe obtenu en 2006;

select * from v_kobe_highscores where happened_in = 2006;

Quand je l'ai expliqué dans toad pour mysql, j'ai découvert que mysql avait scan toutes les lignes pour former la vue, puis trouver des données avec une condition dans celle-ci, sans utiliser l'index sur [happen_in].

explain select * from v_kobe_highscores where happened_in = 2006;

explain result

La vue que nous utilisons dans notre projet est construite entre des tables contenant des millions de lignes. L'analyse de toutes les lignes d'une table à chaque extraction de données d'affichage est inacceptable. S'il vous plaît aider! Merci!

@zerkms Voici le résultat que j'ai testé sur la vie réelle. Je ne vois pas beaucoup de différences entre. Je pense que @ spencer7593 a le bon point. L’optimiseur MySQL n’affiche pas ce prédicat dans la requête de vue. real-life test

51
Roger Ray

Comment obtenez-vous MySQL pour utiliser un index pour une requête de vue? La réponse courte fournit un index que MySQL peut utiliser.

Dans ce cas, l'indice optimal est probablement un indice "couvrant":

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

Il est probable que MySQL utilisera cet index et le message EXPLAIN indiquera: "Using index" en raison de WHERE player = 24 _ (prédicat d’égalité sur la colonne de tête de l’index. Le GROUP BY happened_id (la deuxième colonne de l’index), peut permettre à MySQL de l’optimiser en utilisant l’index pour éviter une opération de tri. L'inclusion de la colonne score dans l'index permettra à la requête d'être entièrement satisfaite à partir de l'index, sans avoir à consulter (rechercher) les pages de données référencées par l'index.

C'est la réponse rapide. La réponse plus longue est qu'il est très peu probable que MySQL utilise un index avec la colonne de droite de happened_id pour la requête de vue.


Pourquoi la vue cause un problème de performances

L’un des problèmes que vous avez avec la vue MySQL est que MySQL n’inscrit pas le prédicat de la requête externe dans la requête de la vue.

Votre requête externe spécifie WHERE happened_in = 2006. L'optimiseur MySQL ne considère pas le prédicat lorsqu'il exécute la "requête de vue" interne. Cette requête pour la vue est exécutée séparément, avant la requête externe. Les résultats de l'exécution de cette requête sont "matérialisés"; c'est-à-dire que les résultats sont stockés sous forme de table MyISAM intermédiaire. (MySQL appelle cela une "table dérivée", et le nom qu'ils utilisent est logique lorsque vous comprenez les opérations effectuées par MysQL.)

La ligne du bas est que l'index que vous avez défini sur happened_in n'est pas utilisé par MySQL lorsqu'il renvoie la requête qui constitue la définition de la vue.

Une fois la "table dérivée" intermédiaire créée, ALORS la requête externe est exécutée, en utilisant cette "table dérivée" comme source de lignes. C'est quand cette requête externe s'exécute que le happened_in = 2006 prédicat est évalué.

Notez que toutes les lignes de la requête de vue sont stockées, ce qui (dans votre cas) est une ligne pour TOUTES les valeurs de happened_in, pas seulement celui sur lequel vous spécifiez un prédicat d'égalité dans la requête externe.

La manière dont les requêtes d'affichage sont traitées peut être "inattendue" par certains, et c'est l'une des raisons pour lesquelles l'utilisation de "vues" dans MySQL peut entraîner des problèmes de performances, par rapport à la manière dont les requêtes sont traitées par d'autres bases de données relationnelles.


Amélioration des performances de la requête de vue avec un index de couverture approprié

Compte tenu de votre définition de vue et de votre requête, le meilleur moyen d'obtenir une méthode d'accès "Utilisation d'index" pour la requête de vue. Pour cela, vous avez besoin d’un index couvrant, par exemple.

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

C'est probablement l'index le plus avantageux (en termes de performances) pour votre définition de vue existante et votre requête existante. La colonne player est la colonne de tête car vous avez un prédicat d'égalité sur cette colonne dans la requête de vue. Le happened_in est la suivante, parce que vous avez une opération GROUP BY sur cette colonne et que MySQL va pouvoir utiliser cet index pour optimiser l’opération GROUP BY. Nous incluons également la colonne score car il s'agit de la seule autre colonne référencée dans votre requête. Cela fait de l'index un index "couvrant", car MySQL peut répondre à cette requête directement à partir de pages d'index, sans qu'il soit nécessaire de visiter les pages de la table sous-jacente. Et c'est aussi bien que nous allons sortir de ce plan de requête: "Utiliser l'index" sans "Utiliser le port de fichiers".


Comparez les performances à une requête autonome sans table dérivée

Vous pouvez comparer le plan d'exécution de votre requête à la vue par rapport à une requête autonome équivalente:

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

La requête autonome peut également utiliser un index couvrant, par exemple.

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

mais sans qu'il soit nécessaire de matérialiser une table MyISAM intermédiaire.


Je ne suis pas sûr que l’un des précédents donne une réponse directe à la question que vous posiez.

Q: Comment faire en sorte que MySQL utilise un INDEX pour une requête d'affichage?

R: Définissez un INDEX approprié que la requête de vue peut utiliser.

La réponse courte est de fournir un "index couvrant" (l'index inclut toutes les colonnes référencées dans la requête de vue). Les colonnes de tête de cet index doivent être les colonnes référencées avec des prédicats d'égalité (dans votre cas, la colonne player serait une colonne de tête car vous avez un player = 24 prédicat dans la requête. De plus, les colonnes référencées dans GROUP BY doivent être des colonnes de tête dans l'index, ce qui permet à MySQL d'optimiser le GROUP BY opération, en utilisant l’index plutôt qu’une opération de tri.

Le point clé ici est que la requête de vue est essentiellement une requête autonome; les résultats de cette requête sont stockés dans une table "dérivée" intermédiaire (une table MyISAM créée lors de l'exécution d'une requête sur la vue).

Utiliser des vues dans MySQL n’est pas forcément une "mauvaise idée", mais j’aimerais fortement avertir ceux qui choisissent d’utiliser des vues dans MySQL d’être conscient de la façon dont MySQL traite les requêtes qui référencent ces vues. Et la manière dont les processus de traitement de MySQL diffèrent (considérablement) de la façon dont les requêtes de vue sont traitées par d’autres bases de données (par exemple, Oracle, SQL Server).

44
spencer7593

Création de l’index composite avec player + happened_in (dans cet ordre particulier), les colonnes sont ce que vous pouvez faire de mieux dans ce cas.

PS: ne testez pas le comportement de l'optimiseur mysql sur une si petite quantité de lignes, car il est susceptible de préférer le fullscan aux index. Si vous voulez voir ce qui se passera dans la vie réelle, remplissez-le avec une quantité de données identique à celle de la vie réelle.

2
zerkms

Cela ne répond pas directement à la question, mais il s'agit d'une solution de contournement directement liée aux autres personnes confrontées à ce problème. Cela présente les mêmes avantages que d'utiliser une vue, tout en minimisant les inconvénients.

J'ai configuré une fonction PHP) à laquelle je peux envoyer des paramètres, des éléments à insérer dans l'intérieur afin de maximiser l'utilisation de l'index, plutôt que de les utiliser dans une jointure ou une clause where en dehors d'une vue. peut formuler la syntaxe SQL d'une table dérivée et la renvoyer. Ensuite, dans le programme appelant, vous pouvez procéder de la manière suivante:

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

Ainsi, vous obtenez les avantages d'encapsulation de la vue, la possibilité de l'appeler comme s'il s'agissait d'une vue, mais pas les limitations d'index.

1
ray.gurganus