什么可以允许使用相同的复合键将多个条目输入数据库?

What could allow several entries into the database with identical composite keys?

SELECT user_id FROM contacts WHERE phone = "+18888576309";

    user_id   |     phone     |         created_at         
--------------+---------------+----------------------------
 db0db85f2331 | +18888576309  | 2022-03-22 23:28:04.308612 
 db0db85f2331 | +18888576309  | 2022-03-22 23:28:04.310283 
 db0db85f2331 | +18888576309  | 2022-03-22 23:28:04.311891 

这看起来并不奇怪,直到您意识到模型是这样定义的:

class Contact(db.Model):
    __tablename__ = 'contacts'
    user_id = db.Column(VARCHAR(16), primary_key=True)
    phone = db.Column(VARCHAR(64), primary_key=True)
    created_at = db.Column(DateTime)

    def __init__(self, user_id, phone):
        self.user_id = user_id
        self.contact_phone = phone
        self.created_at = datetime.datetime.utcnow()

注意:这里定义了两个主键(making,afaik,复合键)。他们是 user_idphone.

我已经尝试了比我愿意承认的时间更长的时间来寻找这可能发生的来源,但互联网似乎没有任何关于这种可能性的信息(实际上有几篇文章说这个是不可能的!)。唯一不寻常的事情,我相信这是问题的根源(尽管这纯粹是一种直觉)是保存这些对象的方法使用 Session.bulk_save_objects()。到目前为止我有:

这种事情怎么会发生?我一直认为,由于某些基本的数据库逻辑,具有相同主键的行不可能存在,而且可能发生这种情况的唯一方法是,如果两个不同的进程同时向数据库添加相同的行。

There are two primary keys defined here (making, afaik, a composite key).

如果(user_id, phone)上确实有PK,那么你的输出就没有任何意义。 UNIQUE 索引(支持 PRIMARY KEYUNIQUE 约束)始终强制执行。

首先要确保你没有找错树。是否有多个名为“联系人”或“联系人”的 table?在多个模式中?

检查:

SELECT oid, relnamespace::regnamespace AS schema, relname, relkind
FROM   pg_class
WHERE  relname ILIKE 'contacts';

参见:

  • Are PostgreSQL column names case-sensitive?
  • How to check if a table exists in a given schema

然后用 \d contacts 检查 psql 中实际的 table 定义 - 或者任何实际的,正确的 fully-qualified tablename 是。

如果这不能消除迷雾(就像它可能会的那样),您一定是在查看损坏的索引。尽快用 REINDEX 修复:

REINDEX TABLE contacts;

或者,至少,只有一个索引:

REINDEX INDEX contacts_pkey;  -- use actual name

或者,更激进地,使用 VACUUM FULL(从头重写 table 和所有索引):

VACUUM FULL contacts;

两者都阻止并发访问。

我自己从未见过损坏的索引,但它确实发生了 - 特别是在旧版本 and/or 有故障的硬件上。最好查明原因...