Реализация системы управления версиями с MySQL

15

Я знаю, что об этом спрашивали здесь и здесь , но у меня та же идея с другой возможной реализацией, и мне нужна помощь.

Изначально у меня была blogstoriesтаблица с такой структурой:

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

После того, как я решил, что хочу внедрить некоторую систему управления версиями для каждой истории в блоге, первое, что пришло мне в голову, - это создать другую таблицу для хранения правок ; после этого я подумал, что смогу изменить существующую таблицу для хранения версий вместо правок . Это структура, которая пришла мне в голову:

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

Причины, по которым я пришел сюда:

  • uidПоле исходной таблицы было уникальным в таблице. Теперь это story_idуже не уникально. Как мне с этим бороться? (Я думал , что я мог бы обратиться , story_id = xа затем найти последнюю версию, но это , кажется , очень много ресурсов потребления, поэтому , пожалуйста , дайте совет)
  • author_idЗначение поля повторяется в каждой строке таблицы. Где и как мне его хранить?

редактировать

Процесс генерации уникальных кодов находится в CreateUniqueCodeфункции:

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

Код написан на Hack и изначально был написан на PHP @Scott в своем ответе .

Поля author_idи editor_id могут быть разными, потому что есть пользователи с достаточными правами на редактирование чьих-либо рассказов.

Виктор
источник

Ответы:

23

Анализируя сценарий, который представляет характеристики, связанные с предметом, известным как временные базы данных, с концептуальной точки зрения можно определить, что: (а) «настоящая» версия блоговой статьи и (б) «прошлая» версия блог-сюжета , хотя и очень напоминающие, являются сущностями разных типов.

Кроме того, при работе на логическом уровне абстракции факты (представленные строками) разных видов должны храниться в разных таблицах. В рассматриваемом случае, даже если они очень похожи, (i) факты о «настоящих» версиях отличаются от (ii) фактов о «прошлых» версиях .

Поэтому я рекомендую управлять ситуацией с помощью двух таблиц:

  • один предназначено исключительно для «текущей» или «настоящей» Версии этих историй Дневника , и

  • один, который является отдельным, но также связан с другим, для всех «предыдущих» или «прошлых» версий ;

каждый с (1) немного отличным количеством столбцов и (2) другой группой ограничений.

Возвращаясь к концептуальному уровню, я считаю, что - в вашей бизнес-среде - автор и редактор - это понятия, которые можно разграничить как роли, которые могут быть воспроизведены пользователем , и эти важные аспекты зависят от вывода данных (посредством операций манипуляции на логическом уровне) и интерпретация (осуществляется читателями и авторами блогов на внешнем уровне компьютеризированной информационной системы с помощью одной или нескольких прикладных программ).

Я подробно опишу все эти факторы и другие важные моменты следующим образом.

Бизнес правила

В соответствии с моим пониманием ваших требований, следующие формулировки бизнес-правил (собранные в терминах соответствующих типов сущностей и их видов взаимосвязей) особенно полезны при создании соответствующей концептуальной схемы:

  • Пользователь пишет ноль-один-или-многих BlogStories
  • BlogStory имеет нулевой один или многим BlogStoryVersions
  • Пользователь написал ноль-один-или-многих BlogStoryVersions

Описательная схема IDEF1X

Следовательно, для того , чтобы изложить свое предложение в силе графического устройства, я создал образец IDEF1x диаграмма , которая является производной от бизнеса - правил , сформулированных выше , и других функций , которые кажутся уместными. Это показано на рисунке 1 :

Рисунок 1 - Версии истории блога Диаграмма IDEF1X

Почему BlogStory и BlogStoryVersion концептуализируются как два разных типа сущностей?

Так как:

  • BlogStoryVersion экземпляр (то есть, «прошлое» один) всегда имеет значение для UpdatedDateTime собственности, в то время как BlogStory явление (то есть, «настоящий» один) никогда не удерживает его.

  • Кроме того, сущности этих типов уникально идентифицируются по значениям двух различных наборов свойств: BlogStoryNumber (в случае вхождений BlogStory ) и BlogStoryNumber плюс CreatedDateTime (в случае экземпляров BlogStoryVersion ).


Определение интеграции для информационного моделирования ( IDEF1X ) является высоко рекомендуемые данные моделирования методомкоторый был созданкачестве стандарта в декабре 1993 года США Национальным институтом стандартов и технологий (NIST). Он основан на раннем теоретическом материале авторство единственным виновником в реляционную модели , т.е. д - р Ф. Кодда ; о представлении данных сущности-отношения , разработанном доктором П.П. Ченом ; а также о методике проектирования логических баз данных, созданной Робертом Г. Брауном.


Иллюстративная логическая компоновка SQL-DDL

Затем, основываясь на ранее представленном концептуальном анализе, я объявил схему логического уровня ниже:

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

Протестировано в этой SQL Fiddle, которая работает на MySQL 5.6.

BlogStoryстол

Как вы можете видеть в демонстрационном проекте, я определил BlogStoryстолбец PRIMARY KEY (PK для краткости) с типом данных INT. В связи с этим вам может потребоваться исправить встроенный автоматический процесс, который генерирует и назначает числовое значение для такого столбца при каждой вставке строки. Если вы не возражаете иногда оставлять пропуски в этом наборе значений, вы можете использовать атрибут AUTO_INCREMENT , обычно используемый в средах MySQL.

При вводе всех ваших отдельных BlogStory.CreatedDateTimeточек данных вы можете использовать функцию NOW () , которая возвращает значения даты и времени, которые являются текущими на сервере базы данных в момент операции INSERT. Для меня эта практика определенно более подходит и менее подвержена ошибкам, чем использование внешних процедур.

При условии, что, как обсуждалось в (теперь удаленных) комментариях, вы хотите избежать возможности сохранения BlogStory.Titleповторяющихся значений, вы должны установить ограничение UNIQUE для этого столбца. Из-за того, что данный заголовок может быть общим для нескольких (или даже всех) «прошлых» BlogStoryVersions , тогда для столбца не должно быть установлено УНИКАЛЬНОЕ ограничение BlogStoryVersion.Title.

Я включил BlogStory.IsActiveстолбец типа BIT (1) (хотя TINYINT также может быть использован) на случай, если вам нужно обеспечить «мягкую» или «логическую» функциональность DELETE.

Подробности о BlogStoryVersionстоле

С другой стороны, PK BlogStoryVersionтаблицы состоит из (a) BlogStoryNumberи (b) столбца с именем, CreatedDateTimeкоторый, конечно, отмечает точный момент, в который BlogStoryстрока прошла INSERT.

BlogStoryVersion.BlogStoryNumberпомимо того, что является частью PK, он также ограничен как FOREIGN KEY (FK), который ссылается BlogStory.BlogStoryNumberна конфигурацию, которая обеспечивает ссылочную целостность между строками этих двух таблиц. В этом отношении реализация автоматической генерации a BlogStoryVersion.BlogStoryNumberне требуется, поскольку, будучи установленным в качестве FK, значения, ВСТАВЛЕННЫЕ в этот столбец, должны быть «взяты из» тех значений, которые уже включены в соответствующий BlogStory.BlogStoryNumberаналог.

BlogStoryVersion.UpdatedDateTimeКолонна должна сохранять, как и ожидалось, точка во времени , когда BlogStoryстрока была изменена и, как следствие, добавляется в BlogStoryVersionтаблицу. Следовательно, вы можете использовать функцию NOW () и в этой ситуации.

Интервал объяла между BlogStoryVersion.CreatedDateTimeи BlogStoryVersion.UpdatedDateTimeвыражает весь Период , в течение которого BlogStoryстрока была «присутствует» или «текущий».

Соображения для Versionстолбца

Это может быть полезно думать BlogStoryVersion.CreatedDateTimeкак столбец , который содержит значение , которое представляет собой отдельную «прошлое» версию о более BlogStory . Я считаю, что это намного выгоднее, чем « VersionIdили» VersionCode, поскольку это удобнее для пользователя в том смысле, что люди, как правило, лучше знакомы с концепциями времени . Например, авторы или читатели блога могут ссылаться на BlogStoryVersion способом, подобным следующему:

  • «Я хочу увидеть специфику Версия в BlogStory идентифицированной Номер 1750 , который был Created на 26 August 2015в 9:30».

Автор и редактор Роли: Вывод данных и интерпретация

При таком подходе можно легко различить , кто держит «оригинал» AuthorIdиз бетона BlogStory , выбирающий «ранняя» версию определенных BlogStoryIdИЗ BlogStoryVersionтаблицы в силе применения функции MIN () для BlogStoryVersion.CreatedDateTime.

Таким образом, каждое BlogStoryVersion.AuthorIdзначение, содержащееся во всех строках «более поздних» или «последующих» версий, указывает, естественно, идентификатор автора соответствующей версии под рукой, но можно также сказать, что такое значение в то же время обозначает роль играет привлеченного пользователя в качестве редактора в «оригинальной» версии в виде BlogStory .

Да, данное AuthorIdзначение может совместно использоваться несколькими BlogStoryVersionстроками, но на самом деле это часть информации, которая рассказывает что-то очень важное о каждой версии , поэтому повторение упомянутых данных не является проблемой.

Формат столбцов DATETIME

Что касается типа данных DATETIME, да, вы правы: « MySQL извлекает и отображает значения DATETIME в« YYYY-MM-DD HH:MM:SSформате », но вы можете уверенно вводить соответствующие данные таким образом, и когда вам нужно выполнить запрос, вам просто нужно используйте встроенные функции ДАТА и ВРЕМЯ , чтобы, среди прочего, показать соответствующие значения в соответствующем формате для ваших пользователей. Или вы могли бы, конечно, выполнить этот вид форматирования данных с помощью кода ваших прикладных программ.

Последствия BlogStoryопераций ОБНОВЛЕНИЕ

Каждый раз, когда BlogStoryстрока переносит UPDATE, вы должны убедиться, что соответствующие значения, которые «присутствовали» до тех пор, пока не произошла модификация, вставляются в BlogStoryVersionтаблицу. Поэтому я настоятельно рекомендую выполнять эти операции в рамках одной КИСЛОТНОЙ СДЕЛКИ, чтобы гарантировать, что они рассматриваются как неделимая единица работы. Вы можете также использовать TRIGGERS, но они, так сказать, делают вещи неопрятными.

Представляя VersionIdили VersionCodeстолбец

Если вы решили (из-за деловых обстоятельств или личных предпочтений) включить столбец BlogStory.VersionIdили, BlogStory.VersionCodeчтобы различать BlogStoryVersions , вам следует подумать о следующих возможностях:

  1. A VersionCodeможет потребоваться быть УНИКАЛЬНЫМ в (i) всей BlogStoryтаблице, а также в (ii) BlogStoryVersion.

    Следовательно, вы должны внедрить тщательно протестированный и полностью надежный метод для генерации и присвоения каждого Codeзначения.

  2. Возможно, VersionCodeзначения могут повторяться в разных BlogStoryстроках, но никогда не дублироваться вместе с одинаковыми BlogStoryNumber. Например, вы могли бы иметь:

    • a BlogStoryNumber 3- версия83o7c5c и, одновременно,
    • a BlogStoryNumber 86- Версия83o7c5c и
    • a BlogStoryNumber 958- Версия83o7c5c .

Более поздняя возможность открывает другую альтернативу:

  1. Хранение VersionNumberдля BlogStories, так что там может быть:

    • BlogStoryNumber 23 - Версии1, 2, 3… ;
    • BlogStoryNumber 650- Версии1, 2, 3… ;
    • BlogStoryNumber 2254 - Версии1, 2, 3… ;
    • и т.п.

Хранение «оригинальной» и «последующих» версий в одной таблице

Несмотря на то, сохраняя все BlogStoryVersions в одной отдельной базовой таблице можно, я предлагаю не делать это , потому что вы бы смешение двух различных (концептуальными) типов фактов, которые , таким образом , имеют нежелательные побочные эффекты на

  • ограничения данных и манипуляции (на логическом уровне), а также
  • соответствующая обработка и хранение (на физическом уровне).

Но при условии, что вы решите следовать этому курсу, вы все равно сможете воспользоваться многими из идей, описанных выше, например:

  • композит ПК , состоящий из столбца INT ( BlogStoryNumber) и столбец DATETIME (CreatedDateTime );
  • использование функций сервера для оптимизации соответствующих процессов, и
  • Автор и редактор выводимые Роли .

Видя, что при таком подходе BlogStoryNumberзначение будет продублировано, как только будут добавлены «более новые» версии , и вариант, который вы можете оценить (который очень похож на упомянутые в предыдущем разделе), устанавливает BlogStoryPK состоящий из колонн BlogStoryNumberи VersionCode, таким образом , вы могли бы однозначно идентифицировать каждую версию в виде BlogStory . И вы можете попробовать с комбинацией BlogStoryNumberи VersionNumberтоже.

Аналогичный сценарий

Вы можете найти мой ответ на этот вопрос помощи, поскольку я также предлагаю включить временные возможности в соответствующей базе данных для решения сопоставимого сценария.

MDCCL
источник
2

Одним из вариантов является использование версии нормальной формы (VNF). Преимущества включают в себя:

  • Текущие данные и все прошлые данные находятся в одной таблице.
  • Тот же запрос используется для извлечения текущих данных или данных, которые были текущими на любую конкретную дату.
  • Ссылки внешнего ключа на версионные данные работают так же, как и для неверсионных данных.

Дополнительное преимущество в вашем случае, поскольку версионные данные однозначно определяются путем включения в ключ даты вступления в силу (даты внесения изменения) отдельного поля version_id.

Вот объяснение для очень похожего типа сущности.

Более подробную информацию можно найти в слайд-презентации здесь и не совсем законченный документ здесь

TommCatt
источник
1

Ваше отношение

(story_id, version_id, editor_id, author_id, отметка времени, заголовок, контент, условное обозначение)

не в 3-й нормальной форме. Для каждой версии вашей истории author_id одинаков. Таким образом, вам нужно два отношения, чтобы преодолеть это

(story_id, author_id)
(story_id, version_id, editor_id, отметка времени, заголовок, содержание, приблизительное значение)

Ключ первого отношения - story_idэто ключ второго отношения - это комбинированный ключ (story_id, version_id). Если вам не нравится комбинированный ключ, вы можете использовать только version_idкак ключ

miracle173
источник
2
Похоже, это не решает мою проблему, оно только подчеркивает их
Виктор
Таким образом, он даже не отвечает на вопрос: author_id значение поля повторяется в каждой строке таблицы. Где и как мне его хранить ?
чудо173
2
Я не очень понимаю, что говорится в вашем ответе. Это может быть потому, что я не являюсь носителем английского языка, поэтому не могли бы вы объяснить это более простыми словами, пожалуйста?
Виктор
Это означает, что вы должны избегать повторения числа author_id (если story_id одинаков для двух строк, их author_id также равен) и разбивать вашу таблицу на две таблицы, как описано в моем посте. Таким образом, вы можете избежать повторения author_id.
чудо173