Quelle est l'architecture d'index appropriée lorsqu'elle est forcée d'implémenter IsDeleted (suppressions en douceur)?

16

Actuellement, nous avons une base de données et une application existantes qui sont entièrement fonctionnelles. Je n'ai pas la possibilité de changer l'architecture à ce stade. Aujourd'hui, chaque table de la base de données possède un champ "IsDeleted" NOT NULL BIT avec une valeur par défaut de "0". Lorsque l'application «supprime» les données, elle met simplement à jour l'indicateur IsDeleted à 1.

Ce que j'ai du mal à comprendre, c'est comment les index de chacune des tables doivent être structurés. À l'heure actuelle, chaque requête / jointure / etc implémente toujours la vérification IsDeleted. C'est une norme que nos développeurs doivent suivre. Cela étant dit, j'essaie de déterminer si tous mes index de clé primaire en cluster sur chacune des tables doivent être modifiés pour inclure la clé primaire ET le champ IsDeleted BIT. De plus, puisque CHAQUE requête / jointure / etc. doit implémenter la vérification IsDeleted, est-ce une hypothèse appropriée que CHAQUE index SINGLE (non cluster également) devrait inclure le champ IsDeleted comme premier champ de l'index?

Une autre question que je me pose concerne les index filtrés. Je comprends que je pourrais mettre des filtres sur les index tels que "WHERE IsDeleted = 0" pour réduire la taille des index. Cependant, comme chaque jointure / requête devra implémenter la vérification IsDeleted, cela empêcherait-il l'utilisation de l'index filtré (puisque la colonne IsDeleted est utilisée dans la jointure / requête)?

N'oubliez pas que je n'ai pas la possibilité de modifier l'approche IsDeleted.

Philᵀᴹ
la source

Réponses:

13

L'approche la plus simple ici consiste à laisser vos clés et index clusterisés seuls et à utiliser des index filtrés pour vos index non clusterisés.

De plus, vous pouvez migrer certaines grandes tables vers des segments de partition ou des magasins de colonnes en cluster partitionnés (SQL Server 2016+), en laissant la clé primaire et les index uniques non partitionnés. Cela vous permettrait de pousser les colonnes non clés des lignes IsDeleted vers une structure de données distincte, qui pourrait en outre être compressée différemment ou stockée sur un groupe de fichiers différent.

Et assurez-vous que les développeurs utilisent un littéral au lieu d'un paramètre pour filtrer les lignes IsDeleted. Avec un paramètre, SQL Server doit utiliser le même plan de requête pour les deux cas.

PAR EXEMPLE

SELECT ... WHERE ... AND IsDeleted=0

Et pas:

SELECT ... WHERE ... AND IsDeleted=@IsDeleted

L'utilisation d'un paramètre empêchera l'utilisation de l'index filtré et peut vous causer des problèmes avec le reniflage des paramètres.

David Browne - Microsoft
la source
Compte tenu de l'omniprésence et de l'importance de la IsDeletedcolonne, quel que soit le stockage physique, il serait probablement judicieux d'exposer les données à travers deux vues (éventuellement dans des schémas différents), résolvant à la fois le problème de paramétrage et faisant des erreurs avec l'accès aux données qui n'auraient pas dû être accès moins probable. L'accès aux données de base n'est pertinent que dans les rares cas où les données supprimées et non supprimées doivent être combinées d'une manière ou d'une autre, et lorsque les lignes doivent réellement être commutées sur "supprimées".
Jeroen Mostert
@JeroenMostert bons conseils. RLS peut également être utilisé ici, ou quelque chose comme EF Core Global Query Filters. docs.microsoft.com/en-us/ef/core/querying/filters
David Browne - Microsoft
9

C'est peut-être une opinion impopulaire, mais je ne pense pas qu'il y ait une réponse "à faire partout" / taille unique à votre question.

Si vous avez des requêtes qui analysent de nombreuses lignes IsDeleted sans raison, une solution consiste à créer un index filtré et non cluster pour satisfaire cette requête.

Une autre option consiste à créer une vue indexée qui pourrait être exploitée par un certain nombre de requêtes différentes, qui est filtrée uniquement sur les lignes non supprimées. Cela pourrait être particulièrement utile sur Enterprise Edition, où la correspondance automatique des vues indexées fonctionne sans fournir d' NOEXPANDindice.

Pour les petites tables, ou les tables qui sont lues intensivement, l'ajout d'index ou de vues non clusterisés filtrés ou quoi que ce soit pourrait vraiment simplement ajouter une surcharge inutile à votre base de données.

Josh Darnell
la source
2

Dans l'hypothèse raisonnable que les suppressions sont rares, aucune modification des indices n'est une solution appropriée.

J'ai constaté que tôt ou tard, il faut rechercher des références aux lignes supprimées, et les lignes se trouvant dans les index en valent soudainement la peine.

Veuillez noter qu'à moins que vous n'utilisiez des vues, vous devez de toute façon modifier toutes vos requêtes pour inclure les filtres.

Joshua
la source
0

J'ai vu un système où le drapeau IS_DELETED est 0 ou la valeur du PK. Dans d'autres systèmes, c'était le négatif du PK.

Étant donné que la plupart des requêtes ont récupéré des valeurs par la clé "naturelle" ou commerciale (parfois multi-champs), elles n'ont jamais interrogé PK sauf par le biais de jointures; mais ils ont toujours ajouté un AND IS_DELETED = 0 à la fin pour la table principale et pour toutes les tables jointes.

Ce système avait également une table d'audit pour chaque table transactionnelle qui suivait les changements; et l'application avait une fonctionnalité pour afficher toutes les modifications de données, y compris les données supprimées.

Rick Ryker
la source
0

J'espère que vous avez le droit et la possibilité de modifier la requête.

Cependant, comme chaque jointure / requête devra implémenter la vérification IsDeleted, cela empêcherait-il l'utilisation de l'index filtré (puisque la colonne IsDeleted est utilisée dans la jointure / requête)?

Je voulais dire un point important, j'espère pouvoir l'expliquer.

Dans une requête complexe, où Transaction tableet Mastertables sont utilisés.

Utiliser IsDeleted=0uniquement dans le Transactiontableau. Ne pas utiliser dans le Mastertableau.

Exemple,

Select * from dbo.Order O
inner join dbo.category C on o.categoryid=o.categoryid
inner join dbo.Product P on P.Productid=o.Productid
where o.isdeleted=0

Cela ne sert à rien c.isdeleted=0(d'utiliser dans le Categorytableau), c'est inutile.

De même, est-il utile d'utiliser P.isdeleted=0?

Parce que je veux tous les Ordres non supprimés et leurs détails.

Comment peut Productêtre supprimé quand Orderest Activeou où Productidest la référence.

Donc, de cette façon, si vous déboguez soigneusement dans une requête importante, vous pouvez supprimer certains isdeleted = 0.

Ne créez pas aveuglément un index filtré, sélectionnez d'abord toutes ces requêtes très importantes et lentes.

Optimisez ces requêtes lentes, puis décidez uniquement de l'index filtré ou de l'index de réglage.

KumarHarsh
la source