Django bulk создает с игнорированием строк, которые вызывают IntegrityError?
Я использую bulk_create для загрузки тысяч или строк в БД postgresql. К сожалению, некоторые строки вызывают IntegrityError и останавливают процесс bulk_create. Мне было интересно, есть ли способ сказать Джанго игнорировать такие строки и сохранить как можно больше партии?
4 ответов
(примечание: Я не использую Django, поэтому могут быть более подходящие конкретные ответы)
это невозможно для Django, чтобы сделать это, просто игнорируя INSERT
сбои, потому что PostgreSQL прерывает всю транзакцию при первой ошибке.
Джанго понадобится один из этих подходов:
-
INSERT
каждая строка в отдельной транзакции и игнорировать ошибки (очень медленно); - создать
SAVEPOINT
перед каждым insert (может иметь проблемы с масштабированием); - используйте процедуру или запрос для вставки, только если строка еще не существует (сложная и медленная); или
- Bulk-вставить или (лучше)
COPY
данные вTEMPORARY
таблица, затем объедините ее с основной таблицей на стороне сервера.
подход, подобный upsert (3), кажется хорошей идеей, но upsert и insert-if-not-exists удивительно сложно.
лично я бы взял (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
, повторяя каждый блок, если он не удался.
один быстрый и грязный обходной путь для этого, который не включает ручной SQL и временные таблицы, - это просто попытка массовой вставки данных. Если это не удается, вернитесь к последовательной вставке.
objs = [(Event), (Event), (Event)...]
try:
Event.objects.bulk_create(objs)
except IntegrityError:
for obj in objs:
try:
obj.save()
except IntegrityError:
continue
Если у вас много и много ошибок, это может быть не так эффективно (вы потратите больше времени на последовательную вставку, чем на массовую), но я работаю с набором данных высокой мощности с несколькими дубликатами, поэтому это решает большинство моих проблем.
или 5. Разделяй и властвуй!--2-->
Я не тестировал или не тестировал это тщательно, но он работает довольно хорошо для меня. 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
даже в Django 1.11 нет способа сделать это. Я нашел лучший вариант, чем использование Raw SQL. Он с помощью djnago-построитель запросов. У него есть 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