Sessions / Queries¶
I’m re-loading data with my Session but it isn’t seeing changes that I committed elsewhere¶
この動作に関する主な問題は、セッションが、トランザクションが分離状態でなくても(通常はそうではありませんが)、あたかも分離状態であるかのように動作することです。実際には、これは、セッションがトランザクションのスコープ内ですでに読み取られたデータを変更しないことを意味します。
“Isolation Level”という用語がよくわからない場合は、まず次のリンクを読む必要があります。
簡単に言えば、直列化可能なアイソレーションレベルとは、一般的に、トランザクション内の一連の行をSELECTすると、そのSELECTを再生成するたびに*同一のデータ*が返されることを意味します。次に低いアイソレーションレベルである”repeatable read”では、新たに追加された行が表示されます(削除された行は表示されなくなります)が、 すでに ロードされた行については、何の変更も表示されません。”read committed”など、より低いアイソレーションレベルである場合にのみ、データの行の値が変更されるのを確認できます。
SQLAlchemy ORMを使用する際のアイソレーションレベルの制御については、 Setting Transaction Isolation Levels / DBAPI AUTOCOMMIT を参照してください。
物事を劇的に単純化するために、 Session
自体は完全に分離されたトランザクションの観点から動作し、あなたが指示しない限り、既に読み込まれたマップされた属性を上書きしません。進行中のトランザクションで既にロードしたデータを再読み込みしようとするユースケースは、多くの場合効果がない 珍しい ユースケースであるため、これは標準ではなく例外と見なされます。この例外内で動作するために、進行中のトランザクションのコンテキスト内で特定のデータを再ロードできるようにするいくつかのメソッドが提供されています。
Session
について話すときの”トランザクション”の意味を理解するために、 Session
はトランザクション内でのみ動作するように意図されています。この概要は Managing Transactions にあります。
アイソレーションレベルが何であるかを理解し、アイソレーションレベルが十分に低いレベルに設定されているため、行を再SELECTした場合に Session
内に新しいデータが表示されるはずだと考えた場合、どのように表示されますか?
最も一般的なものから順に、次の3つの方法があります。
単にトランザクションを終了し、次のアクセス時に
Session
でSession.commit()
を呼び出して新しいトランザクションを開始します(Session
があまり使用されていない”autocommit”モードの場合、Session.begin`も 呼び出されることに注意してください)。ほとんどのアプリケーションやユースケースでは、他のトランザクションのデータを"見る"ことができないという問題はありません。これは、 **短命なトランザクション** のベストプラクティスの中核であるこのパターンに固執しているからです。この点については :ref:`session_faq_whentocreate()
を参照してください。
次に
Session.expire_all()
またはSession.expire()
を使って行を問い合わせたとき、またはrefresh
を使ってオブジェクトに対してすぐに、すでに読み込んだ行を再度読み込むようにSession
に指示します。詳細は Refreshing / Expiring を参照してください。
“populate existing”を使用して、既にロードされたオブジェクトが行を読み込むときに確実に上書きするように設定しながら、クエリ全体を実行できます。これは Populate Existing で説明されている実行オプションです。
しかし、 アイソレーションレベルが繰り返し読み取り以上の場合、新しいトランザクションを開始しない限り、ORMは行の変更を見ることができない ことを覚えておいてください。
“This Session’s transaction has been rolled back due to a previous exception during flush.” (or similar)¶
これは、 Session.flush()
が例外を発生させ、トランザクションをロールバックしたが、 Session
のその後のコマンドが Session.rollback()
または Session.close()
を明示的に呼び出さずに呼び出された場合に発生するエラーです。
これは通常、 Session.flush()
または Session.commit()
で例外をキャッチし、その例外を適切に処理しないアプリケーションに対応します。例えば:
from sqlalchemy import create_engine, Column, Integer
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base(create_engine("sqlite://"))
class Foo(Base):
__tablename__ = "foo"
id = Column(Integer, primary_key=True)
Base.metadata.create_all()
session = sessionmaker()()
# constraint violation
session.add_all([Foo(id=1), Foo(id=1)])
try:
session.commit()
except:
# ignore error
pass
# continue using session without rolling back
session.commit()
Session
の使い方は、次のような構造の中に収まるはずです:
try:
# <use session>
session.commit()
except:
session.rollback()
raise
finally:
session.close() # optional, depends on use case
try/except内では、フラッシュ以外にもさまざまな原因で障害が発生する可能性があります。アプリケーションは、接続リソースとトランザクション・リソースが明確な境界を持つように、また、何らかの障害条件が発生した場合にトランザクションを明示的にロールバックできるように、何らかの「フレーミング」システムがORM指向のプロセスに適用されることを保証する必要があります。
これは、スケーラブルなアーキテクチャではない、アプリケーション全体にtry/exceptブロックがあるべきだという意味ではありません。代わりに、ORM指向のメソッドと関数が最初に呼び出されたとき、一番上から関数を呼び出すプロセスは、一連の操作が正常に完了したときにトランザクションをコミットし、失敗したフラッシュを含む何らかの理由で操作が失敗した場合にトランザクションをロールバックするブロック内にあるというのが一般的なアプローチです。同様の結果を得るために、関数デコレータやコンテキストマネージャを使用するアプローチもあります。採用されるアプローチの種類は、作成されるアプリケーションの種類に大きく依存します。
Session
の使い方の詳細については、 When do I construct a Session, when do I commit it, and when do I close it? を参照してください。
But why does flush() insist on issuing a ROLLBACK?¶
Session.flush()
が部分的に完了し、その後ロールバックできないようにしたいのですが、これは現在の機能を超えています。なぜなら、内部の記録は、いつでも停止でき、データベースにフラッシュされたものと正確に一致するように修正する必要があるからです。これは理論的には可能ですが、多くのデータベース操作がいかなる場合にもROLLBACKを必要とするという事実によって、強化の有用性は大幅に減少します。特にPostgresには、一度失敗するとトランザクションを継続できない操作があります。
test=> create table foo(id integer primary key);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"
CREATE TABLE
test=> begin;
BEGIN
test=> insert into foo values(1);
INSERT 0 1
test=> commit;
COMMIT
test=> begin;
BEGIN
test=> insert into foo values(1);
ERROR: duplicate key value violates unique constraint "foo_pkey"
test=> insert into foo values(2);
ERROR: current transaction is aborted, commands ignored until end of transaction block
SQLAlchemyが両方の問題を解決するために提供しているのは、 Session.begin_nested()
を介したSAVEPOINTのサポートです。 Session.begin_nested()
を使用すると、トランザクション内で失敗する可能性のある操作をフレーム化し、その後、包含するトランザクションを維持しながら、失敗する前のポイントに「ロールバック」できます。
But why isn’t the one automatic call to ROLLBACK enough? Why must I ROLLBACK again?¶
flush()によって引き起こされるロールバックは、完全なトランザクションブロックの終わりではありません。これがデータベーストランザクションを終了している間、 Session
の観点からは、現在非アクティブ状態にあるトランザクションがまだ存在しています。
以下のようなブロックが与えられた場合:
sess = Session() # begins a logical transaction
try:
sess.flush()
sess.commit()
except:
sess.rollback()
上記では、”オートコミット・モード”が使用されていないと仮定して、 Session
が最初に作成されると、 Session
内に論理トランザクションが確立されます。このトランザクションは、SQL文が呼び出されるまでデータベース・リソースを実際に使用しないという点で「論理」です。SQL文が呼び出されると、接続レベルおよびDB APIレベルのトランザクションが開始されます。ただし、データベースレベルのトランザクションがその状態の一部であるかどうかにかかわらず、論理トランザクションは、 Session.commit()
、 Session.rollback()
、または Session.close()
を使用して終了されるまでそのまま残ります。
上記の flush()
が失敗した場合、コードはまだtry/commit/except/rollbackブロックによってフレーム化されたトランザクション内にあります。もし flush()
が論理トランザクションを完全にロールバックする場合、それは次に except:
ブロックに到達したときに、 Session
がクリーンな状態になり、すべての新しいトランザクションで新しいSQLを発行する準備ができていることを意味し、 Session.rollback()
への呼び出しは順序が正しくありません。特に、 Session
はこの時点までに新しいトランザクションを開始しており、 Session.rollback()
は誤って動作しています。通常の使用方法でロールバックが行われようとしているこの場所で新しいトランザクションに対してSQL操作を続行させるのではなく、 Session
は明示的なロールバックが実際に行われるまで続行を拒否します。
言い換えれば、呼び出し元のコードが現在のトランザクションブロックに対応する Session.commit()
、 Session.rollback()
、または Session.close()
を 常に 呼び出すことが期待されます。flush()
は、上記のコードの動作が予測可能で一貫性のあるものとなるように、このトランザクションブロック内に Session
を保持します。
How do I make a Query that always adds a certain filter to every query?¶
See the recipe at FilteredQuery.
FilteredQuery のレシピを参照してください。
My Query does not return the same number of objects as query.count() tells me - why?¶
Query
オブジェクトは、ORMマップされたオブジェクトのリストを返すように要求されると、 プライマリキーに基づいてオブジェクトを重複除外します 。つまり、例えば Using ORM Declarative Forms to Define Table Metadata で説明されている User
マッピングを使用し、次のようなSQLクエリがあったとします。:
q = session.query(User).outerjoin(User.addresses).filter(User.name == "jack")
上の例では、チュートリアルで使用したサンプルデータの addresses
テーブルに、 users
行の2つの行があり、名前は jack
、主キーの値は5です。上のクエリに Query.count()
を要求すると、 2 という答えが得られます。:
>>> q.count()
2
しかし、 Query.all()
を実行したり、問い合わせに対して繰り返しを行うと、 1つの要素 が返されます:
>>> q.all()
[User(id=5, name='jack', ...)]
これは、 Query
オブジェクトが完全な実体を返す場合、それらは 重複除外 されるからです。
>>> session.query(User.id, User.name).outerjoin(User.addresses).filter(
... User.name == "jack"
... ).all()
[(5, 'jack'), (5, 'jack')]
Query
が重複除外を行う主な理由は2つあります。:
結合されたEagerローディングが正しく動作するようにするには - Joined Eager Loading は、関連するテーブルに対して結合を使用して行を照会し、それらの結合からの行をリードオブジェクト上のコレクションにルーティングします。これを行うためには、各サブエントリに対して先頭オブジェクトのプライマリ・キーが繰り返されるローをフェッチする必要があります。このパターンは、その後、複数の行が
User(id=5)
のような単一のリードオブジェクトに対して処理されるように、さらなるサブコレクションに続くことができる。この宣言によって、問い合わせた通りにオブジェクトを受け取ることができます。例えば、名前がjack
であるすべてのUser()
オブジェクトは、relationship()
のlazy='joined'
またはjoinedload()
オプションで示されるように、User.addresses
コレクションが積極的にロードされた1つのオブジェクトです。一貫性のために、結合ロードが確立されているかどうかにかかわらず、重複除外は引き続き適用されます。これは、Eager Loadingの背後にある重要な考え方は、これらのオプションが結果に影響を与えないということです。
IDマップに関する混乱を避けるため - これは確かにそれほど重要ではありません。
Session
は identity map を利用しているので、SQLの結果セットには主キーが5の行が2つありますが、Session
内にはUser(id=5)
オブジェクトが1つしかありません。これはそのID、つまり主キーとクラスの組み合わせで一意に保持されなければなりません。実際には、User()
オブジェクトを問い合わせている場合、リスト内で同じオブジェクトを複数回取得することはあまり意味がありません。順序付きセットは、Query
が完全なオブジェクトを返したときに何を返そうとしているのかをよりよく表している可能性があります。
Query
の重複除外の問題は、主に Query.count()
メソッドに一貫性がないという1つの理由から、依然として問題があります。現在の状況では、結合されたEager Loadingは、最近のリリースでは最初に”subquery eager loading”方式に、最近では”select IN eager loading”方式に取って代わられました。どちらも一般的にコレクションのEager Loadingに適しています。この進化が続くにつれて、SQLAlchemyは Query
のこの動作を変更する可能性があります。これには、この動作をより直接的に制御するための新しいAPIが含まれる可能性があります。また、より一貫性のある使用パターンを作成するために、結合されたEager Loadingの動作を変更する可能性もあります。
I’ve created a mapping against an Outer Join, and while the query returns rows, no objects are returned. Why not?¶
外部結合で返される行には、主キーの一部にNULLが含まれることがあります。これは、主キーが両方のテーブルの合成であるためです。 Query
オブジェクトは、受け入れ可能な主キーを持たない入力行を無視します。 Mapper
の``allow_partial_pks`フラグの設定に基づいて、値が少なくとも1つの非NULL値を持つ場合、または値がNULL値を持たない場合に、主キーが受け入れられます。 Mapper
の allow_partial_pks
を参照してください。
I’m using joinedload()
or lazy=False
to create a JOIN/OUTER JOIN and SQLAlchemy is not constructing the correct query when I try to add a WHERE, ORDER BY, LIMIT, etc. (which relies upon the (OUTER) JOIN)¶
結合されたEager Loadingによって生成される結合は、関連するコレクションを完全にロードするためにのみ使用され、クエリのプライマリ結果に影響を与えないように設計されています。これらは匿名でエイリアスが設定されているため、直接参照することはできません。
この動作の詳細については、 The Zen of Joined Eager Loading を参照してください。
Query has no __len__()
, why not?¶
Pythonのマジックメソッド __len__()
をオブジェクトに適用すると、組み込みコマンドの __len()
を使ってコレクションの長さを決定することができます。直感的には、SQLクエリオブジェクトが __len__()
を Query.count()
メソッドにリンクして、 SELECT COUNT を発行すると思います。これができないのは、クエリをリストとして評価すると、1回ではなく2回のSQL呼び出しが必要になるからです:
class Iterates:
def __len__(self):
print("LEN!")
return 5
def __iter__(self):
print("ITER!")
return iter([1, 2, 3, 4, 5])
list(Iterates())
アウトプット:
ITER!
LEN!
How Do I use Textual SQL with ORM Queries?¶
以下を参照してください。:
Getting ORM Results from Textual Statements -
Query
を持つアドホックなテキストブロック
Using SQL Expressions with Sessions - テキストSQLで直接
Session
を使用します。
I’m calling Session.delete(myobject)
and it isn’t removed from the parent collection!¶
この動作についての説明は Notes on Delete - Deleting Objects Referenced from Collections and Scalar Relationships を参照してください。
why isn’t my __init__()
called when I load objects?¶
この動作の説明については Maintaining Non-Mapped State Across Loads を参照してください。
how do I use ON DELETE CASCADE with SA’s ORM?¶
SQLAlchemyは、現在 Session
にロードされている従属行に対して、常にUPDATEまたはDELETE文を発行します。ロードされていない行に対しては、デフォルトでSELECT文を発行して、それらの行をロードし、更新/削除も行います。つまり、ON DELETE CASCADEが設定されていないことを前提としています。ON DELETE CASCADEと連携するようにSQLAlchemyを設定するには、 Using foreign key ON DELETE cascade with ORM relationships を参照してください。
I set the “foo_id” attribute on my instance to “7”, but the “foo” attribute is still None
- shouldn’t it have loaded Foo with id #7?¶
ORMは、外部キー属性の変更によって駆動される関係の即時生成をサポートするように構築されているのではありません。その代わりに、それは逆に機能するように設計されています。外部キー属性はORMによって背後で処理され、エンドユーザは自然にオブジェクト関係を設定します。したがって、 o.foo
を設定する推奨される方法は、それを行うことです-それを設定します!:
foo = session.get(Foo, 7)
o.foo = foo
Session.commit()
外部キー属性の操作はもちろん完全に合法です。しかし、現在のところ、外部キー属性を新しい値に設定しても、それが関係する relationship()
の”expire”イベントはトリガされません。つまり、次のシーケンスでは:
o = session.scalars(select(SomeClass).limit(1)).first()
# assume the existing o.foo_id value is None;
# accessing o.foo will reconcile this as ``None``, but will effectively
# "load" the value of None
assert o.foo is None
# now set foo_id to something. o.foo will not be immediately affected
o.foo_id = 7
o.foo
is loaded with its effective database value of None
when it is first accessed. Setting o.foo_id = 7
will have the value of “7” as a pending change, but no flush has occurred - so o.foo
is still None
:
``o.foo`` は、最初にアクセスされたときに、有効なデータベース値である ``None`` でロードされます。 ``o.foo_id=7`` を設定すると、保留中の変更として ``7`` の値がありますが、フラッシュは発生していないので、 ``o.foo`` はまだ ``None`` のままです::
# attribute is already "loaded" as None, has not been
# reconciled with o.foo_id = 7 yet
assert o.foo is None
o.foo
が外部キー変換に基づいてロードされるのは、通常、コミット後に自然に達成されます。コミットは、新しい外部キー値をフラッシュし、すべての状態を期限切れにします。:
session.commit() # expires all attributes
foo_7 = session.get(Foo, 7)
# o.foo will lazyload again, this time getting the new object
assert o.foo is foo_7
より最小限の操作は、属性を個別に期限切れにすることです。これは Session.expire()
を使用して任意の persistent オブジェクトに対して実行できます:
o = session.scalars(select(SomeClass).limit(1)).first()
o.foo_id = 7
Session.expire(o, ["foo"]) # object must be persistent for this
foo_7 = session.get(Foo, 7)
assert o.foo is foo_7 # o.foo lazyloads on access
オブジェクトが永続的ではなく、 Session
に存在する場合は、 pending と呼ばれることに注意してください。これは、オブジェクトの行がまだデータベースに挿入されていないことを意味します。このようなオブジェクトでは、行が挿入されるまで foo_id
を設定しても意味がありません。そうでなければ、まだ行はありません:
new_obj = SomeClass()
new_obj.foo_id = 7
Session.add(new_obj)
# returns None but this is not a "lazyload", as the object is not
# persistent in the DB yet, and the None value is not part of the
# object's state
assert new_obj.foo is None
Session.flush() # emits INSERT
assert new_obj.foo is foo_7 # now it loads
ExpireRelationshipOnFKChange のレシピでは、多対1の関係を持つ外部キー属性の設定を調整するために、SQLAlchemyイベントを使用する例を取り上げています。
Is there a way to automagically have only unique keywords (or other kinds of objects) without doing a query for the keyword and getting a reference to the row containing that keyword?¶
ドキュメントの多対多の例を読むと、同じ Keyword
を2回作成すると、DBに2回追加されるという事実に驚かされます。これは少し不便です。
この UniqueObject レシピは、この問題に対処するために作成されました。
Why does post_update emit UPDATE in addition to the first UPDATE?¶
post_update機能は Rows that point to themselves / Mutually Dependent Rows で説明されていますが、対象行に対して通常発行されるINSERT/UPDATE/DELETEに加えて、特定の関係にバインドされた外部キーへの変更に応じてUPDATE文が発行されます。このUPDATE文の主な目的は、その行のINSERTまたはDELETEと対になって、相互に依存する外部キーとのサイクルを断ち切るために外部キー参照を事前設定または事前設定解除できるようにすることですが、現在では、対象行自体がUPDATEの対象となったときに発行される2番目のUPDATEとしてもバンドルされています。この場合、post_updateによって発行されるUPDATEは 通常 不要であり、しばしば無駄に見えます。
しかし、この”UPDATE/UPDATE”動作を削除しようとするいくつかの研究では、post_update実装全体だけでなく、post_updateに関係のない領域でも作業単位プロセスに大きな変更を加える必要があることが明らかになっています。この場合、操作の順序を非post_update側で逆にする必要がある場合があり、その結果、参照される主キー値のUPDATEを正しく処理するなど、他の場合にも影響を与える可能性があります(概念実証については #1063 を参照)。
その答えは、”post_update”が相互に依存する2つの外部キー間のサイクルをブレークするために使用され、このサイクルブレークをターゲットテーブルのINSERT/DELETEだけに制限することは、他の場所でのUPDATE文の順序を自由にする必要があることを意味し、他のエッジケースでのブレークにつながるということです。