Postgres 9.3:简单INSERT的Sharelock问题

JBe*_*Fat 7 postgresql database-deadlocks

更新:以下潜在解决方案

我有一大堆配置文件,包括我想要推送到数据库中的键/值对.在配置文件中重复了很多键和值,因此我使用3个表存储数据.一个用于所有唯一键值,一个用于所有唯一对值,另一个列出每个文件的所有键/值对.

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

TransactionRollbackError:检测到死锁
详细信息:进程26755在事务689456上等待ShareLock; 由进程26754阻止.进程26754在事务689467上等待ShareLock; 被进程26755阻止.

我想知道是否有人可以对可能导致这些死锁的确切原因有所了解,并可能指出我采取某种方法解决问题.看看我正在使用的SQL语句(下面列出),我真的不明白为什么会有任何共同依赖.

谢谢阅读!

示例配置文件:

example_key this_is_the_value
other_example other_value
third example yet_another_value
Run Code Online (Sandbox Code Playgroud)

表定义:

    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);
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

但即使是这样简单的事情也会导致死锁:

    INSERT INTO keys (hash, key)
        VALUES ('hash_value', 'key_value')
        RETURNING id;
Run Code Online (Sandbox Code Playgroud)
  • 在这两种情况下,如果因为插入的哈希值不唯一而抛出异常,我会使用保存点来回滚更改,而使用另一个语句来选择我之后的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]
Run Code Online (Sandbox Code Playgroud)

更新: 所以我相信我在另一个stackexchange网站上提示:

特别:

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

虽然我仍然不确定共同依赖的位置,但似乎在没有提交的情况下处理大量的键/值对可能会导致类似这样的事情.果然,如果我在添加每个单独的配置文件后提交,则不会发生死锁.

Dan*_*ité 12

看起来你正处于这种情况:

  1. INSERT的表有一个主键(或任何类型的唯一索引).
  2. 该表中的几个INSERT在一个事务中执行(而不是在每个事务之后立即提交)
  3. 要插入的行以随机顺序(关于主键)
  4. 行插入并发事务中.

这种情况会造成以下死锁机会:

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

  1. 会话#1:插入带有'A'的行
  2. 会话#2:用PK'B'插入行
  3. 会话#1:尝试使用PK'B'插入行=>会话#1等待会话#2提交或回滚
  4. 会话#2:尝试插入带有'A'的行=> =会话#2等待会​​话#1.

此后不久,死锁检测器意识到两个会话现在正在等待彼此,并且终止其中一个会话时发现致命的死锁检测错误.

如果您在这种情况下,最简单的解决方案是在插入新条目之后进行COMMIT,然后再尝试向表中插入任何新行.