2016-05-29 6 views
1

Ich habe einen einzigen Sellerie Arbeiter mit 5 Fäden. Es scrappt Webseiten und speichert Domains über djangos ORM in der DB.Sellerie/Django doppelte Schlüsselverletzungen

Hier grob ist, wie es aussieht:

domain_all = list(Domain.objects.all()) 
needs_domain = set() 

for x in dup_free_scrape: 
    domain = x['domain'] 
    if any(domain.lower() == s.name.lower() for s in domain_all): 
     x['domainn'] = [o for o in domain_all if domain.lower() == o.name.lower()][0] 
    else: 
     print('adding: {}'.format(domain)) 
     needs_domain.add(domain) 

create_domains = [Domain(name=b.lower()) for b in needs_domain] 
create_domains_ids = Domain.objects.bulk_create(create_domains) 

Wahrscheinlich nicht der beste Weg, aber es überprüft Domains in einem dict (dup_free_scrape) gegen alle Domains bereits in der Datenbank.

Es kann Hunderte gehen oder sogar Tausende vor den Fehler zu stoßen, aber manchmal tut es:

Task keywords.domains.model_work[285c3e74-8e47-4925-9ab6-a99540a24665] raised unexpected: IntegrityError('duplicate key value violates unique constraint "keywords_domain_name_key"\nDETAIL: Key (name)=(domain.com) already exists.\n',) django.db.utils.IntegrityError: duplicate key value violates unique constraint "keywords_domain_name_key"

Der einzige Grund für dieses Problem, das ich von wäre denken kann: Ein Thread gespeichert Domäne DB, während ein anderer war in der Mitte des Codes oben?

Ich kann keine guten Lösungen finden, aber hier ist und Idee (nicht sicher, ob etwas gut): Wrap ganze Sache in der Transaktion und wenn Datenbank Fehler Fehler simply Wiederholung (Abfrage-Datenbank für "Domain.objects.all() " nochmal).

Antwort

0

Wenn Sie diese Datensätze in Bulk erstellen und mehrere Threads sind, ist es in der Tat sehr ähnlich, dass IntegrityError s durch verschiedene Threads verursacht werden, die die gleichen Daten einfügen. Brauchst du wirklich mehrere Threads, die daran arbeiten? Wenn ja, könnten Sie versuchen

create_domains = [] 
create_domain_ids = [] 

for x in dup_free_scrape: 
    domain = x['domain'] 
    new_domain, created = Domain.objects.get_or_create(name = domain.lower() 
    if created: 
     create_domains.append(domain.lower()) 
     created_domain_ids.append(new_domain.pk) 

beachten Sie, dass dies der gesamte Code ist. Die Auswahl aller Abfragen, die Sie am Anfang hatten, ist nicht nötig. Domain.objects.all() wird sehr ineffizient sein, weil Sie dort die gesamte Tabelle lesen.

Beachten Sie auch, dass Ihr Listenverständnis für x['domainn'] völlig redundant erscheint.

create_domains und create_domain_ids Listen sind möglicherweise nicht erforderlich, es sei denn, Sie möchten verfolgen, was erstellt wurde.

Stellen Sie sicher, dass Sie den richtigen Index für den Domänennamen haben. Von get_or_create docs:

This method is atomic assuming correct usage, correct database configuration, and correct behavior of the underlying database. However, if uniqueness is not enforced at the database level for the kwargs used in a get_or_create call (see unique or unique_together), this method is prone to a race-condition which can result in multiple rows with the same parameters being inserted simultaneously.

+0

macht sehr viel Sinn, aber keine Abfrage Datenbank viel get_or_create? Weil manchmal mehr als 100 Domains hinzugefügt werden. Danke – JanRainMan

+0

Es wird aber 100 Abfragen werden wahrscheinlich effizienter als das Durchlaufen der gesamten Tabelle, die Hunderttausende haben könnte. – e4c5

+0

Außerdem haben Sie im ursprünglichen Code tatsächlich eine verschachtelte Schleife aufgrund der Überprüfung jeder Bedingung. – e4c5