我使用bulk_create将数千或行加载到postgresql DB中。不幸的是,有些行导致IntegrityError并停止bulk_create进程。我想知道是否有办法告诉django忽略这些行并尽可能多地保存批次?
答案 0 :(得分:16)
Django 2.2从documentation向ignore_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需要以下方法之一:
INSERT
单独一个事务中的每一行并忽略错误(非常慢); SAVEPOINT
(可能存在缩放问题); COPY
放入TEMPORARY
表,然后将其合并到主表服务器端。类似upsert的方法(3)似乎是一个好主意,但是upsert and insert-if-not-exists are surprisingly complicated。
就个人而言,我会采取(4):我将批量插入到一个新的单独的表中,可能是UNLOGGED
或TEMPORARY
,然后我会运行一些手动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 UPDATE
,INSERT
,UPDATE
和DELETE
,因此从表中读取正常。
如果您无法长时间阻止并发写入,则可以使用可写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)