【问题标题】:Graceful Primary Key Error handling in Python/psycopg2Python/psycopg2 中的优雅主键错误处理
【发布时间】:2012-01-19 20:07:06
【问题描述】:

使用 Python 2.7 和

在 [150] 中:psycopg2.版本 Out[150]: '2.4.2 (dt dec pq3 ext)'

我有一个简单的 python 脚本来处理事务并将数据写入数据库。偶尔会有一个违反我的主键的插入。这很好,我只是希望它忽略该记录并继续愉快地前进。我遇到的问题是 psycopg2 主键错误正在中止整个事务块,并且错误后的所有插入都失败了。这是一个示例错误

ERROR: duplicate key value violates unique constraint "encounter_id_pkey"
DETAIL:  Key (encounter_id)=(9012235) already exists.

这是在下一个插入。不违规。

Inserting: 0163168~9024065
ERROR: current transaction is aborted, commands ignored until end of transaction block

每次插入都会重复第二个错误。这是一个简化的循环。我正在循环遍历 pandas 数据框,但它可以是任何循环。

conn = psycopg2.connect("dbname='XXXX' user='XXXXX' host='XXXX' password='XXXXX'")

cur = conn.cursor()

for i, val in df2.iteritems():
    try:
        cur = conn.cursor()
        cur.execute("""insert into encounter_id_table (
        encounter_id,current_date  )
        values       
        (%(create_date)s, %(encounter_id)s ) ;""",
        'encounter_id':i.split('~')[1],  
        'create_date': datetime.date.today() })           
        cur.commit()
        cur.close() 
    except Exception , e:
        print 'ERROR:', e[0]
        cur.close()
 conn.close()   

同样,基本思想是优雅地处理错误。在皇家海军纳尔逊上将的格言中:“该死的演习直接针对他们”。或者在我们的例子中,该死的错误直接指向他们。”我想通过在每个插入上打开一个光标,我将重置事务块。我不想仅仅因为主键错误而不得不重置连接。是有什么我遗漏的吗?

感谢您抽出宝贵时间。

约翰

【问题讨论】:

    标签: python sql postgresql psycopg2


    【解决方案1】:

    首先:CURRENT_DATE 是每个 SQL 标准以及 PostgreSQL 中的保留字。如果没有双引号,就不能将其用作标识符。我强烈建议不要使用它。在我的示例中,我将该列重命名为 curdate

    接下来,我不是 python 语法方面的专家,但您似乎颠倒了插入列的顺序:

    (%(create_date)s, %(encounter_id)s )
    

    应该是:

    ( %(encounter_id)s, %(create_date)s)
    

    对于您的主要问题:您可以通过在插入命令中使用密钥之前检查密钥是否已经在表中来完全避免该问题:

    INSERT INTO encounter_id_table (encounter_id, curdate)
    SELECT 1234, now()::date
    WHERE  NOT EXISTS (SELECT * FROM encounter_id_table t
                       WHERE t.encounter_id = 1234);
    

    在 Python 语法中,应该是:

    cur.execute("""INSERT INTO encounter_id_table (encounter_id, curdate)
        SELECT %(encounter_id)s, %(create_date)s,
        WHERE  NOT EXISTS (
               SELECT * FROM encounter_id_table t
               WHERE t.encounter_id = %(encounter_id)s);""",
      {'encounter_id':i.split('~')[1],  
      'create_date': datetime.date.today()})       
    

    【讨论】:

    • 除非您专门将隔离级别设置为更严格的级别,否则使用 WHERE NOT EXISTS 语法仍会产生竞争条件。
    • @lusional:在 Postgres 9.5 或更高版本中,您最好使用 INSERT .. ON CONFLICT DO NOTHING。比较:stackoverflow.com/questions/17267417/…。将隔离级别更改为Serializable 会更加昂贵。
    • 同意 - 在可能的情况下使用 INSERT .. ON CONFLICT 语法要好得多。如果处理不当,竞争条件会很糟糕。
    【解决方案2】:

    您应该在出错时回滚事务。

    我在下面的代码中又添加了一个try..except..else 构造,以显示发生异常的确切位置。

    try:
        cur = conn.cursor()
    
        try:
            cur.execute("""insert into encounter_id_table (
                encounter_id,current_date  )
                values       
                (%(create_date)s, %(encounter_id)s ) ;""",
                'encounter_id':i.split('~')[1],  
                'create_date': datetime.date.today() })
        except psycopg2.IntegrityError:
            conn.rollback()
        else:
            conn.commit()
    
        cur.close() 
    except Exception , e:
        print 'ERROR:', e[0]
    

    【讨论】:

    • 这几乎就是答案。但是我需要在这里调用连接对象(conn)的rollback()方法。游标只有提交没有回滚。谢谢
    • 更多方法错误。您必须在连接上调用 commit 而不是光标。
    • 我想知道提交和回滚是连接方法。认为有人应该编辑我的代码以更正它。遗憾的是,我对直接使用 psycopg 不是很熟悉,因为我通常使用 ORM 或非关系数据库。
    • 如果您必须执行上述操作,您宁愿希望自动提交连接,而忽略错误。
    • FWIW,您可以调用cur.execute("COMMIT;")cur.execute("ROLLBACK;") 而不是调用连接对象上的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-29
    • 2015-05-15
    • 1970-01-01
    相关资源
    最近更新 更多