2009-08-25 16 views
8

Toplu ekleme veya güncelleştirme yapmak için SQLAlchemy'yi bir Postgres arka uç kullanarak kullanıyorum. Performansı artırmaya çalışmak için, her bin satırda bir veya daha fazla işlem yapmaya çalışıyorum:SQLAlchemy ile toplu ekleme veya güncelleştirme işlemlerini verimli bir şekilde nasıl yapabilirim?

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() 

Ancak, bu çalışmaz. Görünen o ki, INSERT başarısız olduğunda, UPDATE'in gerçekleşmesini engelleyen garip bir durumda bir şeyler bırakıyor. İşlemi otomatik olarak geri alıyor mu? Eğer öyleyse, bu durdurulabilir mi? Tüm işlemimin bir sorun olması durumunda geri dönmesini istemiyorum, bu yüzden ilk başta özel durumu yakalamaya çalışıyorum.

Hata alıyorum, BTW, "sqlalchemy.exc.InternalError: (InternalError) geçerli işlem iptal edilir, işlem bloğu bitinceye kadar yoksayılır komutlar", ve bu güncelleştirme() olur.) aramak.

cevap

5

Bazı garip Postgresql'ye özgü davranışlar gösteriyorsunuz: bir işlemde bir hata olursa, tüm işlemin geri alınmasına zorlanır. Bunu bir Postgres tasarım hatası olarak görüyorum; Bazı durumlarda etrafta çalışmak biraz SQL contortionism alır.

Bir geçici çözüm önce UPDATE yapmaktır. Bir satırın gerçekten değiştirilmiş olup olmadığını cursor.rowcount'a bakarak algıla. herhangi bir satır değiştirmediyse, bu yoktu, bu yüzden INSERT. (Bu kadar hızlı elbette, insert daha sık daha güncellersem olacaktır.)

başka bir geçici çözüm Savepoint kullanmaktır:

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

Bu üretim kalitesinde kodu için ciddi bir sorun vardır: Mecbur hatayı doğru tespit edin. Muhtemelen benzersiz bir kısıtlama kontrolüne çarpmayı bekliyorsunuz, fakat beklenmedik bir şeye çarpabilirsiniz ve beklenmeyen birinden beklenen hatayı güvenilir bir şekilde ayırt etmek imkansız olabilir. Bu, hata durumuna yanlış isabet ederse, hiçbir şey güncellenmeyecek veya eklenmeyecek ve hiçbir hata görülmeyecek olan belirsiz sorunlara yol açacaktır. Bu konuda çok dikkatli ol. Beklediğiniz hata türünü olduğundan emin olmak için Postgresql'in hata koduna bakarak hata durumunu daraltabilirsiniz, ancak olası sorun hala var.

Son olarak, toplu iş-eki-veya-güncelleştirme yapmak istiyorsanız, aslında her komutta bir öğe değil, bir kaç komutta bunları yapmak istersiniz. Bu zorlayıcı SQL gerektirir: Bir INSERT içine iç içe SELECT, eklemek ve güncelleştirmek için doğru öğeleri filtreleyerek.

+1

"Bir işlemde bir hata oluşursa, tüm işlemin geri alınmasına zorlanır. Bunu bir Postgres tasarım hatası olarak kabul ediyorum." - İşlemlerin amacı bu değil mi? [Wikipedia] 'dan (http: //en.wikipedia.org/wiki/Database_transaction): "İşlemler, bir veritabanında gerçekleştirilen her bir iş biriminin tamamen tamamlanmasının veya herhangi bir etkisinin olmaması gerektiğini belirten bir" tamamen ya da hiçbir şey "önermesi sağlar." – spiffytech

+0

@Spiffytech İyi yanıt. Bu aslında beni LOL yaptı. –

4

Bu hata PostgreSQL kaynaklıdır. PostgreSQL, bir komut bir hata oluşturuyorsa, aynı işlemde komutları yürütmenize izin vermez. Bunu düzeltmek için iç içe geçmiş işlemleri (SQL kaydetme noktaları kullanılarak) conn.begin_nested() aracılığıyla kullanabilirsiniz. Heres işe yarayabilecek bir şey. Kodun açık bağlantıları kullanmasını sağladım, yığın bölümünü belirledim ve kodun işlemleri doğru yönetmek için içerik yöneticisini kullanmasını sağladım.

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(...)) 

Yuvalanmış işlem yükü nedeniyle bu yine de genel performansa sahip olmayacaktır. Daha iyi bir performans istiyorsanız, hangi satırların önceden seçilmiş bir sorguyla hatalar oluşturduğunu algılamaya çalışın ve yürütme desteğini kullanın (tüm ekler aynı sütunları kullanıyorsa yürütme, diktelerin listesini alabilir). Eşzamanlı güncellemelerin üstesinden gelmeniz gerekiyorsa, yine de, yeniden denemeler yaparak ya da tek tek eklere geri dönerek hata işlemlerini gerçekleştirmeniz gerekecektir.

İlgili konular