Postgresql Postgres 9.3:简单插入的共享锁问题

Postgresql Postgres 9.3:简单插入的共享锁问题,postgresql,database-deadlocks,Postgresql,Database Deadlocks,更新:下面是潜在的解决方案 我有一个由键/值对组成的大型配置文件库,我正试图将其推入数据库。许多键和值在配置文件中重复,所以我使用3个表存储数据。一个用于所有唯一键值,一个用于所有唯一对值,另一个用于列出每个文件的所有键值对 问题: 我使用多个并发进程(以及连接)将原始数据添加到数据库中。不幸的是,在尝试向键和值表添加值时,我检测到很多死锁。我尝试了几种插入数据的不同方法(如下所示),但最终总是出现“死锁检测”错误 TransactionRollbackError:检测到死锁详细信息:进程267

更新:下面是潜在的解决方案

我有一个由键/值对组成的大型配置文件库,我正试图将其推入数据库。许多键和值在配置文件中重复,所以我使用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
    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);
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]
表格定义:

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);
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]
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);
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]
最初,我试图使用此语句来避免任何异常:

    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
  • 我对unique字段使用哈希,作为一些键和值 太长,无法编制索引
带有保存点的python代码(使用psycopg2)的完整示例:

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);
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]
更新: 所以我相信我有一个暗示

具体来说:

更新、删除、选择用于更新和选择用于共享命令 在搜索目标行方面的行为与SELECT相同:它们 将仅查找在命令start时提交的目标行 时间1。但是,这样的目标行可能已经更新(或 被另一个并发事务删除或锁定) 建立在这种情况下,可能的更新程序将等待第一次更新 正在更新要提交或回滚的事务(如果仍处于 进展)。如果第一个更新程序回滚,则其效果为 否定,第二个更新程序可以继续更新 原来发现了一排。如果第一个更新程序提交,则第二个更新程序 如果第一个更新程序删除了it2,则将忽略该行,否则 将尝试将其操作应用于行的更新版本


虽然我仍然不能确切地确定相关性在哪里,但似乎在不提交的情况下处理大量键/值对可能会导致类似的结果。当然,如果我在添加每个单独的配置文件后提交,死锁不会发生。

看起来您处于这种情况:

  • 要插入的表有一个主键(或任何类型的唯一索引)
  • 在一个事务中执行对该表的多次插入(而不是在每次插入后立即提交)
  • 要插入的行以随机顺序出现(关于主键)
  • 这些行插入到并发事务中
  • 这种情况会造成以下僵局:

    假设有两个会话,每个会话启动一个事务

  • 会话#1:插入主键为“A”的行
  • 会话2:插入主键为“B”的行
  • 会话1:尝试插入主键为“B”的行 =>将会话#1置于等待状态,直到会话#2提交或回滚
  • 会话2:尝试插入主键为“A”的行 =>会话2被置于等待会话1
  • 此后不久,死锁检测器会意识到两个会话现在都在等待对方,并使用致命的死锁检测错误来终止其中一个会话


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

    另一种方法是始终以相同的顺序插入值。使用自动递增主键插入时会发生这种情况吗?如果是这样,您就不能对它们进行排序,所以您必须一次插入一个,或者使用可序列化隔离并在数据库出错时重试。是吗?@ChristianLong:基于序列的自动递增PK不能发生这种情况,因为会话之间不能有冲突的值。如果PK之外还存在另一个唯一索引,并且几个会话希望按答案中所示的顺序交叉插入相同的值,则仍然可能发生这种情况。@ChristianLong:您提到的解决方案确实可以避免死锁。我想如果死锁在正常工作负载下很少发生,您也可以重试死锁。