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

当我决定为博客上的每个故事实现某种版本控制系统后,我想到的第一件事就是创建一个不同的表来保存编辑内容;之后,我以为可以修改现有表以保留版本而不是edits。这是我想到的结构:

| 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初始表的字段在表中是UNIQUE。现在,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编写的,最初由@Scott在他的回答中使用PHP编写。

字段author_ideditor_id 可以不同,因为有些用户具有编辑任何人的故事的足够权限。

Answers:


23

从概念的角度分析场景(呈现与称为时间数据库的主题相关的特征),可以确定:(a)“当前” 博客故事版本和(b)“过去” 博客故事版本,尽管非常类似,是不同类型的实体。

除此之外,在逻辑抽象级别上工作时,必须将不同种类的事实(用行表示)保留在不同的表中。在考虑的情况下,即使非常相似,(i)关于“当前” 版本的事实也与(ii)关于“过去” 版本的事实不同。

因此,我建议通过两个表来管理情况:

  • 一个专门用于Blog Stories的“当前”或“当前” 版本的,以及

  • 对于所有“先前”或“过去” 版本,一个是单独的,但也与另一个链接;

每个(1)列的数量略有不同,(2)不同的约束组。

回到概念层,我认为-在您的业务环境中- 作者编辑者是可以被描述为可由用户扮演的角色的概念,而这些重要方面取决于数据派生(通过逻辑级别的操作)和解释(由Blog Stories的读者和作家在计算机信息系统的外部级别借助一个或多个应用程序进行)。

我将在下面详细介绍所有这些因素和其他相关要点。

商业规则

根据我对您的需求的理解,以下业务规则公式(根据相关实体类型及其相互关系的种类放在一起)对于建立相应的概念架构特别有用:

  • 一个用户写道零一或一对多BlogStories
  • 一个BlogStory持有零一或一对多BlogStoryVersions
  • 一个用户写了一个零个或多个BlogStoryVersions

信息库IDEF1X图表

因此,为了借助于图形装置来解释我的建议,我已经创建了一个样本IDEF1X 一个是从上面配制的商业规则和其他特征,似乎相关衍生图。如图1所示:

图1-Blog Story Versions IDEF1X图表

为什么将BlogStoryBlogStoryVersion概念化为两种不同的实体类型?

因为:

  • 一个BlogStoryVersion实例(即“过去”之一)始终保持为一个值UpdatedDateTime财产,而BlogStory发生(即“存在”一个)从不保存它。

  • 此外,这些类型的实体由两个不同的属性集的值唯一标识:BlogStoryNumber(对于BlogStory出现),以及BlogStoryNumber加上CreatedDateTime(对于BlogStoryVersion实例)。


一个 用于信息建模集成定义 IDEF1X)是被确立为一个非常可取的数据建模技术标准是由美国在1993年12月美国国家标准与技术研究院(NIST)。它基于关系模型唯一发起人(即 EF Codd博士)撰写的早期理论材料。陈PP博士开发的关于数据的实体关系视图;以及由Robert G. Brown创建的逻辑数据库设计技术。


说明性逻辑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.
);

运行于MySQL 5.6的SQL Fiddle中进行了测试。

BlogStory

如您在演示设计中所见,我已经BlogStory使用INT数据类型定义了PRIMARY KEY(为简便起见,PK)列。在这方面,您可能想修复内置的自动过程,该过程会在每次插入行时为此类列生成并分配一个数值。如果您不介意在这组值中偶尔留下空白,那么可以使用MySQL环境中常用的AUTO_INCREMENT属性。

输入所有单独的BlogStory.CreatedDateTime数据点时,可以使用NOW()函数,该函数在确切的INSERT操作瞬间返回数据库服务器中当前的日期和时间值。对我来说,这种实践绝对比使用外部例程更合适,而且更不容易出错。

就像在(现在删除的)注释中所讨论的那样,如果您希望避免维护BlogStory.Title重复值的可能性,则必须为此列设置UNIQUE约束。由于给定标题可以由几个(甚至全部)“过去”共享BlogStoryVersions,然后UNIQUE约束应该不会被用于建立BlogStoryVersion.Title列。

如果您需要提供“软”或“逻辑” DELETE功能,我会列出BIT(1)BlogStory.IsActive类型的列(尽管也可以使用TINYINT)。

有关BlogStoryVersion表格的详细信息

另一方面,BlogStoryVersion表的PK 由(a)BlogStoryNumber和(b)列组成,该列CreatedDateTime当然标记了BlogStory行经过INSERT 的确切时刻。

BlogStoryVersion.BlogStoryNumber除作为PK的一部分外,还被约束为reference的FOREIGN KEY(FK)BlogStory.BlogStoryNumber,该配置在这两个表的行之间强制实施了引用完整性。在这方面,BlogStoryVersion.BlogStoryNumber不需要自动生成a,因为将其设置为FK时,必须从已包含在相关BlogStory.BlogStoryNumber副本中的值中“提取”插入此列的值。

BlogStoryVersion.UpdatedDateTime如预期的那样,该列应保留BlogStory修改行并因此将其添加到BlogStoryVersion表中的时间点。因此,您也可以在这种情况下使用NOW()函数。

所述区间之间理解BlogStoryVersion.CreatedDateTimeBlogStoryVersion.UpdatedDateTime表达整个时期,在此期间一个BlogStory行是“存在”或“电流”。

Version列注意事项

