Wdrożenie systemu kontroli wersji za pomocą MySQL

15

Wiem, że pytano o to tu i tutaj , ale mam ten sam pomysł z inną możliwą implementacją i potrzebuję pomocy.

Początkowo miałem swój blogstoriesstół o tej strukturze:

| 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()             |

Po tym, jak zdecydowałem, że chcę wdrożyć system wersjonowania dla każdej historii na blogu, pierwszą rzeczą, jaka przyszła mi do głowy, było stworzenie innej tabeli do edycji ; potem pomyślałem, że mogę zmodyfikować istniejącą tabelę, aby przechowywała wersje zamiast edycji . Oto struktura, która przyszła mi do głowy:

| 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                                  |

Powody, dla których tu przybyłem:

  • uidPole początkowej tabeli był wyjątkowy w tabeli. Teraz story_idnie jest już wyjątkowy. Jak mam sobie z tym poradzić? (Myślałem, że mogę rozwiązać, story_id = xa następnie znaleźć najnowszą wersję, ale wydaje się to bardzo pochłaniające zasoby, więc proszę o radę)
  • author_idwartość pola jest powtarzana w każdym wierszu tabeli. Gdzie i jak powinienem to zatrzymać?

Edytować

Unikalny proces generowania kodów odbywa się w CreateUniqueCodefunkcji:

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;
  }
}

Kod został napisany w Hack i pierwotnie został napisany w PHP przez @Scott w swojej odpowiedzi .

Pola author_idi editor_id mogą być różne, ponieważ istnieją użytkownicy z wystarczającymi uprawnieniami do edytowania czyichś historii.

Zwycięzca
źródło

Odpowiedzi:

23

Analizując scenariusz - który prezentuje cechy związane z tematem zwanym tymczasowymi bazami danych - z perspektywy koncepcyjnej można stwierdzić, że: (a) „obecna” wersja historii blogu i (b) „poprzednia” wersja historii blogu , chociaż bardzo przypominające, są bytami różnych typów.

Ponadto, pracując na logicznym poziomie abstrakcji, fakty (reprezentowane przez rzędy) różnych rodzajów muszą być przechowywane w odrębnych tabelach. W omawianym przypadku, nawet jeśli dość podobne, (i) fakty na temat „obecnych” wersji różnią się od (ii) faktów na temat „przeszłych” wersji .

Dlatego zalecam zarządzanie sytuacją za pomocą dwóch tabel:

  • jeden dedykowany wyłącznie dla „aktualnych” lub „obecnych” wersji historii blogów , oraz

  • jeden, który jest odrębny, ale także powiązany z drugim, dla wszystkich „poprzednich” lub „przeszłych” wersji ;

każda z (1) nieco inną liczbą kolumn i (2) inną grupą ograniczeń.

Powrót na warstwie pojęciowej, uważam, że -w Twoja firma środowiska eksploatacji Autor i redaktor są pojęcia, które mogą być wykreślone jako ról , które mogą być odtwarzane przez użytkownika , a te ważne aspekty zależą od danych wyprowadzenia (poprzez operacje logiczne manipulacji szczebla) oraz interpretacja (przeprowadzana przez czytelników i pisarzy Blog Stories , na poziomie zewnętrznym skomputeryzowanego systemu informacyjnego, przy pomocy jednego lub więcej programów aplikacyjnych).

Szczegółowo opiszę wszystkie te czynniki i inne istotne punkty w następujący sposób.

Zasady biznesowe

Zgodnie z moim rozumieniem twoich wymagań, następujące formuły reguł biznesowych (zestawione pod względem odpowiednich typów podmiotów i ich rodzajów wzajemnych powiązań) są szczególnie pomocne w ustanowieniu odpowiedniego schematu pojęciowego :

  • Użytkownik pisze zero-jeden-lub-wiele BlogStories
  • BlogStory posiada zero-jeden-lub-wiele BlogStoryVersions
  • Użytkownik napisał zero-jedynkowa-lub-wiele BlogStoryVersions

Schemat IDEF1X dla ekspozytora

W związku z tym, aby objaśniać moją sugestię dzięki urządzeniu graficznym Stworzyłem próbkę IDEF1X diagram, który wywodzi się z regułami biznesowymi sformułowanych powyżej i innych cech, które wydają się istotne. Pokazano to na rysunku 1 :

Rysunek 1 - Wersje blogu - wersje IDEF1X Diagram

Dlaczego BlogStory i BlogStoryVersion są konceptualizowane jako dwa różne typy jednostek?

