Was ist die geeignete Indexarchitektur, wenn IsDeleted (soft deletes) implementiert werden muss?

16

Derzeit verfügen wir über eine Datenbank und eine Anwendung, die voll funktionsfähig sind. Ich habe nicht die Möglichkeit, die Architektur zu diesem Zeitpunkt zu ändern. Heute hat jede Tabelle in der Datenbank ein Feld "IsDeleted" NOT NULL BIT mit dem Standardwert "0". Wenn die Anwendung Daten "löscht", aktualisiert sie einfach das IsDeleted-Flag auf 1.

Ich habe Probleme zu verstehen, wie die Indizes für jede der Tabellen strukturiert sein sollten. Im Moment implementiert jede Abfrage / Verknüpfung / etc immer die IsDeleted-Prüfung. Es ist ein Standard, dem unsere Entwickler folgen müssen. Davon abgesehen versuche ich festzustellen, ob alle meine gruppierten Primärschlüsselindizes für jede der Tabellen geändert werden müssen, um den Primärschlüssel UND das Feld IsDeleted BIT einzuschließen. Da JEDE Abfrage / Verknüpfung / etc. Muss die IsDeleted-Prüfung implementiert werden? Ist es eine angemessene Annahme, dass JEDER EINZELNE Index (auch nicht geclustert) das IsDeleted-Feld als erstes Feld des Index enthalten sollte?

Eine andere Frage, die ich habe, betrifft gefilterte Indizes. Ich verstehe, dass ich Filter auf die Indizes wie "WHERE IsDeleted = 0" setzen könnte, um die Größe der Indizes zu reduzieren. Würde dies jedoch die Verwendung des gefilterten Index verhindern, da jeder Join / jede Abfrage die IsDeleted-Prüfung implementieren muss (da die IsDeleted-Spalte in Join / Abfrage verwendet wird)?

Denken Sie daran, dass ich den IsDeleted-Ansatz nicht ändern kann.

Philᵀᴹ
quelle

Antworten:

13

Der einfachste Ansatz besteht darin, Ihre Schlüssel und Clustered-Indizes in Ruhe zu lassen und gefilterte Indizes für Ihre Nicht-Clustered-Indizes zu verwenden.

Darüber hinaus können Sie einige große Tabellen in partitionierte Heaps oder partitionierte Clustered-Columnstores (SQL Server 2016+) migrieren, wobei der Primärschlüssel und die eindeutigen Indizes nicht partitioniert bleiben. Auf diese Weise können Sie die Nicht-Schlüsselspalten für IsDeleted-Zeilen in eine separate Datenstruktur verschieben, die zusätzlich unterschiedlich komprimiert oder in einer anderen Dateigruppe gespeichert werden kann.

Und stellen Sie sicher, dass die Entwickler ein Literal anstelle eines Parameters verwenden, um die IsDeleted-Zeilen herauszufiltern. Mit einem Parameter muss SQL Server für beide Fälle denselben Abfrageplan verwenden.

Z.B

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

Und nicht:

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

Die Verwendung eines Parameters verhindert die Verwendung eines gefilterten Index und kann zu Problemen beim Parameter-Sniffing führen.

David Browne - Microsoft
quelle
Angesichts der Allgegenwart und Wichtigkeit der IsDeletedSpalte ist es unabhängig vom physischen Speicher wahrscheinlich sinnvoll, die Daten in zwei Ansichten (optional in verschiedenen Schemata) bereitzustellen, um sowohl das Parametrisierungsproblem zu lösen als auch Fehler beim Zugriff auf Daten zu machen, die nicht hätten sein dürfen weniger wahrscheinlich zugegriffen. Der Zugriff auf die Basisdaten ist nur in den seltenen Fällen relevant, in denen gelöschte und nicht gelöschte Daten kombiniert werden müssen und wenn die Zeilen tatsächlich auf "gelöscht" geschaltet werden müssen.
Jeroen Mostert
@JeroenMostert guten Rat. Hier kann auch RLS oder so etwas wie EF Core Global Query Filter verwendet werden. docs.microsoft.com/en-us/ef/core/querying/filters
David Browne - Microsoft
9

