Wann ist es besser, Flags als Bitmaske zu speichern, als eine assoziative Tabelle zu verwenden?

76

Ich arbeite an einer Anwendung, in der Benutzer unterschiedliche Berechtigungen zur Verwendung unterschiedlicher Funktionen haben (z. B. Lesen, Erstellen, Herunterladen, Drucken, Genehmigen usw.). Es wird nicht erwartet, dass sich die Liste der Berechtigungen häufig ändert. Ich habe einige Möglichkeiten, wie diese Berechtigungen in der Datenbank gespeichert werden können.

In welchen Fällen wäre Option 2 besser?

Option 1

Verwenden Sie eine assoziative Tabelle.

Nutzer
----
UserId (PK)
Name
Abteilung
Genehmigung
----
PermissionId (PK)
Name
User_Permission
----
UserId (FK)
PermissionId (FK)

Option 2

Speichern Sie für jeden Benutzer eine Bitmaske.

Nutzer
----
UserId (PK)
Name
Abteilung
Berechtigungen
[Flags]
enum Permissions {
    Read = 1,
    Create = 2,
    Download = 4,
    Print = 8,
    Approve = 16
}
Ryan Kohn
quelle

Antworten:

63

Herrliche Frage!

Lassen Sie uns zunächst einige Annahmen über "besser" treffen.

Ich gehe davon aus, dass Sie sich nicht viel um Speicherplatz kümmern - eine Bitmaske ist aus Platzgründen effizient, aber ich bin mir nicht sicher, ob dies wichtig ist, wenn Sie SQL Server verwenden.

Ich gehe davon aus, dass Ihnen Geschwindigkeit wichtig ist. Eine Bitmaske kann bei der Verwendung von Berechnungen sehr schnell sein - Sie können jedoch keinen Index verwenden, wenn Sie die Bitmaske abfragen. Dies sollte nicht allzu wichtig sein, aber wenn Sie wissen möchten, welche Benutzer Zugriff erstellen, ist Ihre Abfrage ungefähr so

select * from user where permsission & CREATE = TRUE

(Ich habe heute unterwegs keinen Zugriff auf SQL Server). Diese Abfrage kann aufgrund der mathematischen Operation keinen Index verwenden. Wenn Sie also eine große Anzahl von Benutzern haben, ist dies ziemlich schmerzhaft.

Ich gehe davon aus, dass Ihnen die Wartbarkeit am Herzen liegt. Unter dem Gesichtspunkt der Wartbarkeit ist die Bitmaske nicht so aussagekräftig wie die zugrunde liegende Problemdomäne wie das Speichern expliziter Berechtigungen. Sie müssten mit ziemlicher Sicherheit den Wert der Bitmaskenflags über mehrere Komponenten hinweg synchronisieren - einschließlich der Datenbank. Nicht unmöglich, aber Rückenschmerzen.

Sofern es keine andere Möglichkeit gibt, "besser" zu bewerten, würde ich sagen, dass die Bitmaskenroute nicht so gut ist wie das Speichern der Berechtigungen in einer normalisierten Datenbankstruktur. Ich bin nicht damit einverstanden, dass es "langsamer ist, weil Sie einen Join durchführen müssen" - wenn Sie nicht über eine völlig funktionsgestörte Datenbank verfügen, können Sie dies nicht messen (während das Abfragen ohne den Vorteil eines aktiven Index spürbar werden kann langsamer mit sogar ein paar tausend Datensätzen).

Neville Kuyt
quelle
6
Herrliche Antwort!
Lieven Keersmaekers
5
Da die Kardinalität einer booleschen Spalte (oder eines Bits im SQL Server-Fall) extrem niedrig ist, ist ein Index für diese Spalten völlig nutzlos. Die normalisierte Lösung hätte diese Optimierung also auch nicht zur Verfügung.
Clodoaldo Neto
Packt SQL Server benachbarte Bitfelder nicht in Bytes und speichert sie im Grunde genommen als Bitmaske.
Crush
12

Persönlich würde ich eine assoziative Tabelle verwenden.

Ein Bitmaskenfeld ist sehr schwer abzufragen und zu verbinden.

Sie können dies jederzeit Ihrer C # -Flaggen-Aufzählung zuordnen, und wenn die Leistung steigt und die Datenbank refaktoriert wird.

Lesbarkeit über vorzeitige Optimierung;)

