SQLAlchemy ORM kullanarak veritabanını verimli bir şekilde güncelleme


117

Yeni bir uygulama başlatıyorum ve bir ORM, özellikle SQLAlchemy kullanmaya bakıyorum.

Veritabanımda bir 'foo' sütunum olduğunu ve bunu artırmak istediğimi varsayalım. Düz sqlite'de bu kolaydır:

db = sqlite3.connect('mydata.sqlitedb')
cur = db.cursor()
cur.execute('update table stuff set foo = foo + 1')

SQLAlchemy SQL oluşturucunun eşdeğerini buldum:

engine = sqlalchemy.create_engine('sqlite:///mydata.sqlitedb')
md = sqlalchemy.MetaData(engine)
table = sqlalchemy.Table('stuff', md, autoload=True)
upd = table.update(values={table.c.foo:table.c.foo+1})
engine.execute(upd)

Bu biraz daha yavaştır, ancak içinde fazla bir şey yok.

İşte SQLAlchemy ORM yaklaşımı için en iyi tahminim:

# snip definition of Stuff class made using declarative_base
# snip creation of session object
for c in session.query(Stuff):
    c.foo = c.foo + 1
session.flush()
session.commit()

Bu doğru olanı yapar, ancak diğer ikisi yaklaştığı sürece elli kattan az sürer. Bunun, onunla çalışmadan önce tüm verileri belleğe getirmesi gerektiğinden olduğunu varsayıyorum.

SQLAlchemy'nin ORM'sini kullanarak verimli SQL oluşturmanın herhangi bir yolu var mı? Veya başka bir python ORM kullanıyor musunuz? Yoksa SQL'i elle yazmaya geri mi dönmeliyim?


1
Tamam, cevabın "bu ORM'lerin iyi yaptığı bir şey değil" olduğunu varsayıyorum. Oh iyi; Yaşıyorum ve öğreniyorum.
John Fouhy

Farklı ORM'lerde ve bunların yük ve baskı altında nasıl performans gösterdiklerinde bazı deneyler yapılmıştır. Kullanışlı bir bağlantı yok ama okumaya değer.
Matthew Schinckel

Son (ORM) örneğinde ortaya çıkan bir diğer sorun da atomik olmamasıdır .
Marian

Yanıtlar:


182

SQLAlchemy'nin ORM'si, SQL katmanı ile birlikte kullanılmak içindir, onu gizlemekle kalmaz. Ancak aynı işlemde ORM ve düz SQL kullanırken bir veya iki şeyi aklınızda tutmanız gerekir. Temel olarak, bir taraftan, ORM veri değişiklikleri, yalnızca oturumunuzdaki değişiklikleri temizlediğinizde veritabanına vuracaktır. Diğer taraftan, SQL veri işleme ifadeleri oturumunuzdaki nesneleri etkilemez.

Öyleyse diyorsan

for c in session.query(Stuff).all():
    c.foo = c.foo+1
session.commit()

dediği şeyi yapacak, veritabanından tüm nesneleri getirecek, tüm nesneleri değiştirecek ve sonra değişiklikleri veritabanına temizleme zamanı geldiğinde satırları tek tek güncelleyecektir.

Bunun yerine şunu yapmalısınız:

session.execute(update(stuff_table, values={stuff_table.c.foo: stuff_table.c.foo + 1}))
session.commit()

Bu, beklediğiniz gibi tek bir sorgu olarak yürütülecektir ve en azından varsayılan oturum yapılandırması, kaydetme sırasında oturumdaki tüm verileri sona erdirdiğinden, eski veri sorunlarınız yoktur.

Neredeyse piyasaya sürülen 0.5 serisinde, güncelleme için bu yöntemi de kullanabilirsiniz:

session.query(Stuff).update({Stuff.foo: Stuff.foo + 1})
session.commit()

Bu, temelde önceki kod parçacığı ile aynı SQL ifadesini çalıştırır, ancak aynı zamanda değiştirilen satırları seçer ve oturumdaki eski verilerin son kullanma tarihi geçer. Güncellemeden sonra herhangi bir oturum verisi kullanmadığınızı biliyorsanız synchronize_session=False, güncelleme bildirimine de ekleyebilir ve bu seçimden kurtulabilirsiniz.


2
3. yolla, orm olayını tetikleyecek mi (after_update gibi)?
Ken

