Cập nhật cơ sở dữ liệu hiệu quả bằng SQLAlchemy ORM


116

Tôi đang bắt đầu một ứng dụng mới và xem xét việc sử dụng ORM - cụ thể là SQLAlchemy.

Giả sử tôi có một cột 'foo' trong cơ sở dữ liệu của mình và tôi muốn tăng nó lên. Trong sqlite thẳng, điều này rất dễ dàng:

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

Tôi đã tìm ra SQLAlchemy SQL-builder tương đương:

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)

Điều này hơi chậm hơn một chút, nhưng không có nhiều thứ trong đó.

Đây là dự đoán tốt nhất của tôi cho cách tiếp cận ORM SQLAlchemy:

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

Điều này làm đúng, nhưng chỉ mất chưa đến năm mươi lần khi hai phương pháp còn lại tiếp cận. Tôi cho rằng đó là bởi vì nó phải đưa tất cả dữ liệu vào bộ nhớ trước khi nó có thể hoạt động với nó.

Có cách nào để tạo SQL hiệu quả bằng cách sử dụng ORM của SQLAlchemy không? Hoặc sử dụng bất kỳ ORM python nào khác? Hay tôi nên quay lại viết SQL bằng tay?


1
Ok, tôi giả định câu trả lời là "đây không phải là thứ ORM làm tốt". Tốt thôi; Tôi sống và học hỏi.
John Fouhy

Đã có một số thử nghiệm chạy trên các ORM khác nhau và cách chúng hoạt động khi chịu tải và cưỡng bức. Không có một liên kết hữu ích, nhưng đáng đọc.
Matthew Schinckel

Một vấn đề khác tồn tại với ví dụ cuối cùng (ORM) là nó không phải là nguyên tử .
Marian

Câu trả lời:


181

ORM của SQLAlchemy được sử dụng cùng với lớp SQL, chứ không phải ẩn nó. Nhưng bạn phải ghi nhớ một hoặc hai điều khi sử dụng ORM và SQL thuần túy trong cùng một giao dịch. Về cơ bản, từ một phía, các sửa đổi dữ liệu ORM sẽ chỉ đánh vào cơ sở dữ liệu khi bạn loại bỏ các thay đổi khỏi phiên của mình. Từ phía bên kia, các câu lệnh thao tác dữ liệu SQL không ảnh hưởng đến các đối tượng trong phiên của bạn.

Vì vậy, nếu bạn nói

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

nó sẽ thực hiện những gì nó nói, đi tìm nạp tất cả các đối tượng từ cơ sở dữ liệu, sửa đổi tất cả các đối tượng và sau đó khi đến lúc xóa các thay đổi đối với cơ sở dữ liệu, hãy cập nhật từng hàng một.

Thay vào đó, bạn nên làm điều này:

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

Điều này sẽ thực thi như một truy vấn như bạn mong đợi và vì ít nhất cấu hình phiên mặc định sẽ hết hạn tất cả dữ liệu trong phiên khi cam kết nên bạn không gặp bất kỳ vấn đề nào về dữ liệu cũ.

Trong loạt 0.5 gần như được phát hành, bạn cũng có thể sử dụng phương pháp này để cập nhật:

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

Điều đó về cơ bản sẽ chạy câu lệnh SQL giống như đoạn mã trước đó, nhưng cũng chọn các hàng đã thay đổi và hết hạn mọi dữ liệu cũ trong phiên. Nếu bạn biết bạn không sử dụng bất kỳ dữ liệu phiên nào sau khi cập nhật, bạn cũng có thể thêm synchronize_session=Falsevào câu lệnh cập nhật và loại bỏ lựa chọn đó.


2
theo cách thứ 3, nó sẽ kích hoạt sự kiện orm (như after_update)?
Ken

@Ken, không, nó sẽ không. Xem tài liệu API cho Query.update docs.sqlalchemy.org/en/13/orm/… . Thay vào đó, bạn có một sự kiện cho 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()

Thử cái này =)


Phương pháp này đã làm việc cho tôi. Nhưng vấn đề là nó chậm. Nó cần một khoảng thời gian tốt cho vài bản ghi dữ liệu 100k. Có thể có một phương pháp nhanh hơn?
baermathias

Cảm ơn rất nhiều cách tiếp cận này đã làm việc cho tôi. Nó thực sự xấu mà sqlachemy không có một cách ngắn hơn để cập nhật các jsoncột
Jai Prakash

6
Đối với những người vẫn gặp vấn đề về hiệu suất khi sử dụng phương pháp này: theo mặc định, điều này có thể thực hiện lệnh CHỌN cho mọi bản ghi trước và chỉ CẬP NHẬT sau đó. Việc chuyển đồng bộ hóa_session = False vào phương thức update () sẽ ngăn điều này xảy ra, nhưng hãy đảm bảo chỉ thực hiện điều này nếu bạn không sử dụng các đối tượng bạn cập nhật lại trước commit ().
teuneboon

25

Có một số cách để CẬP NHẬT bằng sqlalchemy

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)

6

Dưới đây là một ví dụ về cách giải quyết vấn đề tương tự mà không cần phải ánh xạ các trường theo cách thủ công:

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

Vì vậy, để cập nhật một phiên bản Media, bạn có thể làm như sau:

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

1

Mặc dù thử nghiệm, tôi sẽ thử:

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

(IIRC, commit () hoạt động mà không cần flush ()).

Tôi nhận thấy rằng đôi khi thực hiện một truy vấn lớn và sau đó lặp lại trong python có thể nhanh hơn tới 2 bậc lớn hơn rất nhiều truy vấn. Tôi giả sử rằng việc lặp qua đối tượng truy vấn kém hiệu quả hơn việc lặp qua danh sách được tạo bởi phương thức all () của đối tượng truy vấn.

[Xin lưu ý bình luận bên dưới - điều này không làm tăng tốc độ chút nào].


2
Thêm .all () và xóa .flush () không thay đổi thời gian.
John Fouhy

1

Nếu đó là do chi phí về mặt tạo đối tượng, thì có lẽ không thể tăng tốc với SA.

Nếu đó là do nó đang tải các đối tượng liên quan, thì bạn có thể thực hiện điều gì đó với tải chậm. Có rất nhiều đối tượng được tạo ra do tham chiếu không? (IE, nhận đối tượng Công ty cũng nhận được tất cả các đối tượng Con người có liên quan).


Không, cái bàn là của riêng nó. Tôi chưa bao giờ sử dụng ORM trước đây - có phải đây chỉ là thứ mà họ kém?
John Fouhy

1
Có một khoản chi phí do việc tạo Đối tượng, nhưng theo ý kiến ​​của tôi, điều đó đáng bị phạt - việc có thể liên tục lưu trữ các đối tượng trong cơ sở dữ liệu thật tuyệt vời.
Matthew Schinckel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.