SQLAlchemy ORM을 사용하여 효율적으로 데이터베이스 업데이트


116

새 응용 프로그램을 시작하고 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()

이것은 옳은 일을하지만 다른 두 가지 접근 방식보다 50 배도 채 걸리지 않습니다. 나는 그것이 작동하기 전에 모든 데이터를 메모리로 가져와야하기 때문이라고 생각합니다.

SQLAlchemy의 ORM을 사용하여 효율적인 SQL을 생성하는 방법이 있습니까? 아니면 다른 파이썬 ORM을 사용하고 계십니까? 아니면 손으로 SQL 작성으로 돌아 가야합니까?


1
좋아, 나는 대답이 "이것은 ORM이 잘하는 일이 아니다"라고 가정하고있다. 오 잘. 나는 살고 배웁니다.
John Fouhy

서로 다른 ORM에서 실행 된 몇 가지 실험과 부하 및 협박 하에서 수행되는 방법이 있습니다. 편리한 링크는 없지만 읽을만한 가치가 있습니다.
Matthew Schinckel

마지막 (ORM) 예제에서 존재하는 또 다른 문제는 원자가 아니라는 것입니다 .
Marian

답변:


181

SQLAlchemy의 ORM은 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업데이트 문에 추가 하고 해당 선택을 제거 할 수도 있습니다 .


2
세 번째 방법으로 orm 이벤트 (after_update와 같은)를 트리거합니까?

@Ken, 아니, 그렇지 않습니다. Query.update docs.sqlalchemy.org/en/13/orm/…에 대한 API 문서를 참조하십시오 . 대신 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()

이것을 시도하십시오 =)


이 방법은 저에게 효과적이었습니다. 그러나 문제는 느리다는 것입니다. 10 만 개의 데이터 기록을 위해 좋은 시간이 필요합니다. 더 빠른 방법이 있습니까?
baermathias dec

이 접근 방식이 저에게 효과적이었습니다. 그 sqlachemy이 업데이트 짧은 방법이되지 않습니다 정말 나쁜 json
자이 프라 카쉬

6
이 방법을 사용할 때 여전히 성능 문제가있는 경우 : 기본적으로 모든 레코드에 대해 먼저 SELECT를 수행하고 나중에 UPDATE 만 수행 할 수 있습니다. sync_session = False를 update () 메소드에 전달하면 이런 일이 발생하지 않지만 commit () 전에 다시 업데이트하는 객체를 사용하지 않는 경우에만이 작업을 수행해야합니다.
teuneboon

25

sqlalchemy를 사용하여 UPDATE하는 방법에는 여러 가지가 있습니다.

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

다음은 필드를 수동으로 매핑하지 않고도 동일한 문제를 해결하는 방법의 예입니다.

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

1

테스트를 거치지 않고 시도해 보겠습니다.

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

(IIRC, commit ()은 flush ()없이 작동합니다).

나는 때때로 큰 쿼리를 수행 한 다음 파이썬에서 반복하는 것이 많은 쿼리보다 최대 2 배 더 빠를 수 있음을 발견했습니다. 쿼리 객체를 반복하는 것이 쿼리 객체의 all () 메서드에 의해 생성 된 목록을 반복하는 것보다 덜 효율적이라고 가정합니다.

[아래에 주석을 달아주세요. 속도가 전혀 빨라지지는 않았습니다].


2
.all ()을 추가하고 .flush ()를 제거해도 시간이 전혀 변경되지 않았습니다.
John Fouhy

1

객체 생성의 오버 헤드 때문이라면 아마도 SA로 속도를 높일 수 없을 것입니다.

관련 객체를로드하기 때문이라면 지연로드로 작업을 수행 할 수 있습니다. 참조로 인해 생성되는 개체가 많이 있습니까? (IE, Company 개체를 가져 오면 관련된 모든 People 개체도 가져옵니다).


아니, 테이블 자체가 전부입니다. 나는 전에 ORM을 사용한 적이 없습니다-이것이 그들이 나쁜 것입니까?
John Fouhy

1
객체 생성으로 인한 오버 헤드가 있지만 제 생각에는 그만한 가치가 있습니다. 객체를 데이터베이스에 지속적으로 저장할 수 있다는 것은 대단합니다.
Matthew Schinckel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.