Dois-je indexer un champ de bits dans SQL Server?


99

Je me souviens avoir lu à un moment donné qu'indexer un champ avec une faible cardinalité (un faible nombre de valeurs distinctes) ne valait pas vraiment la peine d'être fait. J'avoue que je ne sais pas assez sur le fonctionnement des index pour comprendre pourquoi.

Alors, que faire si j'ai une table avec 100 millions de lignes et que je sélectionne des enregistrements où un champ de bits est 1? Et disons qu'à tout moment, il n'y a qu'une poignée d'enregistrements où le champ de bits est 1 (par opposition à 0). Vaut-il la peine d'indexer ce champ de bits ou non? Pourquoi?

Bien sûr, je peux simplement le tester et vérifier le plan d'exécution, et je le ferai, mais je suis également curieux de connaître la théorie derrière cela. Quand la cardinalité importe-t-elle et quand ne l'est-elle pas?


Est-ce une question courante? Cela peut en valoir la peine lorsque vous recherchez la «poignée» d'enregistrements, mais ne vous aidera pas beaucoup sur les autres lignes. Existe-t-il d'autres moyens d'identifier les données?
jason saldo

4
Bien que je ne pense pas que j'indexerais JUSTE une colonne de bits par elle-même, il est très courant d'inclure des colonnes de bits dans le cadre d'un index composé. Un exemple simple serait un index sur ACTIVE, LASTNAME au lieu de simplement le nom de famille, lorsque votre application recherche presque toujours des clients actifs.
BradC

"Je me souviens avoir lu à un moment donné que l'indexation d'un champ avec une faible cardinalité (un faible nombre de valeurs distinctes) ne valait pas vraiment la peine d'être fait. indice. Donc, fondamentalement, votre index ne sera jamais utilisé et c'est un gaspillage de le maintenir. Comme d'autres l'ont dit, cela pourrait être correct dans un index composé.
DJ.

5
Je ne serais pas d’accord. Si votre distribution est 50/50, vous n'utiliserez jamais l'index, car il serait juste plus rapide de faire une analyse de table. Cependant, si vous n'avez que 5, 1 valeurs et 1 million de valeurs 0, il est très probable que vous utilisiez l'index lors de la recherche de 1.
Kibbee

1
Dans l'exemple que vous avez donné, je serais plus enclin à mettre LastName en premier. Cela dépend de la charge de travail de requête spécifique, mais en général, avoir la colonne la plus sélective en premier signifie que l'index est plus susceptible d'être utilisé.
Mitch Wheat

Réponses:


72

Considérez ce qu'est un index en SQL - et l'index est en fait un morceau de mémoire pointant vers d'autres morceaux de mémoire (c'est-à-dire des pointeurs vers des lignes). L'index est divisé en pages afin que des parties de l'index puissent être chargées et déchargées de la mémoire en fonction de l'utilisation.

Lorsque vous demandez un ensemble de lignes, SQL utilise l'index pour trouver les lignes plus rapidement que l'analyse de table (en regardant chaque ligne).

SQL a des index clusterisés et non clusterisés. Ma compréhension des index clusterisés est qu'ils regroupent des valeurs d'index similaires dans la même page. De cette façon, lorsque vous demandez toutes les lignes correspondant à une valeur d'index, SQL peut renvoyer ces lignes à partir d'une page de mémoire en cluster. C'est pourquoi essayer d'indexer une colonne GUID en cluster est une mauvaise idée - vous n'essayez pas de regrouper des valeurs aléatoires.

