Django bulk_create,忽略导致IntegrityError的行?

时间:2012-09-16 21:52:54

标签: django postgresql bulk-load

我使用bulk_create将数千或行加载到postgresql DB中。不幸的是,有些行导致IntegrityError并停止bulk_create进程。我想知道是否有办法告诉django忽略这些行并尽可能多地保存批次?

6 个答案:

答案 0 :(得分:16)

现在可以在Django 2.2上实现

Django 2.2从documentationignore_conflicts方法中添加了一个新的bulk_create选项:

  

在支持它的数据库(除PostgreSQL <9.5和Oracle以外的所有数据库)上,将ignore_conflicts参数设置为True会告诉数据库忽略插入任何失败约束的行的失败,例如重复的唯一值。启用此参数将禁用在每个模型实例上设置主键的功能(如果数据库通常支持的话)。

示例:

Entry.objects.bulk_create([
    Entry(headline='This is a test'),
    Entry(headline='This is only a test'),
], ignore_conflicts=True)

答案 1 :(得分:7)

(注意:我不使用Django,因此可能有更合适的框架特定答案)

Django不可能通过简单地忽略INSERT失败来实现这一点,因为PostgreSQL在第一次错误时中止了整个事务。

Django需要以下方法之一:

  1. INSERT单独一个事务中的每一行并忽略错误(非常慢);
  2. 在每次插入之前创建SAVEPOINT(可能存在缩放问题);
  3. 仅当行尚不存在时才使用过程或查询(复杂且缓慢);或
  4. 将数据批量插入或(更好)COPY放入TEMPORARY表,然后将其合并到主表服务器端。
  5. 类似upsert的方法(3)似乎是一个好主意,但是upsert and insert-if-not-exists are surprisingly complicated

    就个人而言,我会采取(4):我将批量插入到一个新的单独的表中,可能是UNLOGGEDTEMPORARY,然后我会运行一些手动SQL来:

    LOCK TABLE realtable IN EXCLUSIVE MODE;
    
    INSERT INTO realtable 
    SELECT * FROM temptable WHERE NOT EXISTS (
        SELECT 1 FROM realtable WHERE temptable.id = realtable.id
    );
    

    LOCK TABLE ... IN EXCLUSIVE MODE可防止创建行的并发插入导致与上述语句完成的插入冲突并失败。它阻止并发SELECT s,仅SELECT ... FOR UPDATEINSERTUPDATEDELETE,因此从表中读取正常。

    如果您无法长时间阻止并发写入,则可以使用可写CTE将行范围从temptable复制到realtable,如果失败则重试每个块。

答案 2 :(得分:5)

对于不涉及手动SQL和临时表的一个快速而肮脏的解决方法是尝试批量插入数据。如果失败,请恢复为串行插入。

objs = [(Event), (Event), (Event)...]

try:
    Event.objects.bulk_create(objs)

except IntegrityError:
    for obj in objs:
        try:
            obj.save()
        except IntegrityError:
            continue

如果你有很多很多错误,这可能效率不高(你会花费更多的时间进行连续插入而不是批量处理),但我正在通过高基数数据集进行处理重复,这样就解决了我的大部分问题。

答案 3 :(得分:1)

或5.分而治之

我没有对此进行彻底的测试或测试,但它对我来说表现非常好。 YMMV,具体取决于您希望在批量操作中获得的错误数量。

def psql_copy(records):
    count = len(records)
    if count < 1:
        return True
    try:
        pg.copy_bin_values(records)
        return True
    except IntegrityError:
        if count == 1:
            # found culprit!
            msg = "Integrity error copying record:\n%r"
            logger.error(msg % records[0], exc_info=True)
            return False
    finally:
        connection.commit()

    # There was an integrity error but we had more than one record.
    # Divide and conquer.
    mid = count / 2
    return psql_copy(records[:mid]) and psql_copy(records[mid:])
    # or just return False

答案 4 :(得分:0)

即使在Django 1.11中也无法做到这一点。我找到了比使用Raw SQL更好的选择。它使用djnago-query-builder。它有一个upsert方法

from querybuilder.query import Query
q = Query().from_table(YourModel)
# replace with your real objects
rows = [YourModel() for i in range(10)] 
q.upsert(rows, ['unique_fld1', 'unique_fld2'], ['fld1_to_update', 'fld2_to_update'])

注意:该库仅支持postgreSQL

这是我用于批量插入的gist,它支持忽略IntegrityErrors并返回插入的记录。

答案 5 :(得分:0)

Django 2.2之前的项目的最新答案:

我最近遇到了这种情况,并且找到了第二个列表数组来检查唯一性。

在我的情况下,该模型具有唯一的共同检查功能,并且由于大量创建的数组中包含重复数据,因此大量创建引发了Integrity Error异常。

因此,我决定除了批量创建对象列表之外,还要创建清单。这是示例代码;唯一键是 owner brand ,在此示例中owner是用户对象实例,brand是字符串实例:

create_list = []
create_list_check = []
for brand in brands:
    if (owner.id, brand) not in create_list_check:
        create_list_check.append((owner.id, brand))
        create_list.append(ProductBrand(owner=owner, name=brand))

if create_list:
    ProductBrand.objects.bulk_create(create_list)
相关问题