2009-08-25 33 views
8

Tôi đang sử dụng SQLAlchemy với phần phụ trợ Postgres để thực hiện chèn hoặc cập nhật hàng loạt. Để cố gắng cải thiện hiệu suất, tôi đang cố gắng cam kết chỉ một lần mỗi nghìn hàng hoặc như vậy:Làm cách nào để thực hiện chèn hoặc cập nhật hàng loạt với SQLAlchemy hiệu quả?

trans = engine.begin() 
    for i, rec in enumerate(records): 
    if i % 1000 == 0: 
     trans.commit() 
     trans = engine.begin() 
    try: 
     inserter.execute(...) 
    except sa.exceptions.SQLError: 
     my_table.update(...).execute() 
trans.commit() 

Tuy nhiên, điều này không hoạt động. Có vẻ như khi INSERT không thành công, nó để lại mọi thứ trong một trạng thái kỳ lạ ngăn cản việc cập nhật xảy ra. Nó có tự động quay trở lại giao dịch không? Nếu vậy, điều này có thể được dừng lại? Tôi không muốn toàn bộ giao dịch của mình được khôi phục trong trường hợp có vấn đề, đó là lý do tại sao tôi đang cố gắng nắm bắt ngoại lệ ngay từ đầu.

Thông báo lỗi tôi nhận được, BTW, là "sqlalchemy.exc.InternalError: (InternalError) giao dịch hiện tại bị hủy bỏ, lệnh bị bỏ qua cho đến khi kết thúc khối giao dịch", và nó xảy ra trên bản cập nhật().) gọi điện.

Trả lời

5

Bạn đang gặp phải một số hành vi đặc biệt của Postgresql: nếu xảy ra lỗi trong giao dịch, nó sẽ buộc toàn bộ giao dịch được khôi phục. Tôi xem đây là lỗi thiết kế Postgres; phải mất khá nhiều biến dạng SQL để làm việc xung quanh trong một số trường hợp.

Một cách giải quyết khác là thực hiện UPDATE trước. Phát hiện nếu nó thực sự sửa đổi một hàng bằng cách nhìn vào cursor.rowcount; nếu nó không sửa đổi bất kỳ hàng nào, nó không tồn tại, do đó, hãy nhấn INSERT. (Điều này sẽ nhanh hơn nếu bạn cập nhật thường xuyên hơn bạn chèn, tất nhiên.)

workaround khác là sử dụng savepoints:

SAVEPOINT a; 
INSERT INTO ....; 
-- on error: 
ROLLBACK TO SAVEPOINT a; 
UPDATE ...; 
-- on success: 
RELEASE SAVEPOINT a; 

này có một vấn đề nghiêm trọng đối với mã sản xuất chất lượng: bạn phải phát hiện lỗi chính xác. Có lẽ bạn đang mong đợi để đạt một kiểm tra ràng buộc duy nhất, nhưng bạn có thể nhấn một cái gì đó bất ngờ, và nó có thể là bên cạnh không thể phân biệt đáng tin cậy lỗi dự kiến ​​từ một bất ngờ. Nếu điều này xảy ra lỗi không chính xác, điều này sẽ dẫn đến các vấn đề không rõ ràng nơi không có gì sẽ được cập nhật hoặc chèn vào và sẽ không có lỗi nào được nhìn thấy. Hãy rất cẩn thận với điều này. Bạn có thể thu hẹp trường hợp lỗi bằng cách nhìn vào mã lỗi của Postgresql để đảm bảo đó là loại lỗi bạn đang mong đợi, nhưng vấn đề tiềm năng vẫn còn đó.

Cuối cùng, nếu bạn thực sự muốn thực hiện chèn hàng loạt hoặc cập nhật, bạn thực sự muốn thực hiện nhiều lệnh trong một vài lệnh, không phải một mục cho mỗi lệnh. Điều này đòi hỏi SQL phức tạp hơn: SELECT lồng vào bên trong một INSERT, lọc ra các mục bên phải để chèn và cập nhật.

+1

"Nếu xảy ra lỗi trong giao dịch, nó sẽ buộc toàn bộ giao dịch được khôi phục. Tôi xem đây là lỗi thiết kế Postgres". - Đây không phải là điểm giao dịch sao? Từ [Wikipedia] (http: //en.wikipedia.org/wiki/Database_transaction): "Giao dịch cung cấp đề xuất 'tất cả hoặc không có gì', cho biết mỗi đơn vị công việc được thực hiện trong cơ sở dữ liệu phải hoàn thành toàn bộ hoặc không có tác dụng." – spiffytech

+0

@Spiffytech Phản hồi tốt. Điều này thực sự làm tôi LOL. –

4

Lỗi này là từ PostgreSQL. PostgreSQL không cho phép bạn thực hiện các lệnh trong cùng một giao dịch nếu một lệnh tạo ra một lỗi. Để khắc phục điều này, bạn có thể sử dụng các giao dịch lồng nhau (được thực hiện bằng cách sử dụng các điểm lưu trữ SQL) qua conn.begin_nested(). Heres cái gì đó có thể làm việc. Tôi đã làm cho mã sử dụng các kết nối rõ ràng, tạo ra phần chunking và làm cho mã sử dụng trình quản lý ngữ cảnh để quản lý các giao dịch một cách chính xác.

from itertools import chain, islice 
def chunked(seq, chunksize): 
    """Yields items from an iterator in chunks.""" 
    it = iter(seq) 
    while True: 
     yield chain([it.next()], islice(it, chunksize-1)) 

conn = engine.commit() 
for chunk in chunked(records, 1000): 
    with conn.begin(): 
     for rec in chunk: 
      try: 
       with conn.begin_nested(): 
        conn.execute(inserter, ...) 
      except sa.exceptions.SQLError: 
       conn.execute(my_table.update(...)) 

Điều này vẫn sẽ không có hiệu suất sao mặc dù do chi phí giao dịch lồng nhau. Nếu bạn muốn hiệu suất tốt hơn cố gắng phát hiện những hàng nào sẽ tạo lỗi trước bằng một truy vấn chọn và sử dụng hỗ trợ thực thi (thực thi có thể lấy danh sách các dấu gạch ngang nếu tất cả các chèn sử dụng cùng một cột). Nếu bạn cần xử lý các bản cập nhật đồng thời, bạn vẫn cần phải xử lý lỗi hoặc thông qua thử lại hoặc quay trở lại từng lần một.

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