Oded
quelle
6
Management und Wartung. Wie viel schwieriger wird es sein, die in der Datenbank gespeicherten Daten zu verwalten und zu verwalten, wenn kritische Informationen in einer Bitmaskenspalte verschleiert sind? Und jeder Leistungszuwachs wird mit ziemlicher Sicherheit nicht groß genug sein, um einen echten Unterschied zu bewirken.
Philip Kelley
5

Speichern Sie die Berechtigungen normalisiert (dh nicht in einer Bitmaske). Dies ist offensichtlich keine Voraussetzung für Ihr Szenario (insbesondere wenn sich die Berechtigungen nicht häufig ändern), macht das Abfragen jedoch viel einfacher und offensichtlicher.

Adam Robinson
quelle
5

Es gibt keine endgültige Antwort . Tun Sie also , was für Sie funktioniert . Aber hier ist mein Haken:

Verwenden Sie Option 1, wenn

  • Sie erwarten, dass die Berechtigungen auf viele anwachsen
  • Wenn Sie möglicherweise eine Berechtigungsprüfung in den gespeicherten Datenbankprozeduren selbst durchführen müssen
  • Sie erwarten nicht Millionen von Benutzern, damit die Datensätze in der Tabelle nicht massiv wachsen

Verwenden Sie Option 2, wenn

  • Die Berechtigungen sind auf eine Handvoll beschränkt
  • Sie erwarten Millionen von Benutzern
Aliostad
quelle
Millionen von Zeilen ist eine triviale Zahl in modernen (und sogar anständigen) RDBMS
Adam Robinson
Ja, aber angesichts der Indizes, die Sie möglicherweise benötigen, und der Möglichkeit, während der Suche Lesezeichen für Indizes zu setzen, die den Prozess verlangsamen, bevorzuge ich die zweite Option.
Aliostad
1

Das einzige Mal, dass ich mir vorstellen kann, wann ich ein Bitmaskenfeld zum Speichern von Berechtigungen verwenden würde, ist, wenn Sie wirklich sehr eingeschränkt sind, wie viel physischen Speicher Sie haben ... wie vielleicht auf einem alten mobilen Gerät. In Wahrheit ist die Menge an Speicher, die Sie sparen, es nicht wert. Selbst bei Millionen von Benutzern ist der Festplattenspeicher billig, und Sie können Berechtigungen usw. viel einfacher erweitern, indem Sie den Nicht-Bitmasken-Ansatz verwenden (hier geht es darum, zu melden, wer über welche Berechtigungen verfügt usw.).

Eines der größten Probleme, auf das ich gestoßen bin, ist das Zuweisen von Benutzerberechtigungen direkt in der Datenbank. Ich weiß, dass Sie versuchen sollten, die Anwendung zu verwenden, um sich selbst zu verwalten, und nicht viel mit Anwendungsdaten im Allgemeinen, aber manchmal ist es nur notwendig. Wenn die Bitmaske kein Zeichenfeld ist und Sie leicht sehen können, welche Berechtigungen jemand anstelle einer Ganzzahl hat, erklären Sie einem Analysten usw., wie Sie jemandem Schreibzugriff usw. gewähren können, indem Sie das Feld aktualisieren ..... und beten Ihre Arithmetik ist korrekt.

kemiller2002
quelle
1

Es ist nützlich, wenn sie sich in ihrer Struktur nicht ändern und immer zusammen verwendet werden. Auf diese Weise haben Sie kleine Roundtrips zum Server. Sie sind auch in Bezug auf die Leistung gut, da Sie alle Rechte in einer einzigen Zuweisung einer Variablen beeinflussen können.

Ich persönlich mag sie nicht ... In einigen leistungsintensiven Anwendungen werden sie immer noch verwendet. Ich erinnere mich, dass ich mit diesen eine Schach-KI implementiert habe, weil man ein Board in einem einzigen Vergleich bewerten konnte. Es ist mühsam, damit zu arbeiten.

Simon Dufour
quelle
1

Ich würde es immer normal speichern, es sei denn, die Datenbank enthält lediglich den Datensatz für Sie, und Sie werden damit nie etwas anderes tun, als abzurufen und zu speichern. Ein Szenario hierfür ist, wenn beim Anmelden die Berechtigungszeichenfolge Ihres Benutzers abgerufen und im Servercode verarbeitet und zwischengespeichert wird. In diesem Fall ist es wirklich nicht so wichtig, dass es denormalisiert ist.

