Additional Persistence Techniques¶
Embedding SQL Insert/Update Expressions into a Flush¶
この機能を使用すると、データベース列の値をリテラル値ではなくSQL式に設定できます。これは特に、アトミックな更新やストアドプロシージャの呼び出しなどに便利です。属性に式を代入するだけです:
class SomeClass(Base):
__tablename__ = "some_table"
# ...
value = mapped_column(Integer)
someobject = session.get(SomeClass, 5)
# set 'value' attribute to a SQL expression adding one
someobject.value = SomeClass.value + 1
# issues "UPDATE some_table SET value=value+1"
session.commit()
このテクニックはINSERT文とUPDATE文の両方に有効です。flush/commit操作の後、上記の someobject
の value
属性は期限切れになり、次にアクセスされたときに、新しく生成された値がデータベースからロードされます。
この機能には、主キー列と連携して動作する条件付きサポートもあります。RETURNINGサポートを持つバックエンドでは、SQL式を主キー列に割り当てることもできます。これにより、SQL式を評価することも、INSERT時に主キー値を変更するサーバー側のトリガをオブジェクトの主キーの一部としてORMによって正常に取得することもできます。:
class Foo(Base):
__tablename__ = "foo"
pk = mapped_column(Integer, primary_key=True)
bar = mapped_column(Integer)
e = create_engine("postgresql+psycopg2://scott:tiger@localhost/test", echo=True)
Base.metadata.create_all(e)
session = Session(e)
foo = Foo(pk=sql.select(sql.func.coalesce(sql.func.max(Foo.pk) + 1, 1)))
session.add(foo)
session.commit()
PostgreSQLでは、上記の Session
は以下のINSERTを発行します。:
INSERT INTO foo (foopk, bar) VALUES
((SELECT coalesce(max(foo.foopk) + %(max_1)s, %(coalesce_2)s) AS coalesce_1
FROM foo), %(bar)s) RETURNING foo.foopk
New in version 1.3: .. SQL expressions can now be passed to a primary key column during an ORM flush; if the database supports of the primary key attribute.
データベースがプライマリ・キー属性をサポートしている場合、ORMフラッシュ時にSQL式をプライマリ・キー列に渡せるようになりました。
Using SQL Expressions with Sessions¶
SQL式と文字列は、そのトランザクションコンテキスト内で Session
を介して実行することができます。これは Session.execute()
メソッドを使って最も簡単に実現できます。このメソッドは CursorResult
を Engine
や Connection
と同じように返します:
Session = sessionmaker(bind=engine)
session = Session()
# execute a string statement
result = session.execute(text("select * from table where id=:id"), {"id": 7})
# execute a SQL expression construct
result = session.execute(select(mytable).where(mytable.c.id == 7))
Session
が保持している現在の Connection
は、 Session.connection()
メソッドを使ってアクセスできます:
connection = session.connection()
上記の例では、単一の Engine
または Connection
にバインドされた Session
を扱っています。複数のエンジンにバインドされているか、まったくバインドされていない(つまり、バインドされたメタデータに依存している) Session
を使用して文を実行するには、 Session.execute()
と Session.connection()
の両方がバインド引数の辞書を受け入れます。 Session.execute.bind_arguments
には、マップされたクラスまたは Mapper
インスタンスに渡される”mapper”を含めることができます。これは、目的のエンジンの適切なコンテキストを見つけるために使用されます:
Session = sessionmaker()
session = Session()
# need to specify mapper or class when executing
result = session.execute(
text("select * from table where id=:id"),
{"id": 7},
bind_arguments={"mapper": MyMappedClass},
)
result = session.execute(
select(mytable).where(mytable.c.id == 7), bind_arguments={"mapper": MyMappedClass}
)
connection = session.connection(MyMappedClass)
Changed in version 1.4: Session.execute()
への mapper
引数と clause
引数が Session.execute.bind_arguments
パラメータとして送られる辞書の一部として渡されるようになりました。以前の引数はまだ受け入れられますが、この使用法は廃止されました。
Forcing NULL on a column with a default¶
ORMは、オブジェクトに設定されていない属性を”デフォルト”と見なします。この属性はINSERT文から省略されます。:
class MyObject(Base):
__tablename__ = "my_table"
id = mapped_column(Integer, primary_key=True)
data = mapped_column(String(50), nullable=True)
obj = MyObject(id=1)
session.add(obj)
session.commit() # INSERT with the 'data' column omitted; the database
# itself will persist this as the NULL value
INSERT文で列を省略すると、その列にNULL値が設定されることになります。ただし、列にデフォルトが設定されている場合は、デフォルト値が保持されます。これは、純粋なSQLの観点から見ると、サーバ側のデフォルトの場合にも、クライアント側とサーバ側のデフォルトの場合のSQLAlchemyの挿入動作の場合にも当てはまります。:
class MyObject(Base):
__tablename__ = "my_table"
id = mapped_column(Integer, primary_key=True)
data = mapped_column(String(50), nullable=True, server_default="default")
obj = MyObject(id=1)
session.add(obj)
session.commit() # INSERT with the 'data' column omitted; the database
# itself will persist this as the value 'default'
しかしORMでは、Pythonの値 None
を明示的にオブジェクトに代入しても、値が代入されなかったかのように 同じ として扱われます:
class MyObject(Base):
__tablename__ = "my_table"
id = mapped_column(Integer, primary_key=True)
data = mapped_column(String(50), nullable=True, server_default="default")
obj = MyObject(id=1, data=None)
session.add(obj)
session.commit() # INSERT with the 'data' column explicitly set to None;
# the ORM still omits it from the statement and the
# database will still persist this as the value 'default'
上記の操作は、たとえ None
が渡されたとしても、 data
列にはSQL NULLではなく、サーバのデフォルト値である default
が保持されます。これは、多くのアプリケーションが想定しているORMの長年の動作です。
では、列にデフォルト値があっても、この列に実際にNULLを入れたい場合はどうすればよいでしょうか?2つの方法があります。1つは、インスタンスごとのレベルで、 null
SQL構文を使って属性を割り当てる方法です:
from sqlalchemy import null
obj = MyObject(id=1, data=null())
session.add(obj)
session.commit() # INSERT with the 'data' column explicitly set as null();
# the ORM uses this directly, bypassing all client-
# and server-side defaults, and the database will
# persist this as the NULL value
null
SQL構文は、常にターゲットのINSERT文に直接存在するSQL NULL値に変換されます。
Pythonの値 None
を使用し、列のデフォルトが存在してもこれをNULLとして保持したい場合は、コアレベルの修飾子 TypeEngine.evaluates_none()
を使用してORMにこれを設定できます。これは、ORMが値 None
を”欠落した”値として省略するのではなく、他の値と同じように処理して通過させる型を示します:
class MyObject(Base):
__tablename__ = "my_table"
id = mapped_column(Integer, primary_key=True)
data = mapped_column(
String(50).evaluates_none(), # indicate that None should always be passed
nullable=True,
server_default="default",
)
obj = MyObject(id=1, data=None)
session.add(obj)
session.commit() # INSERT with the 'data' column explicitly set to None;
# the ORM uses this directly, bypassing all client-
# and server-side defaults, and the database will
# persist this as the NULL value
Fetching Server-Generated Defaults¶
Server-invoked DDL-Explicit Default Expressions と Marking Implicitly Generated Values, timestamps, and Triggered Columns で紹介されているように、Coreは、データベース自身がINSERT時に値を生成し、あまり一般的ではありませんが、UPDATE文時に値を生成するデータベース列の概念をサポートしています。ORMは、フラッシュ時にこれらの新しく生成された値をフェッチできることに関して、そのような列をサポートしています。この動作は、サーバによって生成される主キー列の場合に必要です。なぜなら、ORMはオブジェクトが永続化された後にその主キーを知る必要があるからです。
ほとんどの場合、データベースによって自動的に生成された値を持つ主キー列は単純な整数列であり、いわゆる「オートインクリメント」列として、または列に関連付けられたシーケンスからデータベースによって実装されます。SQLAlchemy Core内のすべてのデータベース言語は、これらの主キー値を取得するメソッドをサポートしています。これは多くの場合、Python DBAPIにネイティブであり、一般にこのプロセスは自動的に行われます。これに関する詳細なドキュメントは Column.autoincrement
にあります。
サーバが生成した列がプライマリ・キー列でない場合、または単純なオートインクリメント整数列でない場合、ORMはこれらの列に適切な server_default
ディレクティブを付けて、ORMがこの値を取得できるようにする必要があります。ただし、すべてのメソッドがすべてのバックエンドでサポートされているわけではないので、適切なメソッドを使用するように注意する必要があります。回答すべき2つの質問は、1. この列がプライマリ・キーの一部であるかどうか、2. データベースがRETURNINGまたは”OUTPUT inserted”などの同等のものをサポートしているか、です。これらは、INSERT文またはUPDATE文が呼び出されると同時にサーバが生成した値を返すSQL句です。RETURNINGは現在、PostgreSQL、Oracle、MariaDB 10.5、SQLite 3.35、およびSQL Serverでサポートされています。
Case 1: non primary key, RETURNING or equivalent is supported¶
この場合、列は FetchedValue
として、または明示的な Column.server_default
でマークされる必要があります。ORMは、INSERT文を実行するときにこれらの列を自動的にRETURNING句に追加します。ただし、RETURNINGと insertmanyvalues の両方をサポートするダイアレクトでは、 Mapper.eager_defaults
パラメータが True
に設定されているか、デフォルトの設定である "auto"
のままであると仮定します:
class MyModel(Base):
__tablename__ = "my_table"
id = mapped_column(Integer, primary_key=True)
# server-side SQL date function generates a new timestamp
timestamp = mapped_column(DateTime(), server_default=func.now())
# some other server-side function not named here, such as a trigger,
# populates a value into this column during INSERT
special_identifier = mapped_column(String(50), server_default=FetchedValue())
# set eager defaults to True. This is usually optional, as if the
# backend supports RETURNING + insertmanyvalues, eager defaults
# will take place regardless on INSERT
__mapper_args__ = {"eager_defaults": True}
上記の例では、クライアント側から”timestamp”または”special_identifier”の値を明示的に指定しないINSERT文は、RETURNING句内に”timestamp”および”special_identifier”列を含むため、これらはすぐに使用可能になります。PostgreSQLデータベースでは、上記のテーブルに対するINSERTは以下のようになります。
INSERT INTO my_table DEFAULT VALUES RETURNING my_table.id, my_table.timestamp, my_table.special_identifier
Changed in version 2.0.0rc1: Mapper.eager_defaults
パラメータのデフォルトが新しい設定である "auto"
になりました。これは、配下のデータベースがRETURNINGと insertmanyvalues の両方をサポートしている場合、INSERT時にサーバが生成したデフォルト値を取得するために自動的にRETURNINGを使用します。
Note
Mapper.eager_defaults
の "auto"
値はINSERT文にのみ適用されます。UPDATE文は、たとえ使用可能であっても、 Mapper.eager_defaults
が True
に設定されていない限り、RETURNINGを使用しません。これは、UPDATEには同等の「insertmanyvalues」機能がないためです。そのため、UPDATE RETURNINGは、更新される各行に対して個別にUPDATE文を生成する必要があります。
Case 2: Table includes trigger-generated values which are not compatible with RETURNING¶
Mapper.eager_defaults
の "auto"
設定は、RETURNINGをサポートするバックエンドが、新しく生成されたデフォルト値を取得するために、通常INSERT文でRETURNINGを使用することを意味します。しかし、トリガを使用して生成されるサーバ生成値には制限があるため、RETURNINGは使用できません。
SQL Serverでは、トリガによって生成された値を取得するためにINSERT文でRETURNINGを使用することはできません。この文は失敗します。
SQLiteでは、RETURNINGとトリガを組み合わせて使用することには制限があります。たとえば、RETURNING句には使用可能なINSERTed値がありません。
他のバックエンドでは、トリガや他の種類のサーバ生成値と組み合わせたRETURNINGに制限がある場合があります。
サーバが生成したデフォルト値だけでなく、ORMが特定のテーブルに対してRETURNINGを使用しないようにするためにも、このような値に対するRETURNINGの使用を無効にするには、マップされた Table
に対して Table.implicit_returning
を False
として指定します。宣言型マッピングを使用すると、次のようになります:
class MyModel(Base):
__tablename__ = "my_table"
id: Mapped[int] = mapped_column(primary_key=True)
data: Mapped[str] = mapped_column(String(50))
# assume a database trigger populates a value into this column
# during INSERT
special_identifier = mapped_column(String(50), server_default=FetchedValue())
# disable all use of RETURNING for the table
__table_args__ = {"implicit_returning": False}
pyodbcドライバを使用するSQL Serverでは、上記のテーブルに対するINSERTはRETURNINGを使用せず、SQL Serverの scope_identity()
関数を使用して新しく生成されたプライマリ・キー値を取得します。
INSERT INTO my_table (data) VALUES (?); select scope_identity()
See also
INSERT behavior - 新しく生成されたプライマリキー値を取得するSQL Serverダイアレクトのメソッドの背景
Case 3: non primary key, RETURNING or equivalent is not supported or not needed¶
この場合は上記のケース1と同じですが、通常は Mapper.eager_defaults
を使用しないようにします。これは、RETURNINGサポートがない場合の現在の実装では、行ごとにSE LE CTを出力するため、パフォーマンスが低下するためです。したがって、このパラメータは以下のマッピングでは省略されています。:
class MyModel(Base):
__tablename__ = "my_table"
id = mapped_column(Integer, primary_key=True)
timestamp = mapped_column(DateTime(), server_default=func.now())
# assume a database trigger populates a value into this column
# during INSERT
special_identifier = mapped_column(String(50), server_default=FetchedValue())
上記のマッピングを持つレコードが、RETURNINGまたは”insertmanyvalues”サポートを含まないバックエンドに挿入された後、”timestamp”列と”special_identifier”列は空のままになり、フラッシュ後に最初にアクセスされたときに2番目のSELECT文を介してフェッチされます。たとえば、”expired”とマークされます。
Mapper.eager_defaults
に明示的に True
の値が指定されていて、バックエンドデータベースがRETURNINGまたは同等のものをサポートしていない場合、ORMは新しく生成された値を取得するためにINSERT文の直後にSELECT文を発行します。現在、ORMには、RETURNINGが使用できない場合に、新しく挿入された多くの行をバッチでSELECTする機能はありません。これは、フラッシュプロセスに必要のない追加のSELECT文を追加するため、通常は望ましくありません。上記のマッピングを使用し、(MariaDBではなく)MySQLに対して Mapper.eager_defaults
フラグをTrueに設定すると、フラッシュ時に次のようなSQLが生成されます。
INSERT INTO my_table () VALUES ()
-- when eager_defaults **is** used, but RETURNING is not supported
SELECT my_table.timestamp AS my_table_timestamp, my_table.special_identifier AS my_table_special_identifier
FROM my_table WHERE my_table.id = %s
SQLAlchemyの将来のリリースでは、単一のSELECT文の中で多数のローをバッチ処理するRETURNINGを使用して、Eager Defaultsの効率を改善する可能性があります。
Case 4: primary key, RETURNING or equivalent is supported¶
サーバが生成した値を持つプライマリ・キー・カラムは、INSERTの直後にフェッチする必要があります。ORMはプライマリ・キー値を持つローにしかアクセスできないため、プライマリ・キーがサーバによって生成された場合、ORMはINSERTの直後に新しい値を取得する手段を必要とします。
前述したように、整数の”オートインクリメント”列や、 Identity
でマークされた列、PostgreSQLのSERIALのような特殊な構成体の場合、これらの型はコアによって自動的に処理されます。データベースには、RETURNINGがサポートされていない”最後に挿入されたid”を取得する関数が含まれており、RETURNINGがサポートされている場合、SQLAlchemyはそれを使用します。
例えば、 Identity
とマークされた列を持つOracleを使用すると、RETURNINGが自動的に新しい主キー値をフェッチするために使用されます:
class MyOracleModel(Base):
__tablename__ = "my_table"
id: Mapped[int] = mapped_column(Identity(), primary_key=True)
data: Mapped[str] = mapped_column(String(50))
The INSERT for a model as above on Oracle looks like:
Oracle上での上記モデルのINSERTは次のようになります。
INSERT INTO my_table (data) VALUES (:data) RETURNING my_table.id INTO :ret_0
SQLAlchemyは”data”フィールドに対してINSERTを行いますが、RETURNING句には”id”のみを含めます。これにより、サーバ側で「id」が生成され、新しい値が即座に返されます。
サーバー側の関数またはトリガによって生成された整数以外の値の場合、および明示的なシーケンスやトリガを含むテーブル自体の外部の構成から生成された整数値の場合、サーバのデフォルト生成はテーブルのメタデータ内でマークされる必要があります。もう一度Oracleを例として使用すると、 Sequence
構成体を使用して明示的なシーケンスに名前を付けて、上記と同様のテーブルを説明できます:
class MyOracleModel(Base):
__tablename__ = "my_table"
id: Mapped[int] = mapped_column(Sequence("my_oracle_seq"), primary_key=True)
data: Mapped[str] = mapped_column(String(50))
Oracle上のこのバージョンのモデルのINSERTは次のようになります。:
INSERT INTO my_table (id, data) VALUES (my_oracle_seq.nextval, :data) RETURNING my_table.id INTO :ret_0
上記の場合、SQLAlchemyは新しいプライマリ・キーの生成に使用されるようにプライマリ・キー・カラムに対して my_sequence.nextval
をレンダリングし、RETURNINGを使用して新しい値をすぐに返します。
新しい値を生成するトリガやデータベース固有のデータ型を使用する場合など、データのソースが単純なSQL関数や Sequence
で表現されない場合、値を生成するデフォルトの存在は、列定義内で FetchedValue
を使用して示すことができます。以下は、SQL ServerのTIMESTAMP列を主キーとして使用するモデルです。SQL Serverでは、このデータ型は新しい値を自動的に生成します。したがって、これは、 Column.server_default
パラメータに FetchedValue
を指定することによって、テーブルメタデータ内で示されます。:
class MySQLServerModel(Base):
__tablename__ = "my_table"
timestamp: Mapped[datetime.datetime] = mapped_column(
TIMESTAMP(), server_default=FetchedValue(), primary_key=True
)
data: Mapped[str] = mapped_column(String(50))
SQL Serverでの上記のテーブルのINSERTは、次のようになります。:
INSERT INTO my_table (data) OUTPUT inserted.timestamp VALUES (?)
Case 5: primary key, RETURNING or equivalent is not supported¶
この領域では、デフォルトを生成する何らかの手段がサーバ上で実行されているが、データベースの通常のオートインクリメント・ルーチンの外部にあるMySQLなどのデータベースのローを生成します。この場合、SQLAlchemyがデフォルトを「事前実行」できることを確認する必要があります。つまり、明示的なSQL式である必要があります。
Note
このセクションでは、MySQLの日時値を含む複数のレシピについて説明します。これは、このバックエンドの日時データ型には、説明するのに役立つ特異な要件が追加されているためです。ただし、MySQLでは、通常の単一列の自動インクリメント整数値以外に、主キーとして使用される*任意の*自動生成データ型に対して、明示的な「事前実行された」デフォルトジェネレータが必要であることに注意してください。
MySQL with DateTime primary key¶
MySQLの DateTime
列の例では、”NOW()”SQL関数を使って、明示的にpre-executeでサポートされるデフォルトを追加しています:
class MyModel(Base):
__tablename__ = "my_table"
timestamp = mapped_column(DateTime(), default=func.now(), primary_key=True)
上記では、”NOW()”関数を選択して日時の値を列に渡します。上記で生成されるSQLは次のとおりです。:
SELECT now() AS anon_1
INSERT INTO my_table (timestamp) VALUES (%s)
('2018-08-09 13:08:46',)
MySQL with TIMESTAMP primary key¶
MySQLで TIMESTAMP
データ型を使用する場合、MySQLは通常、このデータ型にサーバ側のデフォルトを自動的に関連付けます。しかし、主キーとして使用する場合、Coreは関数を実行しない限り、新しく生成された値を取得できません。MySQLの TIMESTAMP
は実際にバイナリ値を格納しているので、列に永続化できるバイナリ値を取得できるように、”NOW()”の使用法に追加の”CAST”を追加する必要があります:
from sqlalchemy import cast, Binary
class MyModel(Base):
__tablename__ = "my_table"
timestamp = mapped_column(
TIMESTAMP(), default=cast(func.now(), Binary), primary_key=True
)
上記では、”NOW()”関数を選択することに加えて、戻り値がバイナリになるように、 cast()
と組み合わせて Binary
データ型を追加で使用しています。INSERT内で上記からレンダリングされたSQLは次のようになります。:
SELECT CAST(now() AS BINARY) AS anon_1
INSERT INTO my_table (timestamp) VALUES (%s)
(b'2018-08-09 13:08:46',)
See also
Notes on eagerly fetching client invoked SQL expressions used for INSERT or UPDATE¶
上記の例は、 Column.server_default
を使用して、DDL内にデフォルト生成関数を含むテーブルを作成する方法を示しています。
SQLAlchemyは、 Client-Invoked SQL Expressions で文書化されているように、DDL以外のサーバ側のデフォルトもサポートしています。これらの”クライアントが呼び出すSQL式”は、 Column.default
および Column.onupdate
パラメータを使用して設定されます。
これらのSQL式は現在、ORM内で真のサーバ側のデフォルトと同じ制限を受けます。 Mapper.eager_defaults
が "auto"
または True
に設定されている場合、 FetchedValue
ディレクティブが Column
に関連付けられていない限り、これらの式はDDLサーバのデフォルトではなく、SQLAlchemy自身によってアクティブにレンダリングされますが、RETURNINGでは積極的にフェッチされません。この制限は将来のSQLAlchemyリリースで対処される可能性があります。
FetchedValue
構文は Column.server_default
または Column.server_onupdate
に適用することができ、同時にSQL式が Column.default
および Column.onupdate
と共に使用されます。例えば、以下の例では、クライアントが呼び出すSQL式として、 Column.default
および Column.onupdate
に対して、 func.now()
構文が使用されています。 Mapper.eager_defaults
の動作に、利用可能な場合にRETURNINGを使用してこれらの値を取得することを含めるために、 Column.server_default
および Column.server_onupdate
を FetchedValue
と共に使用して、取得が確実に行われるようにします:
class MyModel(Base):
__tablename__ = "my_table"
id = mapped_column(Integer, primary_key=True)
created = mapped_column(
DateTime(), default=func.now(), server_default=FetchedValue()
)
updated = mapped_column(
DateTime(),
onupdate=func.now(),
server_default=FetchedValue(),
server_onupdate=FetchedValue(),
)
__mapper_args__ = {"eager_defaults": True}
上記と同様のマッピングでは、ORMによってINSERTとUPDATEのためにレンダリングされたSQLは、RETURNING句の中に created
と updated
を含みます。:
INSERT INTO my_table (created) VALUES (now()) RETURNING my_table.id, my_table.created, my_table.updated
UPDATE my_table SET updated=now() WHERE my_table.id = %(my_table_id)s RETURNING my_table.updated
Using INSERT, UPDATE and ON CONFLICT (i.e. upsert) to return ORM Objects¶
SQLAlchemy 2.0には、ORM対応のINSERT、UPDATE、upsert文のいくつかの種類を出力する拡張機能が含まれています。ドキュメントについては ORM-Enabled INSERT, UPDATE, and DELETE statements のドキュメントを参照してください。upsertについては ORM “upsert” Statements を参照してください。
Using PostgreSQL ON CONFLICT with RETURNING to return upserted ORM objects¶
このセクションは ORM “upsert” Statements に移動しました。
Partitioning Strategies (e.g. multiple database backends per Session)¶
Simple Vertical Partitioning¶
垂直分割は、 Session.binds
引数で Session
を設定することで、複数のデータベースにまたがって、異なるクラス、クラス階層、またはマップされたテーブルを配置します。この引数は、ORMマップクラス、マップされた階層内の任意のクラス(宣言的な基底クラスやミックスインなど)、 Table
オブジェクト、および Mapper
オブジェクトの任意の組み合わせをキーとして含む辞書を受け取ります。これらは通常 Engine
または、あまり一般的ではありませんが Connection
オブジェクトをターゲットとして参照します。辞書は、データベース接続の適切なソースを見つけるために、 Session
が特定の種類のマップされたクラスに代わってSQLを出力する必要があるときに参照されます:
engine1 = create_engine("postgresql+psycopg2://db1")
engine2 = create_engine("postgresql+psycopg2://db2")
Session = sessionmaker()
# bind User operations to engine 1, Account operations to engine 2
Session.configure(binds={User: engine1, Account: engine2})
session = Session()
上記では、いずれかのクラスに対するSQL操作は、そのクラスにリンクされた Engine
を使用します。この機能は読み取りと書き込みの両方の操作にわたって包括的です。つまり、(要求された項目のリストの最初のエンティティを調べることによって決定される) engine1
にマップされたエンティティに対する Query
は、 engine1
を使用してクエリを実行します。フラッシュ操作は、 User
型と Account
型のオブジェクトをフラッシュするときに、クラスごとに**両方**のエンジンを使用します。
より一般的なケースでは、異なるデータベース接続を対象とした操作を区別するために使用できるベースクラスまたはミックスインクラスが一般的に存在します。 Session.binds
引数は、任意のPythonクラスをキーとして受け入れることができ、特定のマップされたクラスの「__mro__」(Pythonメソッドの解決順序)にあることが判明した場合に使用されます。2つの宣言ベースが2つの異なるデータベース接続を表していると仮定します:
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Session
class BaseA(DeclarativeBase):
pass
class BaseB(DeclarativeBase):
pass
class User(BaseA): ...
class Address(BaseA): ...
class GameInfo(BaseB): ...
class GameStats(BaseB): ...
Session = sessionmaker()
# all User/Address operations will be on engine 1, all
# Game operations will be on engine 2
Session.configure(binds={BaseA: engine1, BaseB: engine2})
上の例では、 BaseA
と BaseB
から派生したクラスのSQL操作は、どのスーパークラスから派生したか(存在する場合)に基づいて、2つのエンジンのいずれかに送られます。複数の「束縛された」スーパークラスから派生したクラスの場合、ターゲットクラスの階層の最上位にあるスーパークラスが、どのエンジンを使うべきかを表すために選ばれます。
See also
Coordination of Transactions for a multiple-engine Session¶
複数のバウンドエンジンを使用する場合の注意点の1つは、あるバックエンドでコミットが成功した後に、別のバックエンドでコミット操作が失敗する可能性がある場合である。これは、リレーショナルデータベースでは「2フェーズトランザクション」を使用して解決される不整合の問題であり、これは、実際にトランザクションを完了する前に複数のデータベースがコミットに同意することを可能にする追加の「準備」ステップをコミットシーケンスに追加する。
DB API内でのサポートが制限されているため、SQLAlchemyはバックエンド間の2フェーズトランザクションのサポートが制限されています。最も一般的には、PostgreSQLバックエンドでうまく機能することが知られていますが、MySQLバックエンドではそれほど機能しません。ただし、バックエンドが2フェーズトランザクション機能をサポートしている場合、 Session
は、 sessionmaker
または Session
内で Session.use_twophase
フラグを設定することで、2フェーズトランザクション機能を十分に活用できます。例については、 Enabling Two-Phase Commit を参照してください。
Custom Vertical Partitioning¶
Session.get_bind()
メソッドをオーバーライドすることで、より包括的なルールベースのクラスレベルのパーティショニングを構築できます。以下に、以下のルールを提供するカスタム Session
を示します。
フラッシュ操作とバルク
更新
および削除
操作は、leader
という名前のエンジンに配信されます。MyOtherClass
をサブクラスとするオブジェクトに対する操作は、すべてother
エンジンで行われます。他のすべてのクラスの読み込み操作は、
follower1
またはfollower2
データベースのランダムな選択で行われます。
engines = {
"leader": create_engine("sqlite:///leader.db"),
"other": create_engine("sqlite:///other.db"),
"follower1": create_engine("sqlite:///follower1.db"),
"follower2": create_engine("sqlite:///follower2.db"),
}
from sqlalchemy.sql import Update, Delete
from sqlalchemy.orm import Session, sessionmaker
import random
class RoutingSession(Session):
def get_bind(self, mapper=None, clause=None):
if mapper and issubclass(mapper.class_, MyOtherClass):
return engines["other"]
elif self._flushing or isinstance(clause, (Update, Delete)):
# NOTE: this is for example, however in practice reader/writer
# splits are likely more straightforward by using two distinct
# Sessions at the top of a "reader" or "writer" operation.
# See note below
return engines["leader"]
else:
return engines[random.choice(["follower1", "follower2"])]
上の Session
クラスは、 sessionmaker
への class_
引数を使ってプラグインされています:
Session = sessionmaker(class_=RoutingSession)
このアプローチは、 __abstract__ で説明されている宣言的な __abstract__
キーワードを使用するなどのアプローチを使用して、複数の MetaData
オブジェクトと組み合わせることができます。
Note
上記の例では、特定のSQL文がデータを書き込むかどうかに基づいて、いわゆる”リーダー”または”フォロワー”データベースにルーティングすることを示していますが、これは、同じ操作内での読み取りと書き込みの間のトランザクション動作が調整されないことになるため、実用的なアプローチではない可能性があります。実際には、進行中の操作/トランザクション全体に基づいて、 Session
を”リーダー”または”ライター”セッションとして事前に構築するのが最善です。そうすれば、データを書き込む操作も、同じトランザクションスコープ内で読み取りクエリを発行します。 Setting Isolation For A Sessionmaker / Engine Wide の例を参照して、オートコミット接続を使用した”読み取り専用”操作用に1つの sessionmaker
を設定し、DML/COMMITを含む”書き込み”操作用に別の sessionmaker
を設定するレシピを確認してください。
See also
Django-style Database Routers in SQLAlchemy - Session.get_bind()
のより包括的な例に関するブログ記事
Horizontal Partitioning¶
水平分割は、1つのテーブル(またはテーブルのセット)の行を複数のデータベースに分割します。SQLAlchemy Session
にはこの概念のサポートが含まれていますが、完全に使用するには Session
と Query
のサブクラスを使用する必要があります。これらのサブクラスの基本バージョンは Horizontal Sharding ORM拡張で利用できます。使用例は: Horizontal Sharding にあります。
Bulk Operations¶
Legacy Feature
SQLAlchemy 2.0は、 Session
の”バルクインサート”と”バルクアップデート”機能を2.0スタイルの Session.execute()
メソッドに統合し、 Insert
と Update
構文を直接使用します。ドキュメントについては ORM-Enabled INSERT, UPDATE, and DELETE statements のドキュメントを参照してください。これには、古いメソッドから新しいメソッドへの移行を示す Legacy Session Bulk INSERT Methods が含まれます。