Эффективное обновление базы данных с помощью SQLAlchemy ORM
Я начинаю новое приложение и смотрю на использование ORM-в частности, SQLAlchemy.
скажем, у меня есть столбец " foo " в моей базе данных, и я хочу увеличить его. В прямом sqlite это легко:
db = sqlite3.connect('mydata.sqlitedb')
cur = db.cursor()
cur.execute('update table stuff set foo = foo + 1')
я вычислил эквивалент SQL-builder SQLAlchemy:
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)
Это немного медленнее, но там не так много в нем.
вот мое лучшее предположение для подхода SQLAlchemy ORM:
# 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()
Это делает правильно, но это занимает чуть меньше пятидесяти раз больше времени, чем два других подхода. Я предполагаю, что это потому, что он должен принести все данные в память, прежде чем он сможет работать с ним.
есть ли способ создать эффективный SQL с помощью ORM SQLAlchemy? Или с помощью любого другого python ORM? Или я должен просто вернуться к написанию SQL вручную?
6 ответов:
ORM SQLAlchemy предназначен для использования вместе со слоем SQL, а не скрывать его. Но вы должны иметь в виду одну или две вещи при использовании ORM и простого SQL в одной транзакции. В принципе, с одной стороны, изменения данных ORM будут попадать в базу данных только при сбросе изменений из сеанса. С другой стороны, операторы обработки данных SQL не влияют на объекты, которые находятся в вашем сеансе.
Так что если вы говорите
for c in session.query(Stuff).all(): c.foo = c.foo+1 session.commit()
он будет делать то, что он говорит, что нужно извлечь все объекты из базы данных, изменить все объекты, а затем, когда пришло время сбросить изменения в базу данных, обновить строки один за другим.
вместо этого вы должны сделать это:
session.execute(update(stuff_table, values={stuff_table.c.foo: stuff_table.c.foo + 1})) session.commit()
Это будет выполняться как один запрос, как и следовало ожидать, и поскольку по крайней мере конфигурация сеанса по умолчанию истекает все данные в сеансе при фиксации, у вас нет проблем с устаревшими данными.
в почти выпущенной серии 0.5 вы также можете использовать это способ обновления:
session.query(Stuff).update({Stuff.foo: Stuff.foo + 1}) session.commit()
это будет в основном выполнять ту же инструкцию SQL, что и предыдущий фрагмент, но также выбрать измененные строки и истекают любые устаревшие данные в сеансе. Если вы знаете, что не используете данные сеанса после обновления, вы также можете добавить synchronize_session=False в инструкцию update и избавиться от этого выбора.
session.query(Clients).filter(Clients.id == client_id_list).update({'status': status}) session.commit()
попробуйте это =)
существует несколько способов обновления с помощью 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)
вот пример того, как решить ту же проблему без необходимости сопоставления полей вручную:
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()
таким образом, чтобы обновить экземпляр носителя, вы можете сделать что-то вроде этого:
media = Media(id=123, title="Titular Line", slug="titular-line", type="movie") media.update()
без тестирования, я бы попробовал:
for c in session.query(Stuff).all(): c.foo = c.foo+1 session.commit()
(IIRC, commit () работает без flush ()).
Я обнаружил, что иногда выполнение большого запроса, а затем итерация в python может быть на 2 порядка быстрее, чем множество запросов. Я предполагаю, что итерация по объекту запроса менее эффективна, чем итерация по списку, созданному методом all() объекта запроса.
[Пожалуйста, обратите внимание комментарий ниже-это не ускоряет вещи вообще].
Если это из-за накладных расходов с точки зрения создания объектов, то он, вероятно, не может быть ускорен вообще с SA.
Если это связано с загрузкой связанных объектов, то вы можете сделать что-то с ленивой загрузкой. Много ли объектов создается из-за ссылок? (Т. е. получение объекта компании также получает все связанные объекты людей).