版本:1.1.0b2 |发布日期:2016年7月1日

SQLAlchemy 1.1文档

额外的持久性技术

将SQL插入/更新表达式嵌入到

此功能允许将数据库列的值设置为SQL表达式而不是文字值。这对原子更新,调用存储过程等特别有用。你所做的就是给表达式分配一个属性:

class SomeClass(object):
    pass
mapper(SomeClass, some_table)

someobject = session.query(SomeClass).get(5)

# set 'value' attribute to a SQL expression adding one
someobject.value = some_table.c.value + 1

# issues "UPDATE some_table SET value=value+1"
session.commit()

这种技术适用于INSERT和UPDATE语句。刷新/提交操作之后,上面的someobject中的value属性已过期,以便下次访问时,新生成的值将从数据库加载。

在会话中使用SQL表达式

SQL表达式和字符串可以通过事务上下文中的Session执行。This is most easily accomplished using the execute() method, which returns a ResultProxy in the same manner as an Engine or Connection:

Session = sessionmaker(bind=engine)
session = Session()

# execute a string statement
result = session.execute("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保存的当前Connection

connection = session.connection()

上面的例子处理了绑定到单个EngineConnectionSessionTo execute statements using a Session which is bound either to multiple engines, or none at all (i.e. relies upon bound metadata), both execute() and connection() accept a mapper keyword argument, which is passed a mapped class or Mapper instance, which is used to locate the proper context for the desired engine:

Session = sessionmaker()
session = Session()

# need to specify mapper or class when executing
result = session.execute("select * from table where id=:id", {'id':7}, mapper=MyMappedClass)

result = session.execute(select([mytable], mytable.c.id==7), mapper=MyMappedClass)

connection = session.connection(MyMappedClass)

用默认的强制NULL

ORM将从未在对象上设置的任何属性视为“默认”情况;该属性将从INSERT语句中省略:

class MyObject(Base):
    __tablename__ = 'my_table'
    id = Column(Integer, primary_key=True)
    data = 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

Omitting a column from the INSERT means that the column will have the NULL value set, unless the column has a default set up, in which case the default value will be persisted. 从纯SQL透视图和服务器端默认值,以及SQLAlchemy的插入行为(同时具有客户端和服务器端默认值)的行为都是如此:

class MyObject(Base):
    __tablename__ = 'my_table'
    id = Column(Integer, primary_key=True)
    data = 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 = Column(Integer, primary_key=True)
    data = 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列的"default"的服务器默认值中,而不是SQL NULL。这是ORM长期以来的一个行为,许多应用程序都将其作为一个假设。

那么,如果我们想实际上把NULL放到这个列中,即使这个列有一个默认值呢?有两种方法。一个是在每个实例级别上,我们使用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

The null SQL construct always translates into the SQL NULL value being directly present in the target INSERT statement.

If we’d like to be able to use the Python value None and have this also be persisted as NULL despite the presence of column defaults, we can configure this for the ORM using a Core-level modifier TypeEngine.evaluates_none(), which indicates a type where the ORM should treat the value None the same as any other value and pass it through, rather than omitting it as a “missing” value:

class MyObject(Base):
    __tablename__ = 'my_table'
    id = Column(Integer, primary_key=True)
    data = 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

评估无

The TypeEngine.evaluates_none() modifier is primarily intended to signal a type where the Python value “None” is significant, the primary example being a JSON type which may want to persist the JSON null value rather than SQL NULL. 我们稍微在这里重新调整它,以便向ORM发出信号,即使没有为它分配特殊的类型级别的行为,我们也希望None被传递到类型中。

版本1.1中的新增功能:添加了TypeEngine.evaluates_none()方法,以表示“无”值应被视为重要。

分区策略

简单垂直分区

垂直分区在多个数据库中放置不同种类的对象或不同的表格:

engine1 = create_engine('postgresql://db1')
engine2 = create_engine('postgresql://db2')

Session = sessionmaker(twophase=True)

# bind User operations to engine 1, Account operations to engine 2
Session.configure(binds={User:engine1, Account:engine2})

session = Session()

以上,针对任何一个类的操作都会使用链接到该类的Engine在刷新操作之后,类似的规则发生,以确保每个类被写入正确的数据库。

如果底层后端支持,则可以通过两阶段提交来协调多个数据库之间的事务。例如,请参阅Enabling Two-Phase Commit

自定义垂直分区

通过覆盖Session.get_bind()方法可以构建更全面的基于规则的类级别分区。下面我们举例说明一个自定义的Session,它提供了以下规则:

  1. 刷新操作交付给名为master的引擎。
  2. MyOtherClass子类的对象的操作都发生在other引擎上。
  3. Read operations for all other classes occur on a random choice of the slave1 or slave2 database.
engines = {
    'master':create_engine("sqlite:///master.db"),
    'other':create_engine("sqlite:///other.db"),
    'slave1':create_engine("sqlite:///slave1.db"),
    'slave2':create_engine("sqlite:///slave2.db"),
}

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:
            return engines['master']
        else:
            return engines[
                random.choice(['slave1','slave2'])
            ]

上面的Session类使用class_参数插入到sessionmaker中:

Session = sessionmaker(class_=RoutingSession)

This approach can be combined with multiple MetaData objects, using an approach such as that of using the declarative __abstract__ keyword, described at __abstract__.

水平分区

水平分区跨多个数据库分区单个表(或一组表)的行。

请参阅“分片”示例:Horizontal Sharding

批量操作

注意

批量操作模式是在Session对象上提供的一系列新操作,用于调用INSERT和UPDATE语句,大大降低了Python开销,代价是功能少,自动化和错误检查。从SQLAlchemy 1.0开始,这些功能应该被视为“测试版”,另外还适用于高级用户。

版本1.0.0中的新功能

Session的批量操作包括Session.bulk_save_objects()Session.bulk_insert_mappings()Session.bulk_update_mappings()这些方法的目的是直接暴露工作单位系统的内部元素,从而可以单独利用发出给予字典或对象状态的INSERT和UPDATE语句的设施,绕过正常的工作状态机制,关系和属性管理。这种方法的好处是严格减少了Python开销:

  • flush()过程包括调查所有对象,状态,级联状态,通过relationship()关联的所有对象的状态,以及所有要执行的操作的拓扑排序完全被绕过。这减少了大量的Python开销。
  • 即使操作完成,给定的对象与目标Session也没有定义的关系,这意味着在附加或管理其身份映射或会话方面没有任何开销。
  • Session.bulk_insert_mappings()Session.bulk_update_mappings()方法接受普通Python字典列表,而不是对象;这进一步减少了与实例化映射对象和将状态分配给它们相关的大量开销,这通常还要以每个属性为基础对历史进行昂贵的跟踪。
  • 在INSERT之后获取主键的过程也被默认禁用。当正确执行时,INSERT语句现在可以更容易地被工作单元批处理成executemany()块,它比单独的语句调用执行得好得多。
  • UPDATE statements can similarly be tailored such that all attributes are subject to the SET clase unconditionally, again making it much more likely that executemany() blocks can be used.

应该使用Performance示例套件研究批量例程的性能行为。这是一系列示例脚本,它们演示了各种场景(包括批量插入和更新场景)中的Python调用计数。

也可以看看

Performance - includes detailed examples of bulk operations contrasted against traditional Core and ORM methods, including performance metrics.

用法¶ T0>

每个方法都在Session对象事务的上下文中工作,就像任何其他方法一样:

s = Session()
objects = [
    User(name="u1"),
    User(name="u2"),
    User(name="u3")
]
s.bulk_save_objects(objects)

对于Session.bulk_insert_mappings()Session.bulk_update_mappings(),传递字典:

s.bulk_insert_mappings(User,
  [dict(name="u1"), dict(name="u2"), dict(name="u3")]
)

比较核心插入/更新结构

批量方法提供的性能在特定情况下可以接近在“executemany”上下文中使用核心InsertUpdate结构的性能(有关“executemany” ,请参阅Core教程中的Executing Multiple Statements)。为了实现这一点,应该禁用Session.bulk_insert_mappings.return_defaults标志,以便可以将行组合在一起。应仔细研究Performance中的示例套件,以便熟悉可以实现批量性能的快速程度。

ORM兼容性

批量插入/更新方法与传统的ORM使用相比失去了大量的功能。以下是使用这些方法时不可用的功能列表:

可用的功能包括:

  • 映射对象的INSERT和UPDATE
  • 版本标识符支持
  • 多表映射(如连接继承) - 但是,要插入多个表的对象要么提前填充主键标识符,否则Session.bulk_save_objects.return_defaults标志必须被使用,这将大大降低性能的好处