Lorsque vous indexez une colonne entière, l'index SQL contient un ensemble de lignes pour chaque valeur d'index. Si vous avez une plage de 1 à 10, vous auriez 10 pointeurs d'index. En fonction du nombre de lignes, cela peut être paginé différemment. Si votre requête recherche l'index correspondant à «1» et que le nom contient «Fred» (en supposant que la colonne Nom n'est pas indexée), SQL obtient très rapidement l'ensemble des lignes correspondant à «1», puis la table analyse pour trouver le reste.

Donc, ce que SQL fait vraiment, c'est essayer de réduire l'ensemble de travail (nombre de lignes) sur lequel il doit itérer.

Lorsque vous indexez un champ de bits (ou une plage étroite), vous ne réduisez le jeu de travail que du nombre de lignes correspondant à cette valeur. Si vous avez un petit nombre de lignes correspondantes, cela réduirait considérablement votre jeu de travail. Pour un grand nombre de lignes avec une distribution 50/50, cela peut vous offrir très peu de gain de performances par rapport à la mise à jour de l'index.

La raison pour laquelle tout le monde dit de tester est que SQL contient un optimiseur très intelligent et complexe qui peut ignorer un index s'il décide que l'analyse de table est plus rapide, ou peut utiliser un tri, ou peut organiser les pages de mémoire comme il le souhaite.


Donc, il semble que si je n'ai qu'une poignée de lignes où le champ de bits est 1 (par exemple en gardant la trace de "IsProcessed"), alors un index serait bon car il les classera par valeur et pourra ensuite sélectionner le petit ensemble de travail très rapidement. Si vous êtes d'accord, ajoutez-le et je l'accepterai.
jeremcc

2
Ce que je veux dire dans mon commentaire précédent, c'est que cette déclaration: "Lorsque vous indexez un champ de bits (ou une plage étroite), vous ne réduisez que de moitié le jeu de travail" n'est pas vraie si la distribution est fortement pondérée vers une valeur. Mais j'aime le reste de votre réponse, donc si vous corrigez cela, je l'accepterai.
jeremcc

1
Terminé. Je pensais que pour un million de lignes, un petit champ aurait une distribution de 50%, mais vous avez raison de dire que pour un espace de problème particulier, cela pourrait réduire considérablement le jeu de travail.
Geoff Cox

Il vaut la peine d'examiner les plans d'exécution avec et sans l'index, et voir si l'index est utilisé et s'il réduit réellement le coût de vos requêtes. Facile et scientifique!
onupdatecascade

Qu'en est-il de l'indexation d'un petit champ + un autre champ? Par exemple. dans un journal d'activité Web, on indexerait l'horodatage, mais un autre index utile pourrait être sur un champ de bits "IsHTTPS" + horodatage, pour afficher rapidement toutes les actions https. Serait-ce également inefficace?
ingrédient_15939

19

Je viens de rencontrer cette question par le biais d'une autre. En supposant que votre déclaration selon laquelle seule une poignée d'enregistrements prend la valeur de 1 (et que ce sont ceux qui vous intéressent), un index filtré pourrait être un bon choix. Quelque chose comme:

create index [IX_foobar] on dbo.Foobar (FooID) where yourBitColumn = 1

Cela créera un index nettement plus petit que l'optimiseur est suffisamment intelligent pour utiliser lorsqu'il s'agit d'un prédicat dans votre requête.


1
Il convient de noter que le prédicat de la requête doit être codé en dur sur la valeur de l'index filtré. Si vous transmettez la valeur dans un paramètre yourBitColumn = @value, l'optimiseur ne peut pas déterminer si l'index filtré est utilisable.
geofftnz

2
Il existe des moyens de contourner cela, mais vous avez raison; L'optimiseur a besoin d'une garantie au moment de la compilation que les valeurs de tout prédicat correspondant au prédicat d'index filtré sont statiques / invariantes puisque c'est le travail de l'optimiseur de créer un plan général qui fonctionnera pour n'importe quel ensemble de paramètres.
Ben Thul

9

100 millions d'enregistrements avec seulement quelques-uns ayant le champ de bits mis à 1? Oui, je pense que l'indexation du champ de bits accélérerait certainement l'interrogation des enregistrements bit = 1. Vous devriez obtenir le temps de recherche logarithmique à partir de l'index, puis ne toucher que les quelques pages avec des enregistrements bit = 1. Sinon, vous devrez toucher toutes les pages du tableau des 100 millions d'enregistrements.

Là encore, je ne suis certainement pas un expert en bases de données et je pourrais manquer quelque chose d'important.


8

Si votre distribution est assez connue et déséquilibrée, comme 99% des lignes sont bit = 1 et les 1% sont bit = 0, lorsque vous effectuez une clause WHERE avec bit = 1, une analyse complète de la table sera à peu près au même moment que l'analyse d'index. Si vous voulez avoir une requête rapide où bit = 0, le meilleur moyen que je connaisse est de créer un index filtré, en ajoutant une clause WHERE bit = 0. De cette façon, cet index ne stockera que la ligne 1%. Ensuite, faire un WHERE bit = 0 laissera simplement l'optimiseur de requête choisir cet index, et toutes les lignes de celui-ci seront bit = 0. Vous avez également l'avantage d'avoir une très petite quantité d'espace disque nécessaire pour comparer un index complet sur le bit .


2
Si 99% des lignes sont bit = 1, l'optimiseur doit ignorer l'index et effectuer une analyse de table. L'utilisation de l'index sera en fait pire qu'une analyse de table, au moins sur un lecteur rotatif, plus d'E / S et des lectures non consécutives à partir du disque. L'index filtré (équivalent Postgres: index partiel) est la voie à suivre. Je suppose que parce que c'est des années après la question, cette réponse n'a pas obtenu les votes qu'elle méritait.
Andrew Lazarus

7

Bien que je ne pense pas que j'indexerais JUSTE une colonne de bits par elle-même, il est très courant d'inclure des colonnes de bits dans le cadre d'un index composé.

Un exemple simple serait un index sur ACTIVE, LASTNAME au lieu de simplement le nom de famille, lorsque votre application recherche presque toujours des clients actifs.


7
Dans l'exemple que vous avez donné, je serais plus enclin à mettre LastName en premier. Cela dépend de la charge de travail de requête spécifique, mais en général, avoir la colonne la plus sélective en premier signifie que l'index est plus susceptible d'être utilisé.
Mitch Wheat

7

cet article n'est plus visible
Homer6

@ Homer6 J'ai ajouté un lien vers ce à quoi ressemble la nouvelle maison pour cet article.
Jeff

Nouveau lien vers la page d'accueil de Toad World.
N West

J'ai trouvé l'article en utilisant la machine Wayback et j'ai trouvé un nouvel article connexe. J'espère que cela t'aides.
Jeff

2

Bien sûr, cela en vaut la peine, surtout si vous devez récupérer les données par cette valeur. Ce serait similaire à l'utilisation d'une matrice creuse au lieu d'utiliser une matrice normale.

Désormais, avec SQL 2008, vous pouvez utiliser des fonctions de partitionnement et vous pouvez filtrer les données qui vont dans un index. L'inconvénient des versions antérieures serait que l'index serait créé pour toutes les données, mais cela peut être optimisé en stockant les valeurs intéressantes dans un groupe de fichiers séparé.


2

Comme d'autres l'ont dit, vous voudrez mesurer cela. Je ne me souviens pas où j'ai lu ceci, mais une colonne doit avoir une cardinalité très élevée (environ 95%) pour qu'un index soit efficace. Votre meilleur test pour cela serait de créer l'index et d'examiner les plans d'exécution pour les valeurs 0 et 1 du champ BIT. Si vous voyez une opération de recherche d'index dans le plan d'exécution, vous savez que votre index sera utilisé.

Votre meilleur plan d'action serait de tester le avec une table SELECT * FROM de base WHERE BitField = 1; et développez lentement les fonctionnalités à partir de là, étape par étape, jusqu'à ce que vous ayez une requête réaliste pour votre application, en examinant le plan d'exécution à chaque étape pour vous assurer que la recherche d'index est toujours utilisée. Certes, il n'y a aucune garantie que ce plan d'exécution sera utilisé en production, mais il y a de fortes chances qu'il le soit.

Certaines informations sont disponibles sur les forums sql-server-performance.com et dans l' article référencé


Ce n'est pas tant la cardinalité de la colonne dans son ensemble qui compte. C'est la sélectivité de la clause WHERE. Donc, s'il y a peu de colonnes avec la valeur 1, il peut toujours être bon d'indexer. Si c'est 50/50 (par exemple homme / femme), cela n'en vaut pas la peine.
WW.

2

"Je me souviens avoir lu à un moment donné qu'indexer un champ avec une faible cardinalité (un faible nombre de valeurs distinctes) ne valait pas vraiment la peine d'être fait"

En effet, SQL Server trouvera presque toujours qu'il est plus efficace de simplement faire une analyse de table que de lire l'index. Donc, fondamentalement, votre index ne sera jamais utilisé et c'est un gaspillage de le maintenir. Comme d'autres l'ont dit, cela pourrait être correct dans un index composé.


2

Si votre objectif est de rechercher plus rapidement les enregistrements où la valeur du champ de bits est égale à «1», vous pouvez essayer une vue indexée de votre table de base qui ne contient que les enregistrements où votre champ de bits est égal à «1». Dans l'édition Entreprise, si une requête pouvait utiliser une vue indexée au lieu d'une table spécifiée pour améliorer les performances de la requête, elle utilisera la vue. En théorie, cela augmenterait la vitesse des requêtes de sélection qui ne recherchent que les enregistrements avec une valeur de champ de bits de «1».

http://www.microsoft.com/technet/prodtechnol/sql/2005/impprfiv.mspx

Tout cela suppose que vous êtes Microsoft SQL Server 2005 Enterprise. La même chose pourrait s'appliquer à 2008, je ne connais pas cette version.


2

Si vous voulez savoir si un index a les effets que vous désirez: testez et testez à nouveau.

En général, vous ne voulez pas d'un index qui ne restreigne pas suffisamment votre table, en raison du coût de maintenance d'un index. (coût> profit). Mais si l'index dans votre cas réduit la table de moitié, vous pouvez gagner quelque chose, mais en le mettant sur la table. Tout dépend de la taille / structure exacte de votre table et de la manière dont vous l'utilisez (nombre de lectures / écritures).


1

En soi, non car il en résulte très peu de sélectivité. Dans le cadre d'un index composé. très probablement, mais seulement après d'autres colonnes d'égalité.


1

Vous ne pouvez pas indexer un champ de bits dans SQL Server 2000, comme indiqué dans la documentation en ligne à l'époque:

bit

Type de données entier 1, 0 ou NULL.

Remarques

Les colonnes de type bit ne peuvent pas avoir d'index sur elles.

Oui, si vous n'avez qu'une poignée de lignes, sur des millions, un index vous aidera. Mais si vous voulez le faire dans ce cas, vous devez rendre la colonne a tinyint.

Remarque : Enterprise Manager ne vous permet pas de créer un index sur une colonne de bits. Si vous le souhaitez, vous pouvez toujours créer manuellement un index sur une colonne de bits:

CREATE INDEX IX_Users_IsActiveUsername ON Users
(
   IsActive,
   Username
)

Mais SQL Server 2000 n'utilisera pas réellement un tel index - exécutant une requête où l'index serait un candidat parfait, par exemple:

SELECT TOP 1 Username 
FROM Users
WHERE IsActive = 0

SQL Server 2000 effectuera une analyse de table à la place, agissant comme si l'index n'existait même pas. Si vous changez la colonne à un tinyint SQL Server 2000 va faire une recherche d' index. En outre, la requête non couverte suivante:

SELECT TOP 1 * 
FROM Users
WHERE IsActive = 0

Il effectuera une recherche d'index, suivie d'une recherche de signet.


SQL Server 2005 a une prise en charge limitée des index sur les colonnes de bits. Par exemple:

SELECT TOP 1 Username 
FROM Users
WHERE IsActive = 0

provoquera une recherche d'index dans l'index de couverture. Mais le cas non couvert:

SELECT TOP 1 * 
FROM Users
WHERE IsActive = 0

ne provoquera pas une recherche d'index suivie d'une recherche de signets, il effectuera une analyse de table (ou une analyse d'index groupé), plutôt que d'effectuer la recherche d'index suivie d'une recherche de signets.

Vérifié par expérimentation et observation directe.


FYI - SQL Server 2005 Management Studio vous permet de le faire.
jeremcc

Ma copie de SQL Server 2000 m'a permis de définir un index sur une colonne de bits.
Kibbee

Ma copie de SQL Server 2000 ne me permet pas de définir un index sur une colonne de bits.
Ian Boyd

1

réponse très tardive ...

Oui, cela peut être utile selon l'équipe SQL CAT (mis à jour, a été consolidé)


1
Le lien semble être mort maintenant. Cependant, ce message semble avoir été consolidé avec plusieurs autres dans un livre électronique . La section référencée commence à la page 86. Le livre électronique peut être téléchargé à partir des eBooks de SQLCAT.com sous le lien «Guide de SQLCAT sur le moteur relationnel».
mwolfe02

0

Est-ce une question courante? Cela peut en valoir la peine lorsque vous recherchez la "poignée" d'enregistrements mais ne vous aidera pas beaucoup sur les autres lignes. Existe-t-il d'autres moyens d'identifier les données?


0

La cardinalité est un facteur, l'autre est la façon dont l'index divise vos données. Si vous avez environ la moitié des 1 et la moitié des 0, cela vous aidera. (En supposant que cet index est un meilleur chemin à choisir que tout autre index). Cependant, à quelle fréquence insérez-vous et mettez-vous à jour? L'ajout d'index pour les performances SELECT nuit également aux performances INSERT, UPDATE et DELETE, alors gardez cela à l'esprit.

Je dirais que si les 1 à 0 (ou vice versa) ne sont pas meilleurs que 75% à 25%, ne vous inquiétez pas.


1
Je ne serais pas d’accord. Si votre distribution est 50/50, vous n'utiliserez jamais l'index, car il serait juste plus rapide de faire une analyse de table. Cependant, si vous n'avez que 5, 1 valeurs et 1 million de valeurs 0, il est très probable que vous utilisiez l'index lors de la recherche de 1.
Kibbee

0

mesurer le temps de réponse avant et après et voir s'il en vaut la peine; théoriquement, cela devrait améliorer les performances des requêtes utilisant les champs indexés, mais cela dépend vraiment de la distribution des valeurs vrai / faux et des autres champs impliqués dans les requêtes qui vous préoccupent


0

Ian Boyd a raison quand il dit que vous ne pouviez pas le faire via Enterprise Manager pour SQL 2000 (voir sa note concernant sa création via T-SQL.


0

Vous devez être intelligent ici pour interroger, vous devez connaître la valeur de charge sur votre colonne si la charge de true est plus dans votre système et que vous voulez vérifier toutes les vraies valeurs écrivez votre requête pour vérifier non faux .. cela aidera beaucoup , c'est juste un truc.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.