@Ken, hayır, olmayacak. Query.update docs.sqlalchemy.org/en/13/orm/… için API belgesine bakın . Bunun yerine after_bulk_update docs.sqlalchemy.org/en/13/orm/…
TrilceAC

91
session.query(Clients).filter(Clients.id == client_id_list).update({'status': status})
session.commit()

Bunu dene =)


Bu yöntem benim için çalıştı. Ama sorun onun yavaş olması. Birkaç 100 bin veri kaydı için iyi bir zamana ihtiyacı var. Daha hızlı bir yöntem var mı?
baermathias

Çok teşekkürler, bu yaklaşım benim için çalıştı. Sqlachemy'nin jsonsütunu güncellemenin daha kısa bir yolu olmaması gerçekten kötü
Jai Prakash

6
Bu yöntemi kullanırken hala performans sorunları yaşayanlar için: varsayılan olarak bu, önce her kayıt için bir SEÇME yapabilir ve daha sonra yalnızca GÜNCELLEME yapabilir. Synize_session = false update () yöntemine iletilmesi bunun olmasını engeller, ancak bunu yalnızca commit () işleminden önce güncellediğiniz nesneleri tekrar kullanmazsanız yaptığınızdan emin olun.
teuneboon

26

Sqlalchemy kullanarak GÜNCELLEMENİN birkaç yolu vardır

1) for c in session.query(Stuff).all():
       c.foo += 1
   session.commit()

2) session.query().\
       update({"foo": (Stuff.foo + 1)})
   session.commit()

3) conn = engine.connect()
   stmt = Stuff.update().\
       values(Stuff.foo = (Stuff.foo + 1))
   conn.execute(stmt)

7

Alanları manuel olarak eşlemek zorunda kalmadan aynı sorunun nasıl çözüleceğine dair bir örnek:

from sqlalchemy import Column, ForeignKey, Integer, String, Date, DateTime, text, create_engine
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.attributes import InstrumentedAttribute

engine = create_engine('postgres://postgres@localhost:5432/database')
session = sessionmaker()
session.configure(bind=engine)

Base = declarative_base()


class Media(Base):
  __tablename__ = 'media'
  id = Column(Integer, primary_key=True)
  title = Column(String, nullable=False)
  slug = Column(String, nullable=False)
  type = Column(String, nullable=False)

  def update(self):
    s = session()
    mapped_values = {}
    for item in Media.__dict__.iteritems():
      field_name = item[0]
      field_type = item[1]
      is_column = isinstance(field_type, InstrumentedAttribute)
      if is_column:
        mapped_values[field_name] = getattr(self, field_name)

    s.query(Media).filter(Media.id == self.id).update(mapped_values)
    s.commit()

Dolayısıyla, bir Medya örneğini güncellemek için şuna benzer bir şey yapabilirsiniz:

media = Media(id=123, title="Titular Line", slug="titular-line", type="movie")
media.update()

1

Zorlu testlerle şunu denerim:

for c in session.query(Stuff).all():
     c.foo = c.foo+1
session.commit()

(IIRC, commit () flush olmadan çalışır ()).

Zaman zaman büyük bir sorgu yapıp sonra python'da yinelemenin birçok sorudan 2 kat daha hızlı olabileceğini buldum. Sorgu nesnesi üzerinde yinelemenin, sorgu nesnesinin all () yöntemi tarafından oluşturulan bir listeyi yinelemekten daha az verimli olduğunu varsayıyorum.

[Lütfen aşağıdaki yorumu not edin - bu işleri hiç hızlandırmadı].


2
.All () eklenmesi ve .flush () öğesinin kaldırılması saati hiç değiştirmedi.
John Fouhy

1

Nesne oluşturma açısından ek yükten kaynaklanıyorsa, muhtemelen SA ile hızlandırılamaz.

İlgili nesneleri yüklemekten kaynaklanıyorsa, tembel yükleme ile bir şeyler yapabilirsiniz. Referanslar nedeniyle yaratılan çok sayıda nesne var mı? (IE, bir Şirket nesnesi almak aynı zamanda ilgili tüm Kişiler nesnelerini de alır).


Hayır, masa kendi başına. Daha önce hiç ORM kullanmadım - bu kötü oldukları bir şey mi?
John Fouhy

1
Nesnelerin yaratılmasından kaynaklanan bir ek yük var, ancak bence cezaya değer - nesneleri bir veritabanında kalıcı olarak saklayabilmek harika.
Matthew Schinckel
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.