【问题标题】:Unique constraint with soft deleted rows excluded排除软删除行的唯一约束
【发布时间】:2012-10-26 23:05:33
【问题描述】:

我们有一个带有唯一约束的表,用于一个用户留下的反馈,另一个反馈,与销售有关。

ALTER TABLE feedback
ADD CONSTRAINT unique_user_subject_and_sale
UNIQUE (user_id, subject_id, sale_id)

这可确保我们不会意外收到重复的反馈行。

目前,我们有时会硬删除错误留下的反馈,让用户再次离开。我们要改为软删除:

ALTER TABLE feedback
ADD COLUMN deleted_at timestamptz

如果deleted_at IS NOT NULL,请考虑删除反馈,尽管我们的数据库中仍有审计跟踪(并且可能会向站点管理员显示它的影子)。

当我们像这样使用软删除时,如何保持我们的唯一约束?是否可以不使用更通用的CHECK() 约束进行聚合检查(我从未尝试过使用这样的检查约束)。

这就像我需要在约束中附加一个 WHERE 子句。

【问题讨论】:

    标签: postgresql database-design constraints


    【解决方案1】:

    您的唯一索引,后来被删除。

    CREATE UNIQUE INDEX feedback_unique_user_subject_and_sale_null
    ON feedback(user_id, subject_id, sale_id)
    WHERE deleted_at IS NULL
    

    您的唯一索引至少有两个可能会给您带来麻烦的副作用。

    1. 在其他表中,您不能设置引用“反馈”的外键约束。外键引用需要将某些列组合声明为 primary keyunique
    2. 您的唯一索引允许在“deleted_at”时间戳中不同的多行。因此,可能最终得到如下例所示的行。这是否是一个问题取决于应用程序。

    例子

    user_id  subject_id  sale_id  deleted_at
    --
    1        1           1        2012-01-01 08:00:01.33
    1        1           1        2012-01-01 08:00:01.34
    1        1           1        2012-01-01 08:00:01.35
    

    PostgreSQL 将这种索引记录为部分索引,如果您需要在某个时候谷歌它。其他平台对它使用不同的术语——过滤索引就是其中之一。您可以通过一对部分索引将问题限制在一定范围内。

    CREATE UNIQUE INDEX feedback_unique_user_subject_and_sale_null
    ON feedback(user_id, subject_id, sale_id)
    WHERE deleted_at IS NULL
    
    CREATE UNIQUE INDEX feedback_unique_user_subject_and_sale_not_null
    ON feedback(user_id, subject_id, sale_id)
    WHERE deleted_at IS NOT NULL
    

    但是我认为没有理由去这么多麻烦,特别是考虑到外键的潜在问题。如果你的桌子是这样的

    create table feedback (
      feedback_id integer primary key,
      user_id ...
      subject_id ...
      sale_id ...
      deleted_at ...
      constraint unique_user_subj_sale 
        unique (user_id, subject_id, sale_id)
    );
    

    那么您所需要的就是对 {user_id, subject_id, sale_id} 的唯一约束。您可以进一步考虑让所有删除都使用“deleted_at”列,而不是进行硬删除。

    【讨论】:

    • 我没有发布完整的架构,但反馈有一个整数(串行)主键。此外,user_id、sale_id 和 subject_id 被声明为外键(并具有索引)。我们只需要强制唯一性作为附加约束。
    • 是的,唯一索引允许仅在 deleted_at 时间戳中不同的行很好......这是我试图实现的目标;)
    • 如果您有 a) 串行主键和 b) 如您所描述和我测试过的部分索引,您仍然对引用“反馈”的其他表存在潜在问题。假设我在上面的示例中发布的三行具有串行主键 1、2 和 3。引用表为其外键选择哪一个?所有引用表如何知道选择 same 值?
    • 如果有帮助,我们会在大多数搜索中包含WHERE deleted_at IS NULL。在这种情况下,每个键只能匹配一行。
    • 在您的更新中,这是否意味着即使进行了软删除,您也无法为相同的销售、主题和用户添加新的反馈?外键对我们也很重要。我们过去曾遇到过大问题,完全依赖于应用程序级别的检查。
    【解决方案2】:

    尽管 PostgreSQL 文档建议不要使用唯一索引而不是约束(如果要点是有约束),但您似乎可以这样做

    CREATE UNIQUE INDEX feedback_unique_user_subject_and_sale
    ON feedback(user_id, subject_id, sale_id)
    WHERE deleted_at IS NULL
    

    【讨论】:

    • 称为“部分索引”。有关重要限制,请参阅@Catcall 的帖子。
    【解决方案3】:

    您可以使用 deleted_at 签名创建约束。

    ALTER TABLE feedback
    ADD CONSTRAINT unique_user_subject_and_sale
    UNIQUE (user_id, subject_id, sale_id, deleted_at)
    

    这不会产生像唯一索引这样的问题。

    唯一的问题是删除用户反馈,创建一个新的,然后在同一时间再次删除它。

    考虑到列的准确性,用户必须适应 1 微秒。这是不可能的,即使他做到了,这些请求之间的时间肯定会大于1微秒。

    唯一的问题是空值不是唯一的。所以它不会起作用。

    但是您可以为未删除的行添加一个默认值作为您可以存储在该列中的最低值。

    如果不能接受deleted_at的默认值,如果对象没有被删除,可以考虑添加deleted_token列,如果删除值则为其生成key,对于未删除的对象,保留0 或其他。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-15
      • 2020-01-27
      • 1970-01-01
      • 2012-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多