Django bulk создает с игнорированием строк, которые вызывают IntegrityError?

Я использую bulk_create для загрузки тысяч или строк в БД postgresql. К сожалению, некоторые строки вызывают IntegrityError и останавливают процесс bulk_create. Мне было интересно, есть ли способ сказать Джанго игнорировать такие строки и сохранить как можно больше партии?

4 ответов


(примечание: Я не использую Django, поэтому могут быть более подходящие конкретные ответы)

это невозможно для Django, чтобы сделать это, просто игнорируя INSERT сбои, потому что PostgreSQL прерывает всю транзакцию при первой ошибке.

Джанго понадобится один из этих подходов:

  1. INSERT каждая строка в отдельной транзакции и игнорировать ошибки (очень медленно);
  2. создать SAVEPOINT перед каждым insert (может иметь проблемы с масштабированием);
  3. используйте процедуру или запрос для вставки, только если строка еще не существует (сложная и медленная); или
  4. 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 предотвращает параллельную вставку, которая создает строку, вызывая конфликт с вставкой, выполненной вышеуказанным оператором, и сбой. Это не предотвращения одновременных SELECTs, только 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