Versionierungssystem mit MySQL implementieren

15

Ich weiß, dass dies hier und hier gefragt wurde , aber ich habe die gleiche Idee mit einer anderen möglichen Implementierung und brauche Hilfe.

Anfangs hatte ich meine blogstoriesTabelle mit dieser Struktur:

| Column    | Type        | Description                                    |
|-----------|-------------|------------------------------------------------|
| uid       | varchar(15) | 15 characters unique generated id              |
| title     | varchar(60) | story title                                    |
| content   | longtext    | story content                                  |
| author    | varchar(10) | id of the user that originally wrote the story |
| timestamp | int         | integer generated with microtime()             |

Nachdem ich beschlossen hatte, ein Versionsverwaltungssystem für jede Story im Blog zu implementieren, kam mir als Erstes die Idee, eine andere Tabelle für die Bearbeitung zu erstellen . Danach dachte ich, ich könnte die vorhandene Tabelle so ändern, dass sie Versionen anstelle von Bearbeitungen enthält . Dies ist die Struktur, die mir in den Sinn kam:

| Column        | Type          | Description                                       |
|------------   |-------------  |------------------------------------------------   |
| story_id      | varchar(15)   | 15 characters unique generated id                 |
| version_id    | varchar(5)    | 5 characters unique generated id                  |
| editor_id     | varchar(10)   | id of the user that commited                      |
| author_id     | varchar(10)   | id of the user that originally wrote the story    |
| timestamp     | int           | integer generated with microtime()                |
| title         | varchar(60)   | current story title                               |
| content       | longtext      | current story text                                |
| coverimg      | varchar(20)   | cover image name                                  |

Die Gründe, warum ich hierher gekommen bin:

  • Das uidFeld der Anfangstabelle war in der Tabelle EINZIGARTIG. Nun ist das story_idnicht mehr einzigartig. Wie soll ich damit umgehen? (Ich dachte, ich könnte story_id = xdie neueste Version ansprechen und dann finden, aber das scheint sehr ressourcenintensiv zu sein. Bitte geben Sie Ihren Rat.)
  • author_idDer Feldwert wird in jeder Zeile der Tabelle wiederholt. Wo und wie soll ich es aufbewahren?

Bearbeiten

Der Generierungsprozess für eindeutige Codes erfolgt in der CreateUniqueCodeFunktion:

trait UIDFactory {
  public function CryptoRand(int $min, int $max): int {
    $range = $max - $min;
    if ($range < 1) return $min;
    $log = ceil(log($range, 2));
    $bytes = (int) ($log / 8) + 1;
    $bits = (int) $log + 1;
    $filter = (int) (1 << $bits) - 1;
    do {
        $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
        $rnd = $rnd & $filter;
    } while ($rnd >= $range);
    return $min + $rnd;
  }
  public function CreateUID(int $length): string {
    $token = "";
    $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
    $codeAlphabet.= "0123456789";
    $max = strlen($codeAlphabet) - 1;
    for ($i=0; $i < $length; $i++) {
        $token .= $codeAlphabet[$this->CryptoRand(0, $max)];
    }
    return $token;
  }
}

Der Code ist in Hack geschrieben und wurde ursprünglich von @Scott in seiner Antwort in PHP geschrieben .

Die Felder author_idund editor_id können unterschiedlich sein, da es Benutzer gibt, die über ausreichende Berechtigungen verfügen, um die Storys eines anderen Benutzers zu bearbeiten.

Sieger
quelle

Antworten:

23

Wenn man das Szenario analysiert, in dem Merkmale des Themas dargestellt werden, die als Zeitdatenbanken bezeichnet werden , kann man aus konzeptioneller Sicht Folgendes feststellen: (a) eine „aktuelle“ Blog-Story-Version und (b) eine „vergangene“ Blog-Story-Version , wenn auch sehr ähnlich sind Entitäten verschiedener Arten.

