【问题标题】:Best practices to enable/disable/delete database rows and its references?启用/禁用/删除数据库行及其引用的最佳实践?
【发布时间】:2018-06-12 22:33:43
【问题描述】:

处理行及其引用表的删除或启用/删除的最佳做法是什么?

例如,假设我有一个非常简单的“论坛”应用程序。

我有一个表 users 包含我的 webapp 帐户,threads 包含用户创建的线程,还有一个表 comments 包含 cmets 用户对线程的评论。

现在,假设在注册时我想在激活用户帐户之前验证用户的电子邮件。最好的方法是什么?什么是最佳实践?也许通过只返回带有字段is_active=true 的行的视图?使用 2 个单独的表,例如 pre_users(包含仍有待验证的用户)和 users(已验证的用户)?

同样,您将如何处理想要暂停其帐户的用户?它的线程和cmets?您会添加另一个标志is_suspended,并更新视图以考虑该标志吗?如果它不是一个视图,而是 2 个单独的表,我该如何处理引用?

另外,删除一个线程。假设在线程删除时我不希望线程被实际删除,因为我不希望发布 cmets 的用户突然看不到他们的 cmets。怎么处理?

解决这类问题的最佳做法是什么?

【问题讨论】:

    标签: sql database database-design data-modeling


    【解决方案1】:

    对于活动状态不影响唯一性的表(例如,用户由用户名或电子邮件地址标识,同一事物不可能有活动和非活动版本)我使用可为空的日期时间字段代表一种状态。例如,对于您的 users 表,我将有一个 verify_at 列,该列最初设置为 null,并设置为用户验证其帐户时的当前日期/时间。用户帐户暂停也可以这样做。如果用户重新激活他们的帐户,我们只需将suspended_at 字段设置为null。

    如果您需要比简单的是/否更多的状态值,我会为状态值和更改日期/时间使用单独的字段。

    如果您想跟踪更新历史记录(例如激活/暂停),最好在单独的表中进行。在这种情况下,您可以从 users 表中引用当前的激活记录,这将比查询状态列更有效地使用表的索引。

    在某些情况下,状态会影响唯一性。例如,在论坛中,用户可能能够存储任意数量的签名或头像,但一次可能只有一个处于活动状态。在这些情况下,最好将当前数据与历史数据分开。例如,用于签名的表,以及在用户表中引用活动签名的外键。 您应该能够更新任何行的状态,而无需关心其他行中的值。

    避免级联状态,它们会破坏子项的先前状态,并使状态字段的使用在许多情况下几乎毫无意义。而是在查询时加入父表以过滤顶级状态。

    最后,数据中的状态是向时间数据建模方向迈出的一步。我建议阅读该主题。

    【讨论】:

    • 感谢您的回答。您如何处理可能存在非活动行的表上的数据检索?说一个用户表。每次查询时,您都必须记住添加“where status=active”。这不是很容易出错吗?我应该处理测试,而不是像只过滤活跃用户的视图那样深入研究,这应该是一个问题吗?另外,假设有一个用户制作的 cmets 的评论表。怎么对付他们?当你禁用一个用户时,你也必须禁用它们,所以同样的数据检索问题也会出现在这里。
    • 在实践中,记住添加“where status=active”并没有太多问题,但是视图应该可以很好地简化这些场景。对于 Comment 表,我建议加入 Users 以过滤用户的状态,而不是将用户的状态传播到 cmets。原因是评论可能会受到多种状态的影响——它自己的、用户、线程、板等。不同的查询会有不同的要求,例如在某些情况下,我们可能希望显示非活动线程,但只能显示来自活动用户的 cmets。最好将该逻辑留给查询/开发。
    • 当然。但是当你有很多用户和很多帖子时,加入可能会变得非常昂贵。当用户和 cmets 之间有许多表时尤其如此(例如评论没有用户的外键,但通过许多其他表相关。
    • 我先使用最简单的方法进行开发,然后在看到问题时进行调整/优化。如果需要,我会从 cmets 向用户添加一个 FK。我经常做涉及十几个表的连接,其中一些表有数百万条记录,并且仍然可以在 20 毫秒内得到结果。
    【解决方案2】:

    您可能需要更详细地处理您的业务需求,然后我们才能回答所有的变化。

    一般而言,正如您在 cmets 中与 @reaanb 争论的那样,我不会担心连接的性能 - 现代硬件和现代数据库可以处理大量记录。

    我还将专注于在关系模型中对问题进行建模,而不用担心“但包含此检查不是更复杂”。专注于可理解性(设计与要求有多接近?)。我真的不喜欢使用视图来捕获这些东西,因为当底层业务需求发生变化时,它们会使更改变得更加困难。

    根据我的经验,您需要回答的最大问题是“业务需求是否关心随时间的变化”?

    如果答案是“否”,那么您可以使用任何有意义的状态标志。例如,在用户表上,您可能有一个带有“已注册/已验证/已停用/已删除”的状态列。这对代码来说相对简单 - 但您不能轻易回答诸如“此已停用用户在哪一天验证?”或“发布此评论时发布此评论的用户的状态是什么”之类的问题。

    如果需求确实关心时间,我喜欢的模型是在需要了解时间的行中添加“valid_from”和“valid_until”。 “当前”行有一个 null valid_until 列。这使您可以随时了解数据的状态 - 但它确实使您的查询更加复杂,尤其是当您跨多个表连接时。

    但是,这意味着 - 例如 - 您可以允许尚未通过验证的用户发布 cmets,但在他们验证其帐户之前隐藏这些 cmets。

    这也意味着您可以创建报告,显示过去每个日期您在每种状态下拥有多少用户,有多少非活动用户重新激活等。

    通过将“valid_from”和“valid_until”添加到您的“帖子”表中,您可以包含版本控制 - 也许您想显示评论是在帖子的旧版本上发布的?

    最后,在一些复杂的应用程序中,我使用了finite state machine 来管理状态之间的有效转换。这对您的系统来说可能是多余的。

    【讨论】:

      【解决方案3】:

      数据建模中的状态模式
      在这种情况下,我们可以使用我将其命名为数据建模中的状态模式的模式。

      假设我们有一个具有许多状态的实体。例如user 实体,具有pre-registerednormal userdeleted usersuspended user 等。

      再举个例子,用户Post(如 Stackoverflow 帖子)有许多 状态,如 normaldeleted by userdeleted by moderatorduplicatedclosed 等。

      假设我们要为user 实体建模状态。在这种情况下,我们可以使用一个实体来保存所有状态类型(如pre-registerednormal userdeleted usersuspended user)。我们可以将其命名为User_Status_Types 并将所有用户状态类型放入其中。

      因此,另一个实体需要保存任何用户状态。让我将其命名为User_Statuses。它的 F.K 为 UserUser_Status_Types

      通过这种模式,我们可以保存用户的所有状态。

      为了改进模式,我们可以在User 中添加User_Statuses 的F.K,以显示用户的最终状态。 (注意这不是循环依赖陷阱)

      问题 1:如果实体有两种不同类型的状态?
      在这种情况下应该使用这种模式的次数。

      问题 2:如果子实体(具有该实体的 F.K 的任何实体)的状态取决于实体的状态?
      例如,如果我们想要显示已删除用户的 cmets。在这种情况下,我们有两个选择:

      选项1:我们可以写一个用户状态变化的触发器。如果用户状态更改为已删除,那么我们的触发器也会将所有用户 cmets 状态更改为已删除。

      选项 2:在此选项中,评论状态不会改变。 但是,我们可以在 cmets 上的 select 命令中使用额外的条件,并向 cmets 显示其父状态正常。

      问题 3:如果状态变化的顺序很重要,并且我们想在我们的数据模型中对其进行建模。
      在这种情况下,我们可以添加一个名为 User_Status_Types_Sequence 的新实体,它具有来自 User_Status_Types 的 2 个 F.K 作为源和目标。这意味着可以将源状态更改为目标状态。因此,我们可以从数据库中选择序列有效性,然后将其执行到我们的数据库中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-31
        • 2010-10-12
        相关资源
        最近更新 更多