Bo:

  • BlogStoryVersion instancji (tj „przeszłości” jeden) zawsze posiada wartość dla UpdatedDateTime nieruchomości, podczas gdy BlogStory występowanie (tj „obecny” jeden) nie trzyma go.

  • Poza tym encje tych typów są jednoznacznie identyfikowane przez wartości dwóch różnych zestawów właściwości: BlogStoryNumber (w przypadku wystąpień BlogStory ) i BlogStoryNumber plus CreatedDateTime (w przypadku instancji BlogStoryVersion ).


Definicja Integration for Information Modeling ( IDEF1X ) jest dane godne polecenia modelowania techniką, która powstała jako standardu w grudniu 1993 przez United States National Institute of Standards and Technology (NIST). Opiera się ona na początku materiału teoretycznego autorem przez jedynego twórcy tego modelu relacyjnego , czyli dr EF Codd ; wwidoku danych Entity-Relationship danych opracowanym przez dr PP Chen ; a także na temat techniki projektowania logicznej bazy danych, stworzonej przez Roberta G. Browna.


Ilustracyjny logiczny układ SQL-DDL

Następnie, na podstawie poprzednio przedstawionej analizy pojęciowej, zadeklarowałem projekt poziomu logicznego poniżej:

-- 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.
);

Testowany w tym SQL Fiddle, który działa na MySQL 5.6.

BlogStorystół

Jak widać w projekcie demonstracyjnym, zdefiniowałem BlogStorykolumnę KLUCZ PODSTAWOWY (PK dla zwięzłości) z typem danych INT. W związku z tym możesz chcieć naprawić wbudowany automatyczny proces, który generuje i przypisuje wartość liczbową dla takiej kolumny w każdym wstawieniu wiersza. Jeśli nie masz nic przeciwko pozostawianiu luk w tym zestawie wartości, możesz zastosować atrybut AUTO_INCREMENT , powszechnie używany w środowiskach MySQL.

Wprowadzając wszystkie swoje indywidualne BlogStory.CreatedDateTimepunkty danych, możesz skorzystać z funkcji NOW () , która zwraca wartości Daty i Czasu , które są aktualne na serwerze bazy danych w momencie wykonania operacji INSERT. Dla mnie ta praktyka jest zdecydowanie bardziej odpowiednia i mniej podatna na błędy niż stosowanie procedur zewnętrznych.

Pod warunkiem, że, jak omówiono w (teraz usuniętych) komentarzach, chcesz uniknąć możliwości utrzymywania BlogStory.Titlezduplikowanych wartości, musisz skonfigurować ograniczenie UNIQUE dla tej kolumny. Z uwagi na fakt, że dany tytuł może być współużytkowany przez kilka (lub nawet wszystkie) „przeszłe” wersje BlogStoryVersions , wówczas dla kolumny nie należy ustanawiać ograniczenia UNIKALNEGO BlogStoryVersion.Title.

I obejmował BlogStory.IsActivekolumnę typu BIT (1) (choć TINYINT mogą być również stosowane) w przypadku trzeba zapewnić „miękkie” lub „logiczne” funkcjonalność usunąć.

Szczegóły dotyczące BlogStoryVersiontabeli

Z drugiej strony, PK BlogStoryVersiontabeli składa się z (a) BlogStoryNumberi (b) kolumny o nazwie, CreatedDateTimektóra oczywiście oznacza dokładny moment, w którym BlogStorywiersz przeszedł INSERT.

BlogStoryVersion.BlogStoryNumber, oprócz tego, że jest częścią PK, jest również ograniczony jako KLUCZ ZAGRANICZNY (FK), który odwołuje się BlogStory.BlogStoryNumber, konfiguracja, która wymusza integralność referencyjną między wierszami tych dwóch tabel. W związku z tym wdrożenie automatycznego generowania a BlogStoryVersion.BlogStoryNumbernie jest konieczne, ponieważ ustawione jako FK wartości WSTAWIONE w tej kolumnie muszą być „zaczerpnięte” z wartości już zawartych w powiązanym BlogStory.BlogStoryNumberodpowiedniku.

BlogStoryVersion.UpdatedDateTimeKolumnie należy zachować, jak oczekiwano, punkt w czasie, gdy BlogStoryrząd został zmodyfikowany, a w związku z tym dodaje do BlogStoryVersiontabeli. Dlatego w tej sytuacji możesz również użyć funkcji NOW ().

Odstęp zrozumiana pomiędzy BlogStoryVersion.CreatedDateTimei BlogStoryVersion.UpdatedDateTimewyraża całą Okres , podczas którego BlogStoryrząd był „obecny” lub „bieżący”.

Uwagi dotyczące Versionkolumny

Może to być przydatne, aby myśleć o BlogStoryVersion.CreatedDateTimejak kolumna, która posiada wartość, która stanowi szczególną „przeszłości” Wersja o BlogStory . Uważam, że jest to o wiele bardziej korzystne niż a, VersionIdlub VersionCode, ponieważ jest bardziej przyjazne dla użytkownika w tym sensie, że ludzie są bardziej zaznajomieni z koncepcjami czasu . Na przykład autorzy lub czytelnicy blogu mogą odnosić się do BlogStoryVersion w sposób podobny do następującego:

  • „Chcę zobaczyć specyficznej wersji z BlogStory zidentyfikowanych przez numer 1750 , który został Created na 26 August 2015co 9:30”.

Autor i Editor Role: wyprowadzenie danych i interpretacja

Dzięki takiemu podejściu, można łatwo odróżnić, kto trzyma „oryginał” AuthorIdz betonu BlogStory wybierając opcję „najwcześniejszy” Wersja pewnej BlogStoryIdod BlogStoryVersionstołu dzięki zastosowaniu funkcji MIN () do BlogStoryVersion.CreatedDateTime.

W ten sposób każda BlogStoryVersion.AuthorIdwartość zawarta we wszystkich wierszach „późniejszych” lub „następnych” wersji wskazuje oczywiście identyfikator autora odpowiedniej wersji , ale można również powiedzieć, że taka wartość oznacza jednocześnie Rola odgrywana przez zaangażowanych użytkownika jako edytora w „oryginalnej” wersji z BlogStory .

Tak, dana AuthorIdwartość może być współużytkowana przez wiele BlogStoryVersionwierszy, ale w rzeczywistości jest to informacja, która mówi coś bardzo ważnego o każdej wersji , więc powtórzenie wspomnianego układu odniesienia nie stanowi problemu.

Format kolumn DATETIME

Jeśli chodzi o typ danych DATETIME, tak, masz rację, „ MySQL pobiera i wyświetla wartości DATETIME w YYYY-MM-DD HH:MM:SSformacie „ ”, ale możesz z pewnością wprowadzić odpowiednie dane w ten sposób, a kiedy musisz wykonać zapytanie, musisz tylko skorzystaj z wbudowanych funkcji DATE i TIME , aby między innymi pokazać odpowiednie wartości w odpowiednim formacie dla użytkowników. Lub z pewnością możesz przeprowadzić tego rodzaju formatowanie danych za pomocą kodu aplikacji.

Konsekwencje BlogStoryoperacji UPDATE

Za każdym razem, gdy BlogStorywiersz ma aktualizację, musisz upewnić się, że odpowiednie wartości, które były „obecne” do czasu modyfikacji, zostały następnie WSTAWIONE do BlogStoryVersiontabeli. Dlatego wysoce sugeruję wykonanie tych operacji w ramach jednej TRANSAKCJI KWASU, aby zagwarantować, że są one traktowane jako niepodzielna Jednostka Pracy. Równie dobrze możesz zatrudnić SPUSTY, ale mają tendencję do robienia rzeczy niechlujnych, że tak powiem.

Przedstawiamy kolumnę VersionIdlubVersionCode

Jeśli zdecydujesz się (ze względu na okoliczności biznesowe lub osobiste preferencje) dołączyć kolumnę BlogStory.VersionIdlub, BlogStory.VersionCodeaby wyróżnić BlogStoryVersions , powinieneś rozważyć następujące możliwości:

  1. VersionCodeMoże być wymagane, aby być unikalny dla wzoru (I) w całym BlogStorystole, a także (ii) BlogStoryVersion.

    Dlatego musisz wdrożyć dokładnie przetestowaną i całkowicie niezawodną metodę, aby wygenerować i przypisać każdą Codewartość.

  2. Być może VersionCodewartości mogą być powtarzane w różnych BlogStorywierszach, ale nigdy nie są powielane wraz z tym samym BlogStoryNumber. Np. Możesz mieć:

    • BlogStoryNumber 3- Wersja83o7c5c , a jednocześnie
    • BlogStoryNumber 86- Wersja83o7c5c i
    • BlogStoryNumber 958- Wersja83o7c5c .

Późniejsza możliwość otwiera kolejną alternatywę:

  1. Utrzymanie VersionNumberdla BlogStories, więc mogą być:

    • BlogStoryNumber 23- wersje1, 2, 3… ;
    • BlogStoryNumber 650- wersje1, 2, 3… ;
    • BlogStoryNumber 2254- wersje1, 2, 3… ;
    • itp.

Trzymanie „oryginalnej” i „kolejnej” wersji w jednym stole

Chociaż możliwe jest utrzymanie wszystkich BlogStoryVersions w tej samej indywidualnej tabeli bazowej , sugeruję, aby tego nie robić, ponieważ mieszalibyście dwa różne (pojęciowe) fakty, co w ten sposób ma niepożądane skutki uboczne dla

  • ograniczenia danych i manipulacje (na poziomie logicznym), wraz z
  • powiązane przetwarzanie i przechowywanie (w warstwie fizycznej).

Ale pod warunkiem, że zdecydujesz się na taki sposób postępowania, możesz nadal korzystać z wielu pomysłów wyszczególnionych powyżej, np .:

  • kompozytu PK, składający się z kolumny (INT BlogStoryNumber), a kolumnę DATETIME ( CreatedDateTime);
  • wykorzystanie funkcji serwera w celu optymalizacji odpowiednich procesów oraz
  • Autor i Editor wywodzących Role .

Widząc, że postępując zgodnie z takim podejściem, BlogStoryNumberwartość zostanie zduplikowana, gdy tylko zostaną dodane „nowsze” wersje , opcją, którą i którą możesz ocenić (która jest bardzo podobna do tych wymienionych w poprzedniej sekcji) jest ustanowienie BlogStoryPK składa się z kolumn BlogStoryNumberi VersionCodew ten sposób będzie można jednoznacznie zidentyfikować każdą wersję o BlogStory . I możesz spróbować z kombinacją BlogStoryNumberi VersionNumberteż.

Podobny scenariusz

Możesz znaleźć moją odpowiedź na to pytanie pomocy, ponieważ ja również proponuję włączenie możliwości czasowych w odpowiedniej bazie danych, aby poradzić sobie z porównywalnym scenariuszem.

MDCCL
źródło
2

Jedną z opcji jest użycie wersji Normal Form (vnf). Zalety obejmują:

  • Bieżące dane i wszystkie poprzednie dane znajdują się w tej samej tabeli.
  • To samo zapytanie służy do pobierania bieżących danych lub danych aktualnych na określoną datę.
  • Odnośniki klucza obcego do danych wersjonowanych działają tak samo, jak w przypadku danych niewersjonowanych.

Dodatkowa korzyść w twoim przypadku, ponieważ dane w wersji są jednoznacznie identyfikowane poprzez włączenie daty wejścia w życie (daty wprowadzenia zmiany) jako części klucza, oddzielne pole version_id nie jest wymagane.

Oto wyjaśnienie bardzo podobnego rodzaju bytu.

Więcej szczegółów można znaleźć w prezentacji slajdów tutaj i niepełnym dokumencie tutaj

TommCatt
źródło
1

Twój związek

(story_id, id_wersji, id_redaktora, id_autora, znacznik czasu, tytuł, treść, coverimg)

nie jest w trzeciej normalnej formie. Dla każdej wersji twojej historii identyfikator autora jest taki sam. Potrzebujesz więc dwóch relacji, aby to przezwyciężyć

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

Kluczem do pierwszej relacji jest story_idklucz do drugiej relacji (story_id, version_id). Jeśli nie podoba Ci się kombinowany klucz, możesz użyć tylko version_idjako klucza

cud173
źródło
2
Wydaje się, że to nie rozwiązuje mojego problemu, po prostu je podkreśla
Wiktor
Nie odpowiada więc nawet, że author_id wartość pola zapytania jest powtarzana w każdym wierszu tabeli. Gdzie i jak powinienem to zatrzymać ?
cud 173
2
Naprawdę nie rozumiem, co mówi twoja odpowiedź. Może dlatego, że nie jestem rodzimym językiem angielskim, więc czy mógłbyś spróbować wyjaśnić to prostymi słowami?
Victor,
Oznacza to, że powinieneś unikać powtarzania numeru autor_id (jeśli story_id jest równy dla dwóch wierszy, ich autor_id też jest równy) i podziel swoją tabelę na dwie tabele, jak opisano w moim poście. Możesz więc uniknąć powtarzania id_autora.
miracle173,