Darüber hinaus müssen auf der logischen Abstraktionsebene Fakten (dargestellt durch Zeilen) unterschiedlicher Art in unterschiedlichen Tabellen aufbewahrt werden. Im betrachteten Fall unterscheiden sich (i) Fakten über „aktuelle“ Versionen von (ii) Fakten über „frühere“ Versionen , selbst wenn sie sehr ähnlich sind .

Daher empfehle ich, die Situation anhand von zwei Tabellen zu verwalten:

  • eine, die ausschließlich für die „aktuellen“ oder „gegenwärtigen“ Versionen der Blog-Geschichten bestimmt ist , und

  • eine, die für alle "vorherigen" oder "früheren" Versionen getrennt, aber auch mit der anderen verbunden ist ;

jeweils mit (1) einer geringfügig unterschiedlichen Anzahl von Spalten und (2) einer unterschiedlichen Gruppe von Einschränkungen.

Zurück zur konzeptionellen Ebene, denke ich, dass in Ihrer Geschäftsumgebung Autor und Herausgeber Begriffe sind, die als Rollen definiert werden können, die von einem Benutzer gespielt werden können. Diese wichtigen Aspekte hängen von der Datenableitung ab (über Manipulationsoperationen auf logischer Ebene). und Interpretation (durchgeführt von den Blog Stories- Lesern und -Schreibern auf der externen Ebene des computergestützten Informationssystems mit Hilfe eines oder mehrerer Anwendungsprogramme).

Ich werde alle diese Faktoren und andere relevante Punkte wie folgt detaillieren.

Geschäftsregeln

Nach meinem Verständnis Ihrer Anforderungen sind die folgenden Geschäftsregelformulierungen (zusammengestellt in Bezug auf die relevanten Entitätstypen und ihre Arten von Wechselbeziehungen) besonders hilfreich, um das entsprechende konzeptionelle Schema zu erstellen:

  • Ein Benutzer schreibt null-eins-oder-viele BlogStories
  • Eine BlogStory enthält null-eins-oder-viele BlogStoryVersions
  • Ein Benutzer hat null-eins-oder-viele BlogStoryVersions geschrieben

Expository IDEF1X Diagramm

Folglich, um meinen Vorschlag auf Grund einer graphischen Einrichtung zu auslegen, hat ich eine Probe IDEF1X erstellt ein Diagramm , das von den Geschäftsregeln formuliert und anderen Merkmalen abgeleitet ist , die relevant erscheinen. In Abbildung 1 ist Folgendes dargestellt :

Abbildung 1 - IDEF1X-Diagramm für Blog-Story-Versionen

Warum werden BlogStory und BlogStoryVersion als zwei verschiedene Entitätstypen konzipiert?

Weil:

  • Eine BlogStoryVersion- Instanz (dh eine "vergangene") enthält immer einen Wert für eine UpdatedDateTime- Eigenschaft, während eine BlogStory-Instanz (dh eine "aktuelle") diesen Wert niemals enthält.

  • Außerdem werden die Entitäten dieser Typen durch die Werte von zwei unterschiedlichen Eigenschaftssätzen eindeutig identifiziert: BlogStoryNumber (im Fall der BlogStory- Vorkommen) und BlogStoryNumber plus CreatedDateTime (im Fall der BlogStoryVersion- Instanzen).


eine Definition Integration für Information Modeling ( IDEF1X ) ist eine sehr empfehlenswerte Datenmodellierungstechnik, die als etabliert wurde Standard im Dezember 1993 von den Vereinigten Staaten National Institute of Standards and Technology (NIST). Es basiert auf dem frühen theoretischen Material, das vom alleinigen Urheber des relationalen Modells verfasst wurde , dh Dr. EF Codd ; zur Entity-Relationship- Sicht der Daten, entwickelt von Dr. PP Chen ; und auch auf der Logical Database Design Technique, erstellt von Robert G. Brown.


Beispielhaftes logisches SQL-DDL-Layout

