其他持久化技术¶
将 SQL Insert/Update 表达式嵌入到 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 的后端(包括 Oracle Database、SQL Server、MariaDB 10.5、SQLite 3.35),也可以将 SQL 表达式分配给主键列。这允许对 SQL 表达式进行评估,并允许 ORM 将修改 INSERT 上的主键值的任何服务器端触发器作为对象主键的一部分成功检索:
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
1.3 版本中的新功能: 现在可以在 ORM 刷新期间将 SQL 表达式传递给主键列;如果数据库支持 RETURNING,或者正在使用 pysqlite,则 ORM 将能够检索服务器生成的值作为主键属性的值。
在 Session 中使用 SQL 表达式¶
SQL 表达式和字符串可以通过
Session
在其事务上下文中。
这最容易使用
Session.execute()
方法,该方法返回一个
CursorResult
与
Engine
或
连接
:
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))
当前 Connection
持有的
Session
可使用
Session.connection()
方法:
connection = session.connection()
上面的示例处理绑定到单个 Engine
或
连接
。要使用
绑定到多个
engines 的 API API 的 API 和任何 API 的
Session.execute()
和
Session.connection()
接受 bind 参数的字典
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)
在 1.4 版本发生变更: mapper
和 clause
参数为
Session.execute()
现在作为作为 Session.execute.bind_arguments
参数发送的字典的一部分传递。前面的参数仍然被接受,但这种用法已被弃用。
在具有默认值的列上强制 NULL¶
ORM 将从未在对象上设置的任何属性视为 “default” 情况;该属性将在 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'
上述作会将服务器默认值 “default”
而不是 SQL NULL 保留在数据
列中,即使 None
获得通过;这是许多应用程序
hold 作为假设。
那么,如果我们想将 NULL 实际放入此列,即使
column 有默认值吗? 有两种方法。 一是
在每个实例级别上,我们使用
零
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,
我们可以使用 Core 级别的修饰符为 ORM 配置它
TypeEngine.evaluates_none()
中,它表示 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
获取服务器生成的默认值¶
如服务器调用的 DDL-Explicit 默认表达式和标记隐式生成的值、时间戳和触发列部分所述,核心支持数据库列的概念,数据库本身在 INSERT 上生成值,在不太常见的情况下,在 UPDATE 语句上生成值。ORM 功能支持此类列,能够在 flush 时获取这些新生成的值。对于服务器生成的主键列,此行为是必需的,因为 ORM 必须在对象持久化后知道主键。
在绝大多数情况下,具有其值
由数据库自动生成的是简单的整数列,它们是
由数据库实现为所谓的 “autoincrement” 列,或者
从与列关联的序列中。 中的每个数据库方言
SQLAlchemy Core 支持一种检索这些主键值的方法,该方法
通常是 Python DBAPI 的原生版本,通常此过程是自动的。
有关此内容的更多文档,请访问
Column.autoincrement 的 Column.autoincrement
中。
对于不是主键列或不是简单自动递增整数列的服务器生成列,ORM 要求这些列使用适当的 server_default
指令进行标记,以允许 ORM 检索此值。但是,并非所有后端都支持所有方法,因此必须注意使用适当的方法。需要回答的两个问题是,1.此列是否是主键的一部分,以及 2.数据库是否支持 RETURNING 或等效名称,例如 “OUTPUT inserted”;这些是 SQL 短语,它们在调用 INSERT 或 UPDATE 语句的同时返回服务器生成的值。PostgreSQL、Oracle Database、MariaDB 10.5、SQLite 3.35 和 SQL Server 目前支持 RETURNING。
情况 1:非主键,支持 RETURNING 或等效¶
在这种情况下,应将列标记为 FetchedValue
或使用显式Column.server_default
。 ORM 将
在执行
INSERT 语句,假设
Mapper.eager_defaults
参数设置为 True
,或者如果保留其默认设置 “auto”
,则对于同时支持 RETURNING 和 insertmanyvalues 的方言:
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
在 2.0.0rc1 版本发生变更: Mapper.eager_defaults
参数现在默认为新设置 “auto”
,如果后备数据库同时支持 RETURNING 和 insertmanyvalues,则将自动使用 RETURNING 在 INSERT 上获取服务器生成的默认值。
注意
Mapper.eager_defaults
的 “auto”
值仅适用于 INSERT 语句。UPDATE 语句不会使用 RETURNING,即使可用,除非 Mapper.eager_defaults
设置为
没错
。这是因为 UPDATE 没有等效的“insertmanyvalues”功能,因此 UPDATE RETURNING 将要求为要 UPDATEd 的每一行单独发出 UPDATE 语句。
情况 2:表包含与 RETURNING 不兼容的触发器生成的值¶
Mapper.eager_defaults
的 “auto”
设置意味着支持 RETURNING 的后端通常会使用 RETURNING 和 INSERT 语句来检索新生成的默认值。但是,使用触发器生成的服务器生成的值存在限制,因此不能使用 RETURNING:
SQL Server 不允许在 INSERT 语句中使用 RETURNING 来检索触发器生成的值;该语句将失败。
SQLite 在将 RETURNING 与触发器结合使用方面存在限制,因此 RETURNING 子句将没有可用的 INSERTed 值
其他后端可能对 RETURNING 与触发器或其他类型的服务器生成的值结合使用有限制。
要禁用对此类值的 RETURNING 使用,包括不仅仅是
server 生成的默认值,但也要确保 ORM 永远不会
将 RETURNING 与特定表一起使用,请指定
Table.implicit_returning
作为映射
的 Table
的 False。使用 Declarative 映射,如下所示:
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()
另请参阅
INSERT 行为 - SQL Server 方言获取新生成的主键值的方法的背景
情况 3:非主键,不支持或不需要 RETURNING 或等效¶
这种情况与上面的情况 1 相同,只是我们通常不想使用 Mapper.eager_defaults
,因为在没有 RETURNING 支持的情况下,它的当前实现是每行发出一个 SELECT,这并不高效。因此,在下面的映射中省略了该参数:
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” 支持的后端对具有上述映射的记录进行 INSERT 后,“timestamp” 和 “special_identifier” 列将保持为空,并在刷新后首次访问时通过第二个 SELECT 语句获取,例如,它们被标记为“过期”。
如果 Mapper.eager_defaults
显式提供了 True
值,并且后端数据库不支持 RETURNING 或等效值,则 ORM 将在 INSERT 语句之后立即发出 SELECT 语句,以便获取新生成的值;如果 RETURNING 不可用,则 ORM 当前无法批量 SELECT 许多新插入的行。这通常是不可取的,因为它会向 flush 进程添加可能不需要的其他 SELECT 语句。对 MySQL(不是 MariaDB)使用上述映射并将 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 批量处理多行。
情况 4:支持主键、RETURNING 或等效项¶
必须在 INSERT 时立即获取具有服务器生成值的主键列;ORM 只能访问具有主键值的行,因此如果主键是由服务器生成的,则 ORM 需要一种方法在 INSERT 后立即检索该新值。
如上所述,对于整数 “autoincrement” 列,以及标有 Identity
和特殊结构(如 PostgreSQL SERIAL)的列,这些类型由 Core 自动处理;数据库包含用于获取“最后插入的 id”的函数,其中不支持 RETURNING,而支持 RETURNING 时,SQLAlchemy 将使用该函数。
例如,使用标记为 Identity
的列的 Oracle Database 时,RETURNING 会自动用于获取新的主键值:
class MyOracleModel(Base):
__tablename__ = "my_table"
id: Mapped[int] = mapped_column(Identity(), primary_key=True)
data: Mapped[str] = mapped_column(String(50))
Oracle Database 上上述模型的 INSERT 如下所示:
INSERT INTO my_table (data) VALUES (:data) RETURNING my_table.id INTO :ret_0
SQLAlchemy 为 “data” 字段呈现 INSERT,但仅在 RETURNING 子句中包含 “id”,因此将进行 “id” 的服务器端生成,并且将立即返回新值。
对于服务器端函数或触发器生成的非整数值,以及
对于来自表本身外部构造的整数值,
包括显式序列和触发器,服务器默认生成必须
在表元数据中标记。再次以 Oracle 数据库为例,我们可以
说明与上面类似的表格,使用
序列
结构:
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 Database 上的 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 函数表示的,或者
序列
,例如,当使用生成新值的触发器或特定于数据库的数据类型时,可以通过在列定义中使用 FetchedValue
来指示存在值生成默认值。下面是一个使用 SQL Server TIMESTAMP 列作为主键的模型;在 SQL Server 上,此数据类型会自动生成新值,因此在表元数据中通过指示
Column.server_default
参数:
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 (?)
情况 5:不支持主键、RETURNING 或等效项¶
在这个区域中,我们正在为数据库(如 MySQL)生成行,其中一些生成默认值的方法发生在服务器上,但超出了数据库通常的自动增量例程。在这种情况下,我们必须确保 SQLAlchemy 可以 “pre-execute ”default,这意味着它必须是一个显式的 SQL 表达式。
注意
本节将说明涉及 MySQL 日期时间值的多个配方,因为此后端上的 datetime 数据类型具有其他特殊要求,这些要求可用于说明。但是请记住,MySQL 需要一个明确的“预执行”默认生成器,用于用作主键的任何自动生成的数据类型,而不是通常的单列自动递增整数值。
带有 DateTime 主键的 MySQL¶
以 MySQL 的 DateTime
列为例,我们使用 “NOW()” SQL 函数添加一个显式的 pre-execute-supported 默认值:
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',)
带有 TIMESTAMP 主键的 MySQL¶
当将 TIMESTAMP
数据类型与 MySQL 一起使用时,MySQL 通常会自动将服务器端默认值与此数据类型相关联。但是,当我们使用 1 作为主键时,除非我们自己执行该函数,否则 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()” 函数外,我们还将 Binary
数据类型与 cast()
结合使用,以便返回值是二进制的。在 INSERT 中从上面呈现的 SQL 如下所示:
SELECT CAST(now() AS BINARY) AS anon_1
INSERT INTO my_table (timestamp) VALUES (%s)
(b'2018-08-09 13:08:46',)
另请参阅
关于急切获取用于 INSERT 或 UPDATE 的客户端调用的 SQL 表达式的注意事项¶
前面的示例说明了 Column.server_default
的用法
创建在其
DDL 的 DDL 中。
SQLAlchemy 还支持非 DDL 服务器端默认值,如
客户端调用的 SQL 表达式;这些“客户端调用的 SQL 表达式”是使用 Column.default
和
Column.onupdate
参数。
这些 SQL 表达式当前在 ORM 中受到与真正的服务器端默认值相同的限制;当 Mapper.eager_defaults
设置为 “auto”
或
如果
FetchedValue
指令与
Column
,即使这些表达式不是 DDL 服务器默认值,并且由 SQLAlchemy 本身主动呈现。此限制可能会在将来的 SQLAlchemy 版本中得到解决。
FetchedValue
构造可以应用于
Column.server_default
或
Column.server_onupdate
SQL 表达式与 Column.default
和
Column.onupdate
,例如在下面的示例中,
func.now()
结构用作 Column.default
的客户端调用的 SQL 表达式,并且
Column.onupdate
中,为了实现
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
使用 INSERT、UPDATE 和 ON CONFLICT(即 upsert)返回 ORM 对象¶
SQLAlchemy 2.0 包括用于发出多种支持 ORM 的 INSERT、UPDATE 和 upsert 语句的增强功能。有关文档,请参阅 ORM-Enabled INSERT、UPDATE 和 DELETE 语句中的文档。 有关 upsert,请参阅
ORM “upsert” 语句。
使用 PostgreSQL ON CONFLICT 和 RETURNING 返回更新插入的 ORM 对象¶
本节已移至 ORM “upsert” Statements。
分区策略 (例如,每个 Session 多个数据库后端)¶
简单的垂直分区¶
垂直分区放置不同的类、类层次结构、
或映射表,通过配置
带有 Session.binds
参数的 Session
。此参数接收一个字典,其中包含 ORM 映射类、映射层次结构中的任意类(例如声明性基类或混合)、Table
对象和 Mapper
对象的任意组合作为键,然后通常引用
Engine
或更常见的 Connection
对象作为目标。每当 Session
需要代表特定类型的 Map 类发出 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
类型的对象。
在更常见的情况下,通常有 base 类或 mixin 类可用于区分发往不同数据库连接的作。Session.binds
参数可以容纳任何
arbitrary Python 类作为键,如果发现它位于
__mro__
(Python 方法解析顺序)对于特定映射的类。假设两个声明性基类表示两个不同的数据库连接:
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作路由到两个引擎之一。如果一个类派生自多个 “bound” 超类,则将选择目标类层次结构中最高的超类来表示应使用哪个引擎。
另请参阅
多引擎 Session 的事务协调¶
使用多个绑定引擎时需要注意的是,在一个后端提交成功后,另一个后端的提交作可能会失败。这是一个不一致问题,在关系数据库中,使用“两阶段事务”来解决,该事务在提交序列中增加了一个额外的“准备”步骤,允许多个数据库在实际完成事务之前同意提交。
由于 DBAPI 中的支持有限,SQLAlchemy 对跨后端的两阶段事务的支持有限。最典型的是,它与 PostgreSQL 后端配合良好,在较小程度上与 MySQL 后端配合良好。但是,Session
完全能够利用这两者
phase 事务功能,通过在后端支持的情况下设置
Session.use_twophase
SessionMaker
中的 flag 或
会话
。有关示例,请参阅 启用两阶段提交 。
自定义垂直分区¶
可以通过覆盖 Session.get_bind()
方法来构建更全面的基于规则的类级分区。下面我们演示了一个自定义 Session
,它提供以下规则:
刷新作以及批量 “update” 和 “delete”作将传送到名为leader
的引擎。
对子类MyOtherClass
的对象的作都发生在另一个
引擎上。
所有其他类的读取作发生在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
类是使用 class_
插入的
对 SessionMaker
的参数:
Session = sessionmaker(class_=RoutingSession)
此方法可以与多个 MetaData
对象结合使用,使用的方法类似于使用声明性__abstract__
关键字,在 __abstract__ 中进行了描述。
注意
虽然上面的示例说明了根据语句是否期望写入数据,将特定 SQL 语句路由到所谓的 “leader” 或 “follower” 数据库,但这可能不是一种实用的方法,因为它会导致同一作中读取和写入之间的事务行为不协调。在实践中,最好将 Session
预先构建为 “reader” 或 “writer”
session,基于正在进行的整体作 / 事务。
这样,将写入数据的作也将发出其读取查询
在同一事务范围内。 请参阅以下示例
为 Sessionmaker / Engine Wide 设置隔离,为使用自动提交连接设置一个 sessionmaker 的 “只读”作,为包括 DML / COMMIT 的 “write”作设置另一个 sessionmaker
。
另请参阅
SQLAlchemy 中的 Django 风格的数据库路由器 - 关于更全面的 Session.get_bind()
示例的博客文章
水平分区¶
水平分区将单个表(或一组表)的行跨多个数据库进行分区。SQLAlchemy 会话
包含对此概念的支持,但是要完全使用它,需要
使用 Session
和 Query
子类。这些子类的基本版本在 水平分片 中可用
ORM 扩展。 一个使用示例在:水平分片。
批量作¶
旧版功能
SQLAlchemy 2.0 已将 Session
的“批量插入”和“批量更新”功能集成到 2.0 风格的 Session.execute()
中
方法,直接使用 Insert
和 Update
构建。有关文档,请参阅 ORM-Enabled INSERT、UPDATE 和 DELETE 语句中的文档,包括说明从旧方法到新方法的 Legacy Session Bulk INSERT 方法。