web-dev-qa-db-fra.com

Quand devrais-je utiliser un index composite?

  1. Quand devrais-je utiliser un index composite dans une base de données?
  2. Quelle est la ramification de la performance en utilisant un indice composite)?
  3. Pourquoi devrais-je utiliser un index composite?

Par exemple, j'ai une table homes:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  PRIMARY KEY  (`home_id`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
) ENGINE=InnoDB  ;

Est-il judicieux pour moi d'utiliser un index composite pour geolat et geolng, tel que:

Je remplace:

  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),

avec:

KEY `geolat_geolng` (`geolat`, `geolng`)

Si c'est le cas:

  • Pourquoi?
  • Quelle est la ramification de la performance en utilisant un indice composite)?

MISE À JOUR:

Étant donné que de nombreuses personnes ont déclaré que cela dépendait entièrement des requêtes que j'effectue, voici la requête la plus courante:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

MISE À JOUR 2:

Avec le schéma de base de données suivant:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `primary_photo_group_id` int(10) unsigned NOT NULL default '0',
  `customer_id` bigint(20) unsigned NOT NULL,
  `account_type_id` int(11) NOT NULL,
  `address` varchar(128) collate utf8_unicode_ci NOT NULL,
  `city` varchar(64) collate utf8_unicode_ci NOT NULL,
  `state` varchar(2) collate utf8_unicode_ci NOT NULL,
  `Zip` mediumint(8) unsigned NOT NULL,
  `price` mediumint(8) unsigned NOT NULL,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `num_of_beds` tinyint(3) unsigned NOT NULL,
  `num_of_baths` decimal(3,1) unsigned NOT NULL,
  `num_of_floors` tinyint(3) unsigned NOT NULL,
  `description` text collate utf8_unicode_ci,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  `display_status` tinyint(1) NOT NULL,
  `date_listed` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `contact_email` varchar(100) collate utf8_unicode_ci NOT NULL,
  `contact_phone_number` varchar(15) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`home_id`),
  KEY `customer_id` (`customer_id`),
  KEY `city` (`city`),
  KEY `num_of_beds` (`num_of_beds`),
  KEY `num_of_baths` (`num_of_baths`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
  KEY `account_type_id` (`account_type_id`),
  KEY `display_status` (`display_status`),
  KEY `sqft` (`sqft`),
  KEY `price` (`price`),
  KEY `primary_photo_group_id` (`primary_photo_group_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=8 ;

En utilisant le SQL suivant:

EXPLAIN SELECT  homes.home_id,
                    address,
                    city,
                    state,
                    Zip,
                    price,
                    sqft,
                    year_built,
                    account_type_id,
                    num_of_beds,
                    num_of_baths,
                    geolat,
                    geolng,
                    photo_id,
                    photo_url_dir
            FROM homes
            LEFT OUTER JOIN home_photos ON homes.home_id = home_photos.home_id
                AND homes.primary_photo_group_id = home_photos.home_photo_group_id
                AND home_photos.home_photo_type_id = 2
            WHERE homes.display_status = true
            AND homes.geolat BETWEEN -100 AND 100
            AND homes.geolng BETWEEN -100 AND 100

EXPLAIN renvoie:

id  select_type  table        type  possible_keys                                    key                  key_len  ref     rows  Extra
----------------------------------------------------------------------------------------------------------
1   SIMPLE       homes        ref   geolat,geolng,display_status                     display_status       1        const   2     Using where
1  SIMPLE        home_photos  ref   home_id,home_photo_type_id,home_photo_group_id   home_photo_group_id  4        homes.primary_photo_group_id   4  

Je ne comprends pas très bien comment lire la commande EXPLAIN. Cela semble-t-il bon ou mauvais? Pour le moment, je n'utilise PAS d'indice composite pour geolat et geolng. Devrais-je être?

120
Teddy

Vous devez utiliser un index composite lorsque vous utilisez des requêtes qui en bénéficient. Un index composite qui ressemble à ceci:

index( column_A, column_B, column_C )

bénéficiera une requête qui utilise ces champs pour la jointure, le filtrage et parfois la sélection. Les requêtes utilisant les sous-ensembles de colonnes les plus à gauche de ce composite bénéficieront également. Donc, l’index ci-dessus répondra aussi aux requêtes qui ont besoin

index( column_A, column_B, column_C )
index( column_A, column_B )
index( column_A )

Mais cela ne le fera pas (du moins pas directement, peut-être que cela peut aider partiellement s'il n'y a pas de meilleurs indices) pour les requêtes nécessitant

index( column_A, column_C )

Notez comment column_B est manquant.

Dans votre exemple d'origine, un index composite pour deux dimensions bénéficiera principalement aux requêtes qui interrogent les deux dimensions ou la dimension la plus à gauche par elle-même, mais pas la dimension la plus à droite par elle-même. Si vous interrogez toujours deux dimensions, un index composite est la solution, peu importe la première (probablement).

96
Mark Canlas

Imaginez que vous ayez les trois requêtes suivantes:

Requête I:

SELECT * FROM homes WHERE `geolat`=42.9 AND `geolng`=36.4

Requête II:

SELECT * FROM homes WHERE `geolat`=42.9

Requête III:

SELECT * FROM homes WHERE `geolng`=36.4

Si vous avez un index séparé par colonne, les trois requêtes utilisent des index. Dans MySQL, si vous avez un index composite (geolat, geolng), seules la requête I et la requête II (qui utilise la première partie de l'index composite) utilisent des index. Dans ce cas, la requête III requiert une recherche de table complète.

Dans la section Index de plusieurs colonnes du manuel, le fonctionnement de plusieurs index de colonne est clairement expliqué. Par conséquent, je ne souhaite pas retaper manuellement.

De la page du manuel de référence de MySQL :

Un index à plusieurs colonnes peut être considéré comme un tableau trié contenant des valeurs créées en concaténant les valeurs des colonnes indexées .

Si vous utilisez des index séparés pour les colonnes geolat et geolng, vous avez deux index différents dans votre table dans lesquels vous pouvez effectuer une recherche indépendante.

INDEX geolat
-----------
VALUE RRN
36.4  1
36.4  8
36.6  2
37.8  3
37.8  12
41.4  4

INDEX geolng
-----------
VALUE RRN
26.1  1
26.1  8
29.6  2
29.6  3
30.1  12
34.7  4

Si vous utilisez un index composite, vous ne disposez que d'un seul index pour les deux colonnes:

INDEX (geolat, geolng)
-----------
VALUE      RRN
36.4,26.1  1
36.4,26.1  8
36.6,29.6  2
37.8,29.6  3
37.8,30.1  12
41.4,34.7  4

RRN est le numéro d'enregistrement relatif (pour simplifier, vous pouvez dire ID). Les deux premiers index sont séparés et le troisième est composite. Comme vous pouvez le constater, vous pouvez effectuer une recherche basée sur geolng sur composite car elle est indexée par geolat. Il est toutefois possible d'effectuer une recherche par geolat ou "geolat AND geolng" (puisque geolng est un index de second niveau).

Regardez aussi Comment MySQL utilise les index section du manuel.

48
Emre Yazici

Il pourrait y avoir une idée fausse sur ce que fait l'indice composite. Beaucoup de gens pensent que l'index composite peut être utilisé pour optimiser une requête de recherche tant que la clause where couvre les colonnes indexées, dans votre cas geolat et geolng. Approfondissons:

Je crois que vos données sur les coordonnées des maisons seraient des nombres décimaux aléatoires en tant que tels:

home_id  geolat  geolng
   1    20.1243  50.4521
   2    22.6456  51.1564
   3    13.5464  45.4562
   4    55.5642 166.5756
   5    24.2624  27.4564
   6    62.1564  24.2542
...

Depuis geolat et geolng les valeurs se répètent à peine. Un index composite sur geolat et geolng ressemblerait à ceci:

index_id  geolat  geolng
   1     20.1243  50.4521
   2     20.1244  61.1564
   3     20.1251  55.4562
   4     20.1293  66.5756
   5     20.1302  57.4564
   6     20.1311  54.2542
...

Par conséquent, la deuxième colonne de l'index composite est fondamentalement inutile ! La vitesse de votre requête avec un index composite sera probablement similaire à celle d’un index de la colonne geolat.

Comme mentionné par Will, MySQL fournit le support extension spatiale . Un point spatial est stocké dans une seule colonne au lieu de deux colonnes latlng distinctes. L'index spatial peut être appliqué à une telle colonne. Cependant, l'efficacité pourrait être surestimée en fonction de mon expérience personnelle. Il se peut que l’index spatial ne résolve pas le problème à deux dimensions mais accélère simplement la recherche en utilisant R-Trees avec division quadratique.

Le compromis est qu'un point spatial consomme beaucoup plus de mémoire car il utilisait des nombres à double précision de huit octets pour stocker les coordonnées. Corrigez-moi si je me trompe.

18
Question Overflow

Les indices composites sont utiles pour

  • 0 ou plusieurs clauses "=", plus
  • au plus un clause de plage.

Un index composite ne peut pas gérer deux plages. J'en discute plus avant dans mon index cookbook .

Trouver le plus proche - Si la question est vraiment sur l'optimisation

WHERE geolat BETWEEN ??? AND ???
  AND geolng BETWEEN ??? AND ???

then no index peut vraiment gérer les deux dimensions.

Au lieu de cela, il faut "sortir des sentiers battus". Si une dimension est implémentée via un partitionnement et que l’autre l’est en sélectionnant soigneusement le PRIMARY KEY, on peut obtenir une efficacité nettement meilleure pour les très grandes tables de recherche lat/long. Mon blogueur ) explique en détail comment implémenter "trouver le plus proche" sur le globe. Il comprend du code.

Les PARTITIONs sont des bandes de plages de latitude. Le PRIMARY KEY commence délibérément par la longitude, de sorte que les lignes utiles se trouvent probablement dans le même bloc. Une routine stockée orchestre le code en désordre pour faire order by... limit... et pour agrandir le carré autour de la cible jusqu'à ce que vous ayez suffisamment de cafés (ou autre). Il prend également en charge les calculs du grand cercle et la manipulation de la ligne de dates et des pôles.

6
Rick James

Les index composites sont très puissants car ils:

  • Appliquer l'intégrité de la structure
  • Activer le tri sur un identifiant FILTERED

APPLIQUER L'INTÉGRITÉ DE LA STRUCTURE

Les index composites ne sont pas simplement un autre type d'index; ils peuvent fournir la structure NECESSARY à une table en appliquant l'intégrité en tant que clé primaire.

Mysql Innodb prend en charge la mise en cluster et l'exemple suivant montre pourquoi un index composite peut être nécessaire.

Pour créer une table d'amis (c'est-à-dire pour un réseau social), nous avons besoin de 2 colonnes: user_id, friend_id.

Strcture de table

user_id (medium_int)
friend_id (medium_int)

Primary Key -> (user_id, friend_id)

En vertu de la clé primaire, une clé primaire est unique. En créant une clé composite, Innodb vérifie automatiquement qu’il n’ya pas de doublons sur user_id, friend_id existe lorsqu'un nouvel enregistrement est ajouté. Ceci est le comportement attendu car aucun utilisateur ne devrait avoir plus d'un enregistrement (lien de relation) avec friend_id = 2 par exemple.

Sans PK composite, nous pouvons créer ce schéma en utilisant une clé de substitution:

user_friend_id
user_id
friend_id

Primary Key -> (user_friend_id)

Maintenant, chaque fois qu'un nouvel enregistrement est ajouté, nous devrons vérifier qu'un enregistrement précédent avec la combinaison user_id, friend_id n'existe pas déjà.

En tant que tel, un index composite peut imposer l'intégrité de la structure.

Activer le tri sur une ID filtrée

Il est très courant de trier un ensemble d'enregistrements en fonction de l'heure de la publication (horodatage ou date/heure). Habituellement, cela signifie publier sur un identifiant donné. Voici un exemple

Table User_Wall_Posts (pensez si les publications sur le mur de Facebook)

user_id (medium_int)
timestamp (timestamp)
author_id (medium_int)
comment_post (text)

Primary Key -> (user_id, timestamp, author_id)

Nous voulons interroger et trouver tous les articles pour user_id = 10 et triez les commentaires de commentaires par timestamp (date).

QUERY SQL

SELECT * FROM User_Wall_Posts WHERE user_id = 10 ORDER BY timestamp DES

La PK composite permet à Mysql de filtrer et de trier les résultats à l'aide de l'index. Mysql n'aura pas à utiliser un fichier temporaire ou un portage de fichiers pour récupérer les résultats. Sans une clé composite, cela ne serait pas possible et causerait une requête très inefficace.

En tant que telles, les touches composites sont très puissantes et conviennent plus au simple problème de "Je veux rechercher column_a, column_b donc je vais utiliser des clés composites. Pour mon schéma de base de données actuel, j'ai autant de clés composites que de clés simples. Ne négligez pas l'utilisation d'une clé composite!

5
ProfileTwist

L'index composite peut être utile lorsque vous souhaitez optimiser group by clause (consultez cet article http://dev.mysql.com/doc/refman/5.0/fr/group-by-optimization.html ). Votre attention s'il vous plaît:

Les conditions préalables les plus importantes pour l'utilisation des index pour GROUP BY sont que toutes les colonnes GROUP BY référencent des attributs du même index et que l'index stocke ses clés dans l'ordre (par exemple, il s'agit d'un index BTREE et non d'un index HASH).

1
Alexander

Pour faire des recherches spatiales, vous avez besoin d'un algorithme R-Tree , qui permet de rechercher très rapidement des zones géographiques. Exactement ce dont vous avez besoin pour ce travail.

Certaines bases de données ont des index spatiaux intégrés. Une recherche rapide dans Google indique que MySQL 5 les possède (ce qui, vu votre code SQL, suppose que vous utilisez MySQL).

1
Will

Il n'y a pas de noir et blanc, une réponse unique.

Vous devez utiliser un index composite, lorsque votre charge de travail de requête en bénéficierait.

Vous devez déterminer votre charge de travail de la requête afin de déterminer votre charge de travail.

Un index composite entre en jeu lorsque les requêtes peuvent être entièrement satisfaites à partir de cet index.

UPDATE (en réponse à la question de modification en publication): Si vous sélectionnez * dans la table, l'index composite peut être utilisé, mais ce n'est pas le cas. Vous devrez exécuter EXPLAIN PLAN pour en être sûr.

1
Mitch Wheat

Je suis avec @Mitch, tout dépend de vos questions. Heureusement, vous pouvez créer et supprimer des index à tout moment, et vous pouvez ajouter le mot clé EXPLAIN à vos requêtes pour voir si l'analyseur de requête utilise les index.

Si vous recherchez une paire exacte lat/long, cet indice aura probablement un sens. Mais vous allez probablement chercher des maisons à une certaine distance d'un lieu particulier. Vos requêtes ressembleront à ceci (voir source ):

select *, sqrt(  pow(h2.geolat - h1.geolat,  2) 
               + pow(h2.geolng - h1.geolng, 2) ) as distance
from homes h1, homes h2
where h1.home_id = 12345 and h2.home_id != h1.home_id
order by distance

et l'index ne sera très probablement pas utile du tout. Pour les requêtes géospatiales, vous avez besoin de quelque chose comme this .

Mise à jour: avec cette requête:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

L'analyseur de requête peut utiliser un index sur geolat seul ou un index sur geolng seul, ou éventuellement les deux index. Je ne pense pas qu'il utiliserait un index composite. Mais il est facile d'essayer chacune de ces permutations sur un ensemble de données réel, puis (a) de voir ce que EXPLAIN vous dit et (b) de mesurer le temps que prend réellement la requête.

0
Jim Ferrans