Dann habe ich auf der Grundlage der zuvor vorgestellten konzeptionellen Analyse den folgenden Entwurf auf logischer Ebene deklariert:

-- You should determine which are the most fitting 
-- data types and sizes for all your table columns 
-- depending on your business context characteristics.

-- Also you should make accurate tests to define the most
-- convenient index strategies at the physical level.

-- As one would expect, you are free to make use of 
-- your preferred (or required) naming conventions.    

CREATE TABLE UserProfile (
    UserId          INT      NOT NULL,
    FirstName       CHAR(30) NOT NULL,
    LastName        CHAR(30) NOT NULL,
    BirthDate       DATETIME NOT NULL,
    GenderCode      CHAR(3)  NOT NULL,
    UserName        CHAR(20) NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT UserProfile_PK  PRIMARY KEY (UserId),
    CONSTRAINT UserProfile_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
        FirstName,
        LastName,
        BirthDate,
        GenderCode
    ), 
    CONSTRAINT UserProfile_AK2 UNIQUE (UserName) -- ALTERNATE KEY.
);

CREATE TABLE BlogStory (
    BlogStoryNumber INT      NOT NULL,
    Title           CHAR(60) NOT NULL,
    Content         TEXT     NOT NULL,
    CoverImageName  CHAR(30) NOT NULL,
    IsActive        BIT(1)   NOT NULL,
    AuthorId        INT      NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT BlogStory_PK              PRIMARY KEY (BlogStoryNumber),
    CONSTRAINT BlogStory_AK              UNIQUE      (Title), -- ALTERNATE KEY.
    CONSTRAINT BlogStoryToUserProfile_FK FOREIGN KEY (AuthorId)
        REFERENCES UserProfile (UserId)
);

CREATE TABLE BlogStoryVersion  (
    BlogStoryNumber INT      NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    Title           CHAR(60) NOT NULL,
    Content         TEXT     NOT NULL,
    CoverImageName  CHAR(30) NOT NULL,
    IsActive        BIT(1)   NOT NULL,
    AuthorId        INT      NOT NULL,
    UpdatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT BlogStoryVersion_PK              PRIMARY KEY (BlogStoryNumber, CreatedDateTime), -- Composite PK.
    CONSTRAINT BlogStoryVersionToBlogStory_FK   FOREIGN KEY (BlogStoryNumber)
        REFERENCES BlogStory (BlogStoryNumber),
    CONSTRAINT BlogStoryVersionToUserProfile_FK FOREIGN KEY (AuthorId)
        REFERENCES UserProfile (UserId),
    CONSTRAINT DatesSuccession_CK               CHECK       (UpdatedDateTime > CreatedDateTime) --Let us hope that MySQL will finally enforce CHECK constraints in a near future version.
);

Getestet in dieser SQL-Geige , die unter MySQL 5.6 ausgeführt wird.

Der BlogStoryTisch

Wie Sie im Demo-Design sehen können, habe ich die BlogStorySpalte PRIMARY KEY (PK der Kürze halber) mit dem Datentyp INT definiert. In diesem Zusammenhang möchten Sie möglicherweise einen integrierten automatischen Prozess festlegen, der in jeder Zeileneinfügung einen numerischen Wert für eine solche Spalte generiert und zuweist. Wenn es Ihnen nichts ausmacht, gelegentlich Lücken in diesem Wertesatz zu lassen, können Sie das Attribut AUTO_INCREMENT verwenden , das häufig in MySQL-Umgebungen verwendet wird.

Wenn Sie alle Ihre individuellen BlogStory.CreatedDateTimeDatenpunkte eingeben, können Sie die Funktion NOW () verwenden , die die Datums- und Zeitwerte zurückgibt, die zum genauen Zeitpunkt des INSERT-Vorgangs auf dem Datenbankserver aktuell sind. Diese Praxis ist für mich entschieden geeigneter und weniger fehleranfällig als der Einsatz externer Routinen.

