Эффективное обновление базы данных с использованием SQLAlchemy ORM - PullRequest
90 голосов
/ 07 ноября 2008

Я запускаю новое приложение и смотрю на использование ORM - в частности, SQLAlchemy.

Допустим, в моей базе данных есть столбец 'foo', и я хочу увеличить его. В прямом sqlite это легко:

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

Я вычислил эквивалент SQLAlchemy для SQL-компоновщика:

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 ]

152 голосов
/ 10 ноября 2008

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 в оператор обновления и избавиться от этого выбора.

78 голосов
/ 27 декабря 2010
session.query(Clients).filter(Clients.id == client_id_list).update({'status': status})
session.commit()

Попробуйте это =)

18 голосов
/ 10 ноября 2015

Есть несколько способов ОБНОВИТЬ использование 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)
1 голос
/ 08 сентября 2015

Вот пример того, как решить ту же проблему без необходимости вручную сопоставлять поля:

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 = Media(id=123, title="Titular Line", slug="titular-line", type="movie")
media.update()
0 голосов
/ 07 ноября 2008

Если это связано с накладными расходами в плане создания объектов, то, вероятно, его вообще нельзя ускорить с помощью SA.

Если это происходит из-за загрузки связанных объектов, возможно, вы сможете что-то сделать с отложенной загрузкой. Много ли объектов создается по ссылкам? (IE, получая объект Company, также получает все связанные объекты People).

0 голосов
/ 07 ноября 2008

Без тестов я бы попробовал:

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

(IIRC, commit () работает без flush ()).

Я обнаружил, что время от времени выполнение большого запроса, а затем повторение в python может быть на 2 порядка быстрее, чем множество запросов. Я предполагаю, что перебор объекта запроса менее эффективен, чем перебор списка, созданного методом all () объекта запроса.

[Обратите внимание на комментарий ниже - это совсем не ускорило процесс.]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...