Postgres 9.3: 简单插入操作出现共享锁问题

7

更新:下面可能有解决方案

我有一大堆配置文件,其中包含键值对,我正在尝试将它们推入数据库中。许多键和值在配置文件之间重复,因此我正在使用3个表存储数据。一个用于所有唯一的键值,一个用于所有唯一的键值对,以及一个列出每个文件的所有键/值对。

问题:我正在使用多个并发进程(因此连接)将原始数据添加到数据库中。不幸的是,当尝试向键和值表中添加值时,我遇到了很多检测到的死锁。我尝试了几种不同的插入数据方法(如下所示),但总是最终出现“检测到死锁”错误。

TransactionRollbackError: deadlock detected
DETAIL: Process 26755 waits for ShareLock on transaction 689456; blocked by process 26754. Process 26754 waits for ShareLock on transaction 689467; blocked by process 26755.

我想知道到底是什么导致了这些死锁,并可能指引我找到解决问题的方法。从我正在使用的SQL语句(列在下面)来看,我真的看不出为什么会有任何相互依赖性。

感谢您的阅读!

示例配置文件:

example_key this_is_the_value
other_example other_value
third example yet_another_value

表格定义:

    CREATE TABLE keys (
        id SERIAL PRIMARY KEY,
        hash UUID UNIQUE NOT NULL,
        key TEXT);

    CREATE TABLE values (
        id SERIAL PRIMARY KEY,
        hash UUID UNIQUE NOT NULL,
        key TEXT);

    CREATE TABLE keyvalue_pairs (
        id SERIAL PRIMARY KEY,
        file_id INTEGER REFERENCES filenames,
        key_id INTEGER REFERENCES keys,
        value_id INTEGER REFERENCES values);

SQL语句:

最初,我尝试使用这个语句来避免任何异常:

    WITH s AS (
        SELECT id, hash, key FROM keys
            WHERE hash = 'hash_value';
    ), i AS (
        INSERT INTO keys (hash, key)
        SELECT 'hash_value', 'key_value'
        WHERE NOT EXISTS (SELECT 1 FROM s)
        returning id, hash, key
    )
    SELECT id, hash, key FROM i
    UNION ALL
    SELECT id, hash, key FROM s;

但即使像这样简单的事情也会导致死锁:
    INSERT INTO keys (hash, key)
        VALUES ('hash_value', 'key_value')
        RETURNING id;
  • 在这两种情况下,如果插入的哈希值不唯一而引发异常,我使用存储点回滚更改,并使用另一个语句仅选择我想要的id。
  • 我使用哈希作为唯一字段,因为有些键和值太长以至于无法索引。

带有存储点的 Python 代码(使用 psycopg2)的完整示例:

key_value = 'this_key'
hash_val = generate_uuid(value)
try:
    cursor.execute(
        '''
        SAVEPOINT duplicate_hash_savepoint;
        INSERT INTO keys (hash, key)
            VALUES (%s, %s)
            RETURNING id;
        '''
        (hash_val, key_value)
    )

    result = cursor.fetchone()[0]
    cursor.execute('''RELEASE SAVEPOINT duplicate_hash_savepoint''')
    return result
except psycopg2.IntegrityError as e:
    cursor.execute(
        '''
        ROLLBACK TO SAVEPOINT duplicate_hash_savepoint;
        '''
    )

    #TODO: Should ensure that values match and this isn't just
    #a hash collision

    cursor.execute(
        '''
        SELECT id FROM keys WHERE hash=%s LIMIT 1;
        '''
        (hash_val,)
    )
    return cursor.fetchone()[0]

更新: 所以我在另一个stackexchange网站上找到了线索:

具体来说:

UPDATE、DELETE、SELECT FOR UPDATE和SELECT FOR SHARE命令在搜索目标行方面的行为与SELECT相同:它们只会找到作为命令开始时间1提交的目标行。但是,这样的目标行可能已经被另一个并发事务更新(或删除或锁定)了,在找到它时。在这种情况下,应该更新者将等待第一个更新事务提交或回滚(如果它仍在进行中)。如果第一个更新程序回滚,则其效果将被否定,第二个更新程序可以继续更新最初找到的行。如果第一个更新程序提交,则第二个更新程序将忽略该行(如果第一个更新程序删除它),否则它将尝试将其操作应用于该行的更新版本。

虽然我仍然不确定协同依赖关系在哪里,但是处理大量键/值对而不提交可能会导致类似的情况。如果我在添加每个单独的配置文件后提交,死锁就不会发生。

2个回答

13

看起来您处于以下情况:

  1. 要插入的表有一个主键(或任何一种唯一索引)。
  2. 在一个事务中执行了多个INSERT操作(而不是在每个操作之后立即提交)
  3. 要插入的行是随机顺序(与主键无关)的。
  4. 这些行是在并发事务中插入的。

这种情况会导致死锁的出现:

假设有两个会话,每个会话都开始了一个事务。

  1. 会话#1:插入PK为'A'的行
  2. 会话#2:插入PK为'B'的行
  3. 会话#1:尝试插入PK为'B'的行 => 会话#1等待直到会话#2提交或回滚
  4. 会话#2:尝试插入PK为'A'的行 => 会话#2等待会话#1。

不久之后,死锁检测器意识到两个会话现在彼此等待,然后用致命的死锁检测错误终止其中一个会话。

如果您处于这种情况,则最简单的解决方法是在插入新条目后进行COMMIT,在尝试将任何新行插入表之前。


1
另一种选择是始终以相同的顺序插入值。 - user330315
这种情况会发生在使用自增主键插入时吗?如果是这样,您无法对它们进行排序,因此您必须逐个插入,或者使用可串行化隔离并在数据库错误时重试。是这样吗? - Christian Long
1
@ChristianLong:使用基于序列的自增主键是不可能发生冲突的,因为不同会话之间不存在冲突值。但如果除主键以外还存在其他唯一索引,并且多个会话想要以交叉顺序插入相同的值,则仍然可能发生冲突,如答案所示。 - Daniel Vérité
1
@ChristianLong:你提到的解决方案确实可以绕过死锁。我猜在正常工作负载下死锁很少发生,所以你也可以尝试在死锁发生时重试。 - Daniel Vérité

1

说实话,Postgres以那种死锁而闻名。当不同的工作进程更新有交错关系的实体信息时,我经常遇到这样的问题。最近,我有一个任务要从多个json文件中导入一大批科学论文元数据。我使用joblib通过并行处理同时从几个文件中读取。死锁一直挂在 authors(id bigint primary key, name text) 表上,因为许多文件都包含相同作者的论文,因此会产生插入具有相同作者的情况。我使用了 insert into authors (id,name) values %s on conflict(id) do nothing,但这并没有帮助。我尝试将元组排序后再发送到Postgres服务器,但效果不佳。真正帮助我的是在Redis集合中保留已知作者的列表(可供所有进程访问):

if not rexecute("sismember", "known_authors", author_id):
   # your logic...
   rexecute("sadd", "known_authors", author_id)

我推荐给每个人使用。如果你只能使用Windows,那么请使用Memurai。可悲但事实如此,对于Postgres来说没有太多其他的选择。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接