私は現在SQLAlchemy(GoogleのクラウドMySQLに接続しているGAE上)を使用し、テーブルの一括更新を行う必要があるWebアプリケーション(Flask)を作成しています。要するに、多くの計算が行われ、結果として1000のオブジェクトで更新する必要がある単一の値になります。現時点では、トランザクションですべてをやっていますが、最後には、フラッシュ/コミットに時間がかかります。SQLAlchemy一括更新の戦略
テーブルのインデックスはid
であり、これはすべて1回のトランザクションで実行されます。だから、私はいつもの間違いを避けたと信じていますが、それはまだ非常に遅いです。
INFO 2017-01-26 00:45:46,412 log.py:109] UPDATE wallet SET balance=%(balance)s WHERE wallet.id = %(wallet_id)s
2017-01-26 00:45:46,418 INFO sqlalchemy.engine.base.Engine ({'wallet_id': u'3c291a05-e2ed-11e6-9b55-19626d8c7624', 'balance': 1.8711760000000002}, {'wallet_id': u'3c352035-e2ed-11e6-a64c-19626d8c7624', 'balance': 1.5875759999999999}, {'wallet_id': u'3c52c047-e2ed-11e6-a903-19626d8c7624', 'balance': 1.441656}
私の理解から、そこに実際にSQLでの一括更新を実行する方法はありませんし、上記のステートメントがサーバーに送信される複数のUPDATEステートメントなってしまいます。
私はSession.bulk_update_mappings()
を使用しようとしましたが、実際に何もしていないようです:(理由はわかりませんが、実際には更新は実際には起こりません。パフォーマンススイート)を使用しようとしているかどうかは分かりません。
One technique I've seen discussed別のテーブルに一括挿入してからUPDATE JOINを実行しています。以下のようなテストを行いました。
wallets = db_session.query(Wallet).all()
ledgers = [ Ledger(id=w.id, amount=w._balance) for w in wallets ]
db_session.bulk_save_objects(ledgers)
db_session.execute('UPDATE wallet w JOIN ledger l on w.id = l.id SET w.balance = l.amount')
db_session.execute('TRUNCATE ledger')
しかし、問題は私のコードをどのように構造化するかです。私はORMを使用しています。私はどうにかして元のWallet
オブジェクトを「汚い」ことなく、古い方法でコミットしないようにする必要があります。私はちょうどこれらのLedger
オブジェクトを代わりに作成し、それらのリストを保持して、手動で一括操作の最後にそれらを挿入することができます。しかし、ORMの仕組みの一部を複製しているような臭いはほとんどありません。
これを行うにはスマートな方法がありますか?これまでのところ、私の脳が何か下がっている:私が言ったように
class Wallet(Base):
...
_balance = Column(Float)
...
@property
def balance(self):
# first check if we have a ledger of the same id
# and return the amount in that, otherwise...
return self._balance
@balance.setter
def balance(self, amount):
l = Ledger(id=self.id, amount=amount)
# add l to a list somewhere then process later
# At the end of the transaction, do a bulk insert of Ledgers
# and then do an UPDATE JOIN and TRUNCATE
を、このすべては、私が持っている(こと)ツールと戦っているようです。これを処理するより良い方法はありますか?これを行うには、ORMメカニズムを利用できますか?あるいは、一括更新を行うためのより良い方法がありますか?
EDIT:または、イベントやセッションで賢明なことがありますか?たぶんbefore_flush?
EDIT 2:だから私は今イベントの機械を活用し、しようとしているが、これを持っている:
私にはかなりハックと悪のようだが、正常に動作するように見える@event.listens_for(SignallingSession, 'before_flush')
def before_flush(session, flush_context, instances):
ledgers = []
if session.dirty:
for elem in session.dirty:
if (session.is_modified(elem, include_collections=False)):
if isinstance(elem, Wallet):
session.expunge(elem)
ledgers.append(Ledger(id=elem.id, amount=elem.balance))
if ledgers:
session.bulk_save_objects(ledgers)
session.execute('UPDATE wallet w JOIN ledger l on w.id = l.id SET w.balance = l.amount')
session.execute('TRUNCATE ledger')
。どんな落とし穴、またはより良いアプローチ?
-Matt
はい、私はORMとの戦いがあまりにも多すぎると思います。問題は、私はすでにこれのほとんどのためにORMを使用しているので、私は上記のフォームの何かを行うことができるようにそれを得るためにかなりの書き直しが必要になります。 'calculate_new_balance()'は以前の値を計算したものです(このユースケースはネットワークを介して資金を送り出しています)ので、モデルと元帳の両方を調べてみる必要がありますそれは正しい値を持っていた。しかし、ありがとう、これは確かに私に考えのための食糧を与えた! –