Vorausgesetzt, Sie möchten, wie in den (jetzt entfernten) Kommentaren erläutert, die Möglichkeit vermeiden, BlogStory.Titledoppelte Werte beizubehalten , müssen Sie eine EINZIGARTIGE Einschränkung für diese Spalte einrichten . Aufgrund der Tatsache, dass ein bestimmter Titel von mehreren (oder sogar allen) "früheren" BlogStoryVersions gemeinsam genutzt werden kann , sollte für die Spalte keine EINZIGARTIGE Einschränkung festgelegt werden BlogStoryVersion.Title.

Ich habe die BlogStory.IsActiveSpalte vom Typ BIT (1) eingefügt (obwohl auch ein TINYINT verwendet werden kann), falls Sie die Funktion "soft" oder "logical" DELETE bereitstellen müssen.

Details zur BlogStoryVersionTabelle

Andererseits setzt sich die PK der BlogStoryVersionTabelle aus (a) BlogStoryNumberund (b) einer Spalte zusammen CreatedDateTime, die natürlich den genauen Zeitpunkt kennzeichnet, zu dem eine BlogStoryZeile ein INSERT durchlief.

BlogStoryVersion.BlogStoryNumberIst nicht nur Teil der PK, sondern auch ein FOREIGN KEY (FK), der auf BlogStory.BlogStoryNumbereine Konfiguration verweist , die die referenzielle Integrität zwischen den Zeilen dieser beiden Tabellen erzwingt . Insofern ist die Implementierung einer automatischen Generierung von a BlogStoryVersion.BlogStoryNumbernicht erforderlich, da die in diese Spalte eingefügten Werte als FK aus den Werten "gezogen" werden müssen, die bereits im zugehörigen BlogStory.BlogStoryNumberGegenstück enthalten sind.

Die BlogStoryVersion.UpdatedDateTimeSpalte sollte wie erwartet den Zeitpunkt beibehalten, zu dem eine BlogStoryZeile geändert und folglich der BlogStoryVersionTabelle hinzugefügt wurde . Daher können Sie die Funktion NOW () auch in dieser Situation verwenden.

Das Intervall zwischen BlogStoryVersion.CreatedDateTimeund BlogStoryVersion.UpdatedDateTimedrückt den gesamten Zeitraum aus, in dem eine BlogStoryZeile „vorhanden“ oder „aktuell“ war.

Überlegungen zu einer VersionSpalte

Es kann nützlich sein, sich BlogStoryVersion.CreatedDateTimedie Spalte vorzustellen , die den Wert enthält, der eine bestimmte „frühere“ Version einer BlogStory darstellt . Ich halte dies viel günstiger als ein VersionIdoder VersionCode, da sie benutzerfreundlicher in dem Sinne, dass Menschen mit sein vertrauten neigen Zeit Konzepten. Die Blog-Autoren oder -Leser könnten beispielsweise auf eine BlogStoryVersion wie folgt verweisen :

  • „Ich möchte die spezifische sehen Version des BlogStory durch identifiziert Zahl 1750 , die wurde Erstellt am 26 August 2015an 9:30“.

Die Autoren- und Herausgeberrollen : Datenableitung und Interpretation

Mit diesem Ansatz können Sie leicht unterscheiden, wer das „Original“ AuthorIdeiner konkreten BlogStory besitzt. Wählen Sie die „früheste“ Version einer bestimmten BlogStoryIdFROM- BlogStoryVersionTabelle aus, indem Sie die MIN () -Funktion auf anwenden BlogStoryVersion.CreatedDateTime.

Auf diese Weise gibt jeder BlogStoryVersion.AuthorIdWert, der in allen Zeilen "später" oder "nachfolgender" Versionen enthalten ist , natürlich die Autorenkennung der jeweiligen Version an, aber man kann auch sagen, dass ein solcher Wert gleichzeitig bezeichnet die Rolle der beteiligten gespielt Benutzer als Editor der „Original“ Version eines BlogStory .