Dies mag eine unpopuläre Meinung sein, aber ich glaube nicht, dass es ein "überall machen" gibt / eine einheitliche Antwort auf Ihre Frage.

Wenn Sie Abfragen haben, bei denen viele IsDeleted-Zeilen ohne Grund gescannt werden, besteht eine Lösung darin, einen gefilterten Nonclustered-Index zu erstellen, um diese Abfrage zu erfüllen.

Eine andere Möglichkeit besteht darin, eine indizierte Ansicht zu erstellen, die von einer Reihe verschiedener Abfragen genutzt werden kann und die nur nach den nicht gelöschten Zeilen gefiltert wird. Dies kann insbesondere in der Enterprise Edition hilfreich sein, in der der automatische Abgleich indizierter Ansichten ohne Angabe eines NOEXPANDHinweises funktioniert .

Bei kleinen oder stark gelesenen Tabellen kann das Hinzufügen von gefilterten Nonclustered-Indizes oder -Ansichten oder anderen Elementen zu unnötigem Overhead für Ihre Datenbank führen.

Josh Darnell
quelle
2

Unter der vernünftigen Annahme, dass Löschungen selten sind, sind keine Änderungen an den Indizes eine angemessene Lösung.

Ich fand, dass man früher oder später nach Verweisen auf gelöschte Zeilen fragen muss, und die Zeilen, die sich in den Indizes befinden, sind es plötzlich sehr wert.

Beachten Sie, dass Sie, sofern Sie keine Ansichten verwenden, alle Ihre Abfragen bearbeiten müssen, um die Filter trotzdem einzuschließen.

Joshua
quelle
0

Ich habe ein System gesehen, in dem das IS_DELETED-Flag entweder 0 oder der Wert des PK ist. In anderen Systemen war es das Negativ der PK.

Da die meisten Abfragen Werte mit dem Schlüssel "natural" oder business (manchmal multi-field) abrufen, werden sie nur über Joins von PK abgefragt. Am Ende der Haupttabelle und aller verknüpften Tabellen wurde jedoch immer AND IS_DELETED = 0 hinzugefügt.

Dieses System verfügte auch über eine Prüftabelle für jede Transaktionstabelle, in der Änderungen nachverfolgt wurden. und die Anwendung hatte eine Funktion, um alle Datenänderungen einschließlich der gelöschten Daten anzuzeigen.

Rick Ryker
quelle
0

Ich hoffe, Sie haben Recht und die Möglichkeit, die Abfrage zu ändern.

Würde dies jedoch die Verwendung des gefilterten Index verhindern, da jeder Join / jede Abfrage die IsDeleted-Prüfung implementieren muss (da die IsDeleted-Spalte in Join / Abfrage verwendet wird)?

Ich wollte einen wichtigen Punkt sagen, hoffe, ich kann es erklären.

In komplexen Abfragen werden sowohl where Transaction tableals auch Mastertables verwendet.

Verwenden Sie IsDeleted=0nur in TransactionTabelle. Nicht in der MasterTabelle verwenden.

Beispiel,

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

Es hat keinen Sinn c.isdeleted=0(in der CategoryTabelle zu verwenden). Es ist unnötig.

Gibt es in ähnlicher Weise einen Grund für die Verwendung P.isdeleted=0?

Weil ich alle nicht gelöschten Bestellungen und deren Details haben möchte.

Wie kann Productgelöscht werden, wann Orderist Activeoder wo Productidist Referenz.

Wenn Sie also in wichtigen Abfragen sorgfältig debuggen, können Sie möglicherweise einen Teil des Werts isdeleted = 0 entfernen.

Erstellen Sie nicht blindlings einen gefilterten Index, sondern wählen Sie zunächst alle sehr wichtigen und langsamen Abfragen aus.

Optimieren Sie diese langsamen Abfragen, und entscheiden Sie dann nur über den gefilterten Index oder die Optimierung des Index.

KumarHarsh
quelle