Wenn Sie es in einer Zeichenfolge speichern und versuchen, auf DB-Ebene daran zu arbeiten, müssen Sie etwas turnen, um die Berechtigungen für Seite X zu erhalten, was schmerzhaft sein kann.

Mike M.
quelle
1

Ich rate aus folgenden Gründen von der Verwendung einer Bitmaske ab:

  • Index kann nicht effizient verwendet werden
  • Abfragen ist schwieriger
  • Lesbarkeit / Wartung wird stark beeinträchtigt
  • Der durchschnittliche Entwickler da draußen weiß nicht, was eine Bitmaske ist
  • Die Flexibilität wird reduziert (Obergrenze für nr Bits in einer Zahl)

Abhängig von Ihren Abfragemustern, dem geplanten Funktionsumfang und der Datenverteilung würde ich mich für Option 1 entscheiden, oder sogar für etwas Einfaches wie:

user_permissions(
   user_id
  ,read     
  ,create   
  ,download 
  ,print    
  ,approve  
  ,primary key(user_id)
);

Das Hinzufügen einer Spalte ist eine Schemaänderung, aber ich vermute, dass für das Hinzufügen eines Privilegs "Bereinigen" Code erforderlich ist, sodass die Privilegien möglicherweise nicht so dynamisch sein müssen, wie Sie denken.

Wenn Sie eine kranke Datenverteilung haben, z. B. wenn 90% der Benutzer keine einzige Berechtigung haben, funktioniert das folgende Modell ebenfalls einwandfrei (fällt jedoch bei größeren Scans auseinander (ein 5-Wege-Join gegenüber einer einzelnen vollständigen Tabelle) Scan).

user_permission_read(
   user_id
  ,primary key(user_id)
  ,foreign key(user_id) references user(user_id)
)

user_permission_write(
   user_id
  ,primary key(user_id)
  ,foreign key(user_id) references user(user_id)
)

user_permission_etcetera(
   user_id
  ,primary key(user_id)
  ,foreign key(user_id) references user(user_id)
)
Ronnis
quelle
-2

Ihre Abfragen werden mithilfe einer Flags-Aufzählung (Bitmaske) schneller ausgeführt, da Sie der zugeordneten Tabelle keinen Join hinzufügen müssen, um den Wert zu verstehen.

Smartcaveman
quelle
4
-1 Dies impliziert fälschlicherweise, dass es mit einem Join nicht schnell ausgeführt wird. Sie berücksichtigen auch nicht, was die Abfrage ist . Wenn geprüft wird, ob eine bestimmte Berechtigung vorhanden ist, werden durch die Verknüpfung einer ordnungsgemäß indizierten Spalte die Türen eines Bitmaskenfelds gesprengt, dessen bitweise Operationen einen Tabellenscan erfordern würden.
Adam Robinson
@ Adam Robinson, (1) Nein, das bedeutet das überhaupt nicht. Dies bedeutet, dass die Abfrage schneller ausgeführt wird , was korrekt ist. (2) Sie vergleichen die am besten optimierte Abfrage in einer assoziativen Tabelle mit der am schlechtesten optimierten Abfrage in einem ganzzahligen Feld. Das ist wirklich nicht sehr praktisch.
Smartcaveman
1
Während es sicherlich möglich ist, dass der Code, den Sie zur Interpretation der Bitmaske schreiben, effizienter ist als eine Verknüpfung mit der USER_PERMISSIONTabelle, ist es unwahrscheinlich, dass der Leistungsunterschied von Bedeutung ist - dies ist wahrscheinlich nicht die Engpassoperation - und es gibt eine erheblicher Klarheitsverlust im Code.
Justin Cave
Ihre ursprüngliche Version sagte "schnell", nicht "schneller", wie es jetzt tut, daher mein erster Kommentar. Ja, ich vergleiche "die am besten optimierte Abfrage" für die assoziative Version, aber es ist auch die Version, die am wahrscheinlichsten vorhanden ist. Ich vergleiche das mit der "am schlechtesten optimierten" Abfrage im Bitmaskenfeld, da dies wahrscheinlich auch wieder vorhanden sein wird. Es gibt keine Möglichkeit, einen bitweisen Index für ein Feld zu erstellen. Wenn Sie die Berechtigungen als Teil der Abfrage überprüfen möchten, ist eine bitweise Operation unvermeidbar. Haben Sie eine bessere Möglichkeit dafür?
Adam Robinson