【问题标题】:Continuing a transaction after primary key violation error主键违规错误后继续交易
【发布时间】:2020-02-23 14:24:14
【问题描述】:

我正在将记录从日志文件批量插入到数据库中。偶尔(每千人中有 1 行)其中一行违反主键并导致事务失败。目前,用户必须手动检查导致失败的文件并删除有问题的行,然后再尝试重新导入。鉴于要导入数百个此类文件,这是不切实际的。

我的问题:如何跳过插入违反主键约束的记录,而不必在每行之前执行SELECT 语句来查看它是否已经存在? p>

注意:我知道#1054695 的问题非常相似,但它似乎是 SQL Server 特定的答案,我使用的是 PostgreSQL(通过 Python/psycopg2 导入)。

【问题讨论】:

    标签: sql database postgresql transactions constraints


    【解决方案1】:

    您也可以在事务中使用 SAVEPOINT。

    Pythonish 伪代码是从应用程序端说明的:

    database.execute("BEGIN")
    foreach data_row in input_data_dictionary:
        database.execute("SAVEPOINT bulk_savepoint")
        try:
            database.execute("INSERT", table, data_row)
        except:
            database.execute("ROLLBACK TO SAVEPOINT bulk_savepoint")
            log_error(data_row)
            error_count = error_count + 1
        else:
            database.execute("RELEASE SAVEPOINT bulk_savepoint")
    
    if error_count > error_threshold:
        database.execute("ROLLBACK")
    else:
        database.execute("COMMIT")
    

    编辑:这是一个在 psql 中执行的实际示例,基于文档中示例的细微变化(以“>”为前缀的 SQL 语句):

    > CREATE TABLE table1 (test_field INTEGER NOT NULL PRIMARY KEY);
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "table1_pkey" for table "table1"
    CREATE TABLE
    
    > BEGIN;
    BEGIN
    > INSERT INTO table1 VALUES (1);
    INSERT 0 1
    > SAVEPOINT my_savepoint;
    SAVEPOINT
    > INSERT INTO table1 VALUES (1);
    ERROR:  duplicate key value violates unique constraint "table1_pkey"
    > ROLLBACK TO SAVEPOINT my_savepoint;
    ROLLBACK
    > INSERT INTO table1 VALUES (3);
    INSERT 0 1
    > COMMIT;
    COMMIT
    > SELECT * FROM table1;  
     test_field 
    ------------
              1
              3
    (2 rows)
    

    请注意,值 3 是在错误之后插入的,但仍在同一个事务中!

    SAVEPOINT 的文档位于 http://www.postgresql.org/docs/8.4/static/sql-savepoint.html

    【讨论】:

    • 那不行,当发生错误时,事务被中止并回滚。您需要数据库内的异常处理程序。查询失败:错误:当前事务被中止,命令被忽略,直到事务块结束
    • 会的。这就是 SAVEPOINT 的全部意义所在。为了给出一个具体的例子,我已经编辑了我的答案。
    • ----edit---- 对不起,我错了...真丢脸 ;) 它工作正常,你是对的。
    • 我不确定性能影响。如果您已经在没有保存点的情况下运行它,我认为添加它们并进行测试不会太难。即使每次插入都会影响性能,您也可以将数据加载到记录的“块”中以最小化保存点,只回滚并详细处理失败的块。此外,文档指出具有相同名称的保存点不会被破坏,只会被最近的保存点所掩盖,因此您可能希望在我的示例中的 try 块中的 INSERT 之后添加 RELEASE SAVEPOINT
    • @Jeff,如果是我,我会将整个新文件加载到一个临时表中,然后从目标表中不存在 PK 的临时表中插入所有记录。有几种方法可以做到这一点(NOT IN、LEFT JOIN、EXCEPT),因此根据数据量应该可以采用一种有效的方法。
    【解决方案2】:

    我会使用存储过程来捕获您独特违规的异常。示例:

    CREATE OR REPLACE FUNCTION my_insert(i_foo text, i_bar text)
      RETURNS boolean LANGUAGE plpgsql AS
    $BODY$
    begin   
        insert into foo(x, y) values(i_foo, i_bar);
        exception
            when unique_violation THEN -- nothing
    
        return true;
    end;
    $BODY$;
    
    SELECT my_insert('value 1','another value');
    

    【讨论】:

    • 记录你的异常总是更好的。你可以修改异常博客来记录它并继续。
    • 可以让函数记录异常,没问题。
    【解决方案3】:

    您可以对事务执行rollback 或回滚到引发异常的代码之前的保存点(cr 是光标):

    name = uuid.uuid1().hex
    cr.execute('SAVEPOINT "%s"' % name)
    try:
        # your failing query goes here
    except Exception:
        cr.execute('ROLLBACK TO SAVEPOINT "%s"' % name)
        # your alternative code goes here 
    else:
        cr.execute('RELEASE SAVEPOINT "%s"' % name)
    

    此代码假定有正在运行的事务,否则您将不会收到该错误消息。

    Django postgresql 后端creates cursors 直接来自psycopg。也许将来他们会为 Django 游标创建一个代理类,类似于cursor of odoo。他们用following code扩展光标(self是光标):

    @contextmanager
    @check
    def savepoint(self):
        """context manager entering in a new savepoint"""
        name = uuid.uuid1().hex
        self.execute('SAVEPOINT "%s"' % name)
        try:
            yield
        except Exception:
            self.execute('ROLLBACK TO SAVEPOINT "%s"' % name)
            raise
        else:
            self.execute('RELEASE SAVEPOINT "%s"' % name)
    

    这样,上下文使您的代码更容易,它将是:

    try:
        with cr.savepoint():
            # your failing query goes here
    except Exception:
        # your alternative code goes here 
    

    而且代码更具可读性,因为交易的东西不存在。

    【讨论】:

      【解决方案4】:

      或者您可以使用 SSIS 并让失败的行与成功的行采用不同的路径。

      既然您使用的是不同的数据库,您是否可以将文件批量插入临时表,然后使用 SQL 代码仅选择那些没有现有 ID 的记录?

      【讨论】:

      • 你能详细说明你所说的 SSIS 是什么意思吗?
      • SSIS是SQL Server自带的数据导入工具。我没有发现您正在使用 postgre。它仍然可以为 postgre 完成这项工作,但我不确定您将如何获得它,因为我认为它不附带 SQL Server 的免费版本。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-27
      • 2020-12-12
      • 1970-01-01
      • 2011-04-19
      • 1970-01-01
      相关资源
      最近更新 更多