Ja, ein gegebener AuthorIdWert kann von mehreren BlogStoryVersionZeilen geteilt werden , aber dies ist tatsächlich eine Information, die etwas sehr Wichtiges über jede Version aussagt , so dass die Wiederholung dieses Datums kein Problem darstellt.

Das Format der DATETIME-Spalten

Was den Datentyp DATETIME betrifft, haben Sie Recht: „ MySQL ruft DATETIME-Werte im YYYY-MM-DD HH:MM:SSFormat ' ' ab und zeigt sie an “, aber Sie können die relevanten Daten auf diese Weise sicher eingeben, und wenn Sie eine Abfrage durchführen müssen, müssen Sie nur Nutzen Sie die eingebauten Funktionen DATE und TIME , um unter anderem die betreffenden Werte in dem für Ihre Benutzer geeigneten Format anzuzeigen. Oder Sie können diese Art der Datenformatierung sicher über den Code Ihres Anwendungsprogramms durchführen.

Auswirkungen von BlogStoryUPDATE-Operationen

Jedes Mal, wenn eine BlogStoryZeile ein UPDATE erleidet, müssen Sie sicherstellen, dass die entsprechenden Werte, die bis zur Änderung „vorhanden“ waren, dann in die BlogStoryVersionTabelle eingefügt werden . Ich empfehle daher dringend, diese Operationen in einer einzigen ACID TRANSACTION durchzuführen, um sicherzustellen , dass sie als unteilbare Arbeitseinheit behandelt werden. Sie können TRIGGERS auch einsetzen, aber sie neigen dazu, die Dinge sozusagen unordentlich zu machen.

Einführen einer VersionIdoder VersionCodeSpalte

Wenn Sie sich (aufgrund geschäftlicher Umstände oder persönlicher Vorlieben) dafür entscheiden, eine BlogStory.VersionIdoder BlogStory.VersionCode-Spalte zur Unterscheidung der BlogStoryVersions einzufügen , sollten Sie über die folgenden Möglichkeiten nachdenken:

  1. Es VersionCodekann erforderlich sein, dass A in (i) der gesamten BlogStoryTabelle und auch in (ii) EINZIGARTIG ist BlogStoryVersion.

    Daher müssen Sie eine sorgfältig getestete und absolut zuverlässige Methode implementieren, um jeden CodeWert zu generieren und zuzuweisen .

  2. Möglicherweise VersionCodekönnten die Werte in verschiedenen BlogStoryZeilen wiederholt werden , jedoch niemals zusammen mit denselben dupliziert werden BlogStoryNumber. ZB könntest du haben:

    • eine BlogStoryNumber 3- Version83o7c5c und gleichzeitig
    • eine BlogStoryNumber 86- Version83o7c5c und
    • eine BlogStoryNumber 958- Version83o7c5c .

Die spätere Möglichkeit eröffnet eine weitere Alternative:

  1. Halten Sie ein VersionNumberfür BlogStories, so könnte es sein:

    • BlogStoryNumber 23- Versionen1, 2, 3… ;
    • BlogStoryNumber 650- Versionen1, 2, 3… ;
    • BlogStoryNumber 2254- Versionen1, 2, 3… ;
    • etc.

Halten Sie "ursprüngliche" und "nachfolgende" Versionen in einer einzigen Tabelle

Obwohl Beibehaltung aller BlogStoryVersions in der gleichen individuellen Basistabelle ist möglich, schlage ich vor, es nicht zu tun , weil Sie zwei unterschiedliche (konzeptionelle) Arten von Tatsachen Vermischung würden, die damit unerwünschte Nebenwirkungen auf

  • Datenbeschränkungen und -manipulation (auf logischer Ebene) sowie
  • die damit verbundene Verarbeitung und Speicherung (auf der physischen Ebene).

Unter der Voraussetzung, dass Sie sich für diese Vorgehensweise entscheiden, können Sie dennoch viele der oben beschriebenen Ideen nutzen, z.

  • eine zusammengesetzte PK, die aus einer INT-Spalte ( BlogStoryNumber) und einer DATETIME-Spalte ( CreatedDateTime) besteht;
  • die Nutzung von Serverfunktionen zur Optimierung der dazugehörigen Prozesse und
  • die vom Autor und Herausgeber ableitbaren Rollen .

Wenn Sie mit einem solchen Ansatz fortfahren, wird ein BlogStoryNumberWert dupliziert , sobald „neuere“ Versionen hinzugefügt werden. Eine Option, die Sie auswerten können (die den im vorherigen Abschnitt erwähnten sehr ähnlich ist), erstellt eine BlogStoryPK bestehend aus den Spalten BlogStoryNumberund VersionCodeman auf diese Weise eindeutig wäre in der Lage , um jede zu identifizieren Version eines BlogStory . Und Sie können es mit einer Kombination von BlogStoryNumberund versuchen VersionNumber.

Ähnliches Szenario

Vielleicht finden Sie meine Antwort auf diese Frage der Hilfe, da ich auch vorschlage, zeitliche Fähigkeiten in der betreffenden Datenbank zu aktivieren , um mit einem vergleichbaren Szenario fertig zu werden.

MDCCL
quelle
2

Eine Möglichkeit ist die Verwendung von Version Normal Form (vnf). Die Vorteile umfassen:

  • Die aktuellen Daten und alle früheren Daten befinden sich in derselben Tabelle.
  • Dieselbe Abfrage wird verwendet, um aktuelle Daten oder Daten abzurufen, die zu einem bestimmten Datum aktuell waren.
  • Fremdschlüsselverweise auf versionierte Daten funktionieren genauso wie für nicht versionierte Daten.

Ein zusätzlicher Vorteil in Ihrem Fall: Da versionierte Daten eindeutig identifiziert werden, indem das Gültigkeitsdatum (das Datum, an dem die Änderung vorgenommen wurde) Teil des Schlüssels ist, ist kein separates version_id-Feld erforderlich.

Hier finden Sie eine Erklärung für einen sehr ähnlichen Entitätstyp.

Weitere Details finden Sie in einer Dia - Präsentation finden Sie hier und ein nicht ganz abgeschlossen Dokument hier

TommCatt
quelle
1

Deine Beziehung

(story_id, version_id, editor_id, author_id, timestamp, title, content, coverimg)

ist nicht in der 3. Normalform. Für jede Version Ihrer Geschichte ist die author_id gleich. Sie benötigen also zwei Beziehungen, um dies zu überwinden

(story_id, author_id)
(story_id, version_id, editor_id, timestamp, title, content, coverimg)

Der Schlüssel der ersten Relation ist story_id, der Schlüssel der zweiten Relation ist der kombinierte Schlüssel (story_id, version_id). Wenn Ihnen der kombinierte Schlüssel nicht gefällt, können Sie ihn nur version_idals Schlüssel verwenden

miracle173
quelle
2
Dies scheint mein Problem nicht zu lösen, es betont sie nur
Victor
Es antwortet also nicht einmal, wenn der Feldwert der Abfrage author_id in jeder Zeile der Tabelle wiederholt wird. Wo und wie soll ich es aufbewahren ?
miracle173
2
Ich verstehe nicht wirklich, was Ihre Antwort aussagt. Es könnte sein, dass ich kein englischer Muttersprachler bin. Könnten Sie also bitte versuchen, es in einfacheren Worten zu erklären?
Victor
Das bedeutet, dass Sie die Wiederholung der author_id-Nummer vermeiden sollten (wenn die story_id für zwei Zeilen gleich ist, ist auch ihre author_id gleich) und Ihre Tabelle in die beiden Tabellen aufteilen sollten, wie in meinem Beitrag beschrieben. So können Sie die Wiederholung von author_id vermeiden.
miracle173