2012-09-16 25 views
16

Tôi đang sử dụng bulk_create để tải hàng nghìn hoặc hàng vào một DB postgresql. Thật không may một số hàng đang gây ra IntegrityError và ngừng quá trình bulk_create. Tôi đã tự hỏi nếu có một cách để nói với django để bỏ qua các hàng như vậy và tiết kiệm càng nhiều của lô càng tốt?Django bulk_create có bỏ qua các hàng gây ra IntegrityError?

+0

Điều đó có thể không thực hiện được vì PostgreSQL hủy bỏ giao dịch về lỗi đầu tiên. Django sẽ cần hoặc là (a) tạo ra một SAVEPOINT trước mỗi chèn, làm chậm mọi thứ xuống và chi phí tài nguyên; hoặc (b) Sử dụng một thủ tục hoặc truy vấn để chèn chỉ khi hàng không tồn tại. Cá nhân, tôi muốn chèn hàng loạt vào một bảng riêng biệt mới, có thể là 'UNLOGGED' hoặc' TEMPORARY', sau đó 'INSERT INTO SELECT có thể thực hiện được * TỪ CÓ THỂ KHÔNG CÓ (SELECT 1 FROM realtable WHERE temptable.id = realtable.id)' hoặc tương tự. –

Trả lời

6

(Lưu ý: Tôi không sử dụng Django, vì vậy có thể có câu trả lời khuôn khổ cụ thể phù hợp hơn)

Nó không phải là có thể cho Django để làm điều này bằng cách đơn giản bỏ qua INSERT thất bại vì PostgreSQL hủy bỏ toàn bộ giao dịch trên lỗi đầu tiên.

Django sẽ cần một trong những cách tiếp cận:

  1. INSERT mỗi hàng trong một giao dịch riêng biệt và bỏ qua lỗi (rất chậm);
  2. Tạo SAVEPOINT trước mỗi lần chèn (có thể có vấn đề về tỷ lệ);
  3. Sử dụng quy trình hoặc truy vấn để chèn chỉ nếu hàng không tồn tại (phức tạp và chậm); hoặc
  4. Chèn hàng loạt hoặc (tốt hơn) COPY dữ liệu vào bảng TEMPORARY, sau đó hợp nhất vào bảng phía máy chủ chính.

Cách tiếp cận giống như cận cảnh (3) có vẻ như là một ý tưởng hay, nhưng upsert and insert-if-not-exists are surprisingly complicated.

Cá nhân, tôi muốn thực hiện (4): tôi muốn số lượng lớn chèn vào một bảng riêng biệt mới, có lẽ UNLOGGED hoặc TEMPORARY, sau đó tôi muốn chạy một số lệnh SQL bằng tay để:

LOCK TABLE realtable IN EXCLUSIVE MODE; 

INSERT INTO realtable 
SELECT * FROM temptable WHERE NOT EXISTS (
    SELECT 1 FROM realtable WHERE temptable.id = realtable.id 
); 

Các LOCK TABLE ... IN EXCLUSIVE MODE ngăn chặn một chèn đồng thời tạo ra một hàng từ gây ra một cuộc xung đột với một chèn được thực hiện bởi các tuyên bố trên và không. Số điện thoại không ngăn đồng thời SELECT s, chỉ SELECT ... FOR UPDATE, INSERT, UPDATEDELETE, vì vậy đọc từ bảng tiếp tục như bình thường.

Nếu bạn không thể chặn đồng thời ghi quá lâu, bạn có thể sử dụng CTE có thể ghi để sao chép phạm vi hàng từ temptable thành realtable, thử lại mỗi khối nếu không thành công.

+0

Cảm ơn @ craig-ringer Tôi đã kết thúc thanh toán bù trừ danh sách các đối tượng python của tôi trước khi chèn chúng vào DB, một cái gì đó tương tự như cách tiếp cận # 3 của bạn, nhưng trong python tinh khiết. – Meitham

+0

Có một ví dụ chi tiết về (4) tại https://rodmtech.net/docs/django/django-bulk_create-without-integrityerror-rollback/ – eugene

1

Hoặc 5. Chia và chinh phục

Tôi đã không kiểm tra hoặc đo điểm chuẩn kỹ lưỡng, nhưng nó hoạt động khá tốt đối với tôi. YMMV, tùy thuộc vào số lượng lỗi bạn mong đợi nhận được trong một thao tác hàng loạt.

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 
1

Một cách giải quyết nhanh chóng và không có liên quan đến SQL thủ công và bảng tạm thời là cố gắng chèn hàng loạt dữ liệu. Nếu không thành công, hãy hoàn nguyên về chèn nối tiếp.

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

try: 
    Event.objects.bulk_create(objs) 

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

Nếu bạn có rất nhiều và rất nhiều sai sót này có thể không được như vậy hiệu quả (bạn sẽ dành nhiều thời gian serially chèn hơn làm như vậy với số lượng lớn), nhưng tôi đang làm việc thông qua một tập dữ liệu số cao với vài trùng lặp để giải quyết hầu hết các vấn đề của tôi.

0

Ngay cả ở Django 1.11 không có cách nào để thực hiện việc này.Tôi đã tìm thấy một lựa chọn tốt hơn so với sử dụng SQL thô. Nó sử dụng djnago-query-builder. Nó có một phương pháp 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']) 

Lưu ý: Các thư viện chỉ hỗ trợ PostgreSQL

Các vấn đề liên quan