它可以认为是有用的BlogStoryVersion.CreatedDateTime作为保持代表一个特定的“过去”值的列版本 A的BlogStory。我认为这比a VersionId或更为有益VersionCode,因为从某种意义上说,它倾向于用户友好,因为人们往往更熟悉时间概念。例如,博客作者或读者可以通过类似于以下方式引用BlogStoryVersion

  • “我希望看到具体的版本中的BlogStory通过识别号码 1750这是创建26 August 20159:30”。

作者编辑的角色:数据推导和解释

通过这种方法,可以很容易地分辨谁拥有“原始”的AuthorId一个具体的BlogStory选择“最早的” 版本有一定的BlogStoryIdBlogStoryVersion凭借应用的表MIN()函数BlogStoryVersion.CreatedDateTime

这样,所有 “以后”或“成功” 版本行中BlogStoryVersion.AuthorId包含的每个值自然会指示出相应版本作者标识符,但是也可以说,该值同时表示由相关用户担任BlogStory的“原始” 版本编辑者所扮演的角色

是的,一个给定的AuthorId值可以由多BlogStoryVersion行共享,但这实际上是一条信息,可以告诉每个Version非常重要的信息,因此重复上述数据就不是问题。

DATETIME列的格式

至于DATETIME数据类型,是的,您是对的,“ MySQL检索并以' YYYY-MM-DD HH:MM:SS'format格式显示DATETIME值,但是您可以放心地以这种方式输入相关数据,而当您必须执行查询时,只需利用内置的DATE和TIME函数,除其他外,以适合您的用户的格式显示相关值。或者,您当然可以通过您的应用程序代码执行这种数据格式化。

BlogStoryUPDATE操作的含义

每次BlogStory一行发生UPDATE时,您必须确保在修改发生之前将“存在”的相应值插入BlogStoryVersion表中。因此,我强烈建议在一次酸交易中完成这些操作,以确保将它们视为不可分割的工作单元。您也可以雇用TRIGGERS,但是可以这样说,它们会使事情变得不整洁。

介绍VersionIdVersionCode

如果您出于业务状况或个人喜好而选择合并BlogStory.VersionIdBlogStory.VersionCode列来区分BlogStoryVersions,则应考虑以下可能性:

  1. VersionCode在(i)整个BlogStory表格以及(ii)中可能要求A 为唯一BlogStoryVersion

    因此,您必须实施经过仔细测试且完全可靠的方法,以便生成和分配每个Code值。

  2. 也许这些VersionCode值可以在不同的BlogStory行中重复,但绝不能与同一个重复BlogStoryNumber。例如,您可能有:

    • 一个BlogStoryNumber 3-版本83o7c5c,同时,
    • 一个BlogStoryNumber 86-版本83o7c5c
    • 一个BlogStoryNumber 958-版本83o7c5c

后面的可能性开辟了另一种选择:

  1. 保持一个VersionNumberBlogStories,所以有可能是:

    • BlogStoryNumber- 23版本1, 2, 3…
    • BlogStoryNumber- 650版本1, 2, 3…
    • BlogStoryNumber- 2254版本1, 2, 3…
    • 等等

在一个表中保存“原始”和“后续”版本

虽然维护所有BlogStoryVersions同一个人基地台是可能的,我建议不要做,因为你会被混合两种截然不同的(概念)类型的事实,因此,其对不良副作用的

  • 数据约束和操作(在逻辑级别),以及
  • 相关处理和存储(在物理层)。

但是,如果您选择遵循该行动方案,则仍然可以利用上面详述的许多想法,例如:

  • 一个复合 PK由一个INT柱(的BlogStoryNumber)和DATETIME柱(CreatedDateTime);
  • 服务器功能的使用,以优化相关流程,以及
  • 作者编辑器导出角色

看到,通过采用这种方法,BlogStoryNumber将在添加“较新” 版本后立即复制一个值,并且您可以评估的一个选项(与上一节中提到的选项非常相似)正在建立PK列组成,并以这种方式,你将能够唯一标识每个版本 A的BlogStory。你还可以用组合尝试和太。BlogStoryBlogStoryNumberVersionCodeBlogStoryNumberVersionNumber

类似情况

您可能会找到我对这个帮助问题的答案,因为我还建议在相关数据库中启用临时功能以处理类似的情况。


2

一种选择是使用版本标准格式(vnf)。优点包括:

  • 当前数据和所有过去数据位于同一表中。
  • 使用相同的查询来检索当前数据或任何特定日期的最新数据。
  • 对版本化数据的外键引用与未版本化数据的外键引用相同。

对于您而言,这还有一个好处,因为版本化数据通过将生效日期(更改日期)作为密钥的一部分来唯一标识,因此不需要单独的version_id字段。

是非常相似类型的实体的说明。

更多的细节可以在幻灯片演示文稿中找到这里和一个不很完成文档在这里


1

您的关系

(story_id,version_id,editer_id,author_id,时间戳,标题,内容,coverimg)

不是第三范式。对于您故事的每个版本,author_id都是相同的。所以你需要两个关系来克服这个

(story_id,author_id)
(story_id,version_id,editer_id,时间戳,标题,内容,coverimg)

第一个关系story_id的关键是,第二个关系的关键是组合键(story_id, version_id)。如果您不喜欢组合键,则只能将其version_id用作键


2
这似乎并不能解决我的问题,只是强调了他们
Victor

因此,它甚至无法回答查询- author_id 字段值在表的每一行中都在重复。我应该在哪里以及如何保存它
miracle173

2
我不太了解您的回答。可能是因为我不是英语母语人士,所以请您尝试用更多简单的单词来解释一下吗?
维克多

这意味着您应该避免重复author_id号(如果两行的story_id相等,它们的author_id也相等),并按照我的文章中的描述将表拆分成两个表。因此,您可以避免重复author_id。
miracle173
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.