SqlAlchemy 2.0 中文文档(三十三)(3)https://developer.aliyun.com/article/1562716
动态关系作为字典
演示了如何在“动态”关系之上放置类似于字典的外观,以便字典操作(假设简单的字符串键)可以在大型集合上进行操作,而无需一次加载整个集合。
文件清单:
- dynamic_dict.py
通用关联
演示了将多种类型的父类与特定子对象关联的各种方法。
所有示例都使用声明性扩展以及声明性 mixin。每个示例最后都呈现相同的用例 - 两个类,Customer
和Supplier
,都是HasAddresses
mixin 的子类,该 mixin 确保父类提供了一个包含Address
对象的addresses
集合。
discriminator_on_association.py 和 generic_fk.py 脚本是 2007 年博客文章使用 SQLAlchemy 进行多态关联中提出的配方的现代化版本。
文件清单:
- table_per_association.py - 通过为每个父类单独生成关联表格来提供通用关联的 mixin 示例。关联对象本身存储在所有父类之间共享的单个表中。
- table_per_related.py - 演示了一种通用关联,它在各个表中持久化关联对象,每个表都生成来代表特定父类持久化这些对象。
- discriminator_on_association.py - 演示了一种提供通用关联的 mixin,该关联使用单个目标表和单个关联表,所有父表都引用它。关联表包含一个“区分符”列,用于确定哪种类型的父对象与关联表中的每个特定行关联。
- generic_fk.py - 演示了所谓的“通用外键”,类似于流行框架(如 Django,ROR 等)的方式。这种方法绕过了标准的参照完整性实践,因为“外键”列实际上并不限制引用任何特定表;相反,应用程序逻辑用于确定引用的是哪个表。
材料化路径
演示了使用 SQLAlchemy ORM 实现“材料化路径”模式的方法。
文件清单:
- materialized_paths.py - 演示了“材料化路径”模式。
嵌套集
演示了使用 SQLAlchemy ORM 实现“嵌套集”模式的一种基本方法。
文件清单:
- nested_sets.py - Celko 的“嵌套集”树结构。
性能
用于各种 SQLAlchemy 用例的性能分析套件。
每个套件都专注于特定用例,具有特定的性能概况和相关含义:
- 批量插入
- 单独插入,有或没有事务
- 获取大量行
- 运行大量短查询
所有套件都包括各种使用模式,说明了 Core 和 ORM 的使用,并且通常按性能从最差到最佳的顺序排序,根据 SQLAlchemy 提供的功能量的多少,从最大到最小排列(这两件事通常完美对应)。
一个命令行工具在包级别呈现,允许运行各个套件:
$ python -m examples.performance --help usage: python -m examples.performance [-h] [--test TEST] [--dburl DBURL] [--num NUM] [--profile] [--dump] [--echo] {bulk_inserts,large_resultsets,single_inserts} positional arguments: {bulk_inserts,large_resultsets,single_inserts} suite to run optional arguments: -h, --help show this help message and exit --test TEST run specific test name --dburl DBURL database URL, default sqlite:///profile.db --num NUM Number of iterations/items/etc for tests; default is module-specific --profile run profiling and dump call counts --dump dump full call profile (implies --profile) --echo Echo SQL output
一个示例运行如下:
$ python -m examples.performance bulk_inserts
或者带有选项:
$ python -m examples.performance bulk_inserts \ --dburl mysql+mysqldb://scott:tiger@localhost/test \ --profile --num 1000
另请参阅
我如何分析一个由 SQLAlchemy 驱动的应用程序?
文件清单
文件清单:
- bulk_updates.py - 这一系列测试将说明不同的方式来批量更新大量行(正在施工!目前只有一个测试)
- large_resultsets.py - 在这系列测试中,我们研究加载大量非常小而简单的行所需的时间。
- bulk_inserts.py - 这一系列的测试展示了批量插入大量行的不同方法。
- short_selects.py - 这一系列的测试展示了通过主键选择单个记录的不同方法。
- single_inserts.py - 在这一系列的测试中,我们看到了一种在不同事务中插入行并且之后返回到实质上“关闭”状态的方法。这类似于启动数据库连接的 API 调用,插入行,提交并关闭的过程。
- main.py - 允许 examples/performance 包作为脚本运行。
使用时间运行所有测试
这是运行的默认形式:
$ python -m examples.performance single_inserts Tests to run: test_orm_commit, test_bulk_save, test_bulk_insert_dictionaries, test_core, test_core_query_caching, test_dbapi_raw_w_connect, test_dbapi_raw_w_pool test_orm_commit : Individual INSERT/COMMIT pairs via the ORM (10000 iterations); total time 13.690218 sec test_bulk_save : Individual INSERT/COMMIT pairs using the "bulk" API (10000 iterations); total time 11.290371 sec test_bulk_insert_dictionaries : Individual INSERT/COMMIT pairs using the "bulk" API with dictionaries (10000 iterations); total time 10.814626 sec test_core : Individual INSERT/COMMIT pairs using Core. (10000 iterations); total time 9.665620 sec test_core_query_caching : Individual INSERT/COMMIT pairs using Core with query caching (10000 iterations); total time 9.209010 sec test_dbapi_raw_w_connect : Individual INSERT/COMMIT pairs w/ DBAPI + connection each time (10000 iterations); total time 9.551103 sec test_dbapi_raw_w_pool : Individual INSERT/COMMIT pairs w/ DBAPI + connection pool (10000 iterations); total time 8.001813 sec
为个别测试输出简要概要
可以为所有测试或更常见的是个别测试转储 Python 分析输出:
$ python -m examples.performance single_inserts --test test_core --num 1000 --dump Tests to run: test_core test_core : Individual INSERT/COMMIT pairs using Core. (1000 iterations); total fn calls 186109 186109 function calls (186102 primitive calls) in 1.089 seconds Ordered by: internal time, call count ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.634 0.001 0.634 0.001 {method 'commit' of 'sqlite3.Connection' objects} 1000 0.154 0.000 0.154 0.000 {method 'execute' of 'sqlite3.Cursor' objects} 1000 0.021 0.000 0.074 0.000 /Users/classic/dev/sqlalchemy/lib/sqlalchemy/sql/compiler.py:1950(_get_colparams) 1000 0.015 0.000 0.034 0.000 /Users/classic/dev/sqlalchemy/lib/sqlalchemy/engine/default.py:503(_init_compiled) 1 0.012 0.012 1.091 1.091 examples/performance/single_inserts.py:79(test_core) ...
编写你自己的测试套件
分析套件系统是可扩展的,并且可以应用于你自己的一组测试。这是一种在决定某些性能关键例程的正确方法时使用的有价值技术。例如,如果我们想要分析几种加载方式之间的差异,我们可以创建一个名为 test_loads.py
的文件,其内容如下:
from examples.performance import Profiler from sqlalchemy import Integer, Column, create_engine, ForeignKey from sqlalchemy.orm import relationship, joinedload, subqueryload, Session from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() engine = None session = None class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) children = relationship("Child") class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parent_id = Column(Integer, ForeignKey('parent.id')) # Init with name of file, default number of items Profiler.init("test_loads", 1000) @Profiler.setup_once def setup_once(dburl, echo, num): "setup once. create an engine, insert fixture data" global engine engine = create_engine(dburl, echo=echo) Base.metadata.drop_all(engine) Base.metadata.create_all(engine) sess = Session(engine) sess.add_all([ Parent(children=[Child() for j in range(100)]) for i in range(num) ]) sess.commit() @Profiler.setup def setup(dburl, echo, num): "setup per test. create a new Session." global session session = Session(engine) # pre-connect so this part isn't profiled (if we choose) session.connection() @Profiler.profile def test_lazyload(n): "load everything, no eager loading." for parent in session.query(Parent): parent.children @Profiler.profile def test_joinedload(n): "load everything, joined eager loading." for parent in session.query(Parent).options(joinedload("children")): parent.children @Profiler.profile def test_subqueryload(n): "load everything, subquery eager loading." for parent in session.query(Parent).options(subqueryload("children")): parent.children if __name__ == '__main__': Profiler.main()
我们可以直接运行我们的新脚本:
$ python test_loads.py --dburl postgresql+psycopg2://scott:tiger@localhost/test Running setup once... Tests to run: test_lazyload, test_joinedload, test_subqueryload test_lazyload : load everything, no eager loading. (1000 iterations); total time 11.971159 sec test_joinedload : load everything, joined eager loading. (1000 iterations); total time 2.754592 sec test_subqueryload : load everything, subquery eager loading. (1000 iterations); total time 2.977696 sec
文件列表
文件列表:
- bulk_updates.py - 这一系列的测试将展示不同方法批量更新大量行(正在构建!目前只有一个测试)
- large_resultsets.py - 在这一系列的测试中,我们关注的是加载大量非常小而简单的行的时间。
- bulk_inserts.py - 这一系列的测试展示了批量插入大量行的不同方法。
- short_selects.py - 这一系列的测试展示了通过主键选择单个记录的不同方法。
- single_inserts.py - 在这一系列的测试中,我们看到了一种在不同事务中插入行并且之后返回到实质上“关闭”状态的方法。这类似于启动数据库连接的 API 调用,插入行,提交并关闭的过程。
- main.py - 允许 examples/performance 包作为脚本运行。
使用时间运行所有测试
这是运行的默认形式:
$ python -m examples.performance single_inserts Tests to run: test_orm_commit, test_bulk_save, test_bulk_insert_dictionaries, test_core, test_core_query_caching, test_dbapi_raw_w_connect, test_dbapi_raw_w_pool test_orm_commit : Individual INSERT/COMMIT pairs via the ORM (10000 iterations); total time 13.690218 sec test_bulk_save : Individual INSERT/COMMIT pairs using the "bulk" API (10000 iterations); total time 11.290371 sec test_bulk_insert_dictionaries : Individual INSERT/COMMIT pairs using the "bulk" API with dictionaries (10000 iterations); total time 10.814626 sec test_core : Individual INSERT/COMMIT pairs using Core. (10000 iterations); total time 9.665620 sec test_core_query_caching : Individual INSERT/COMMIT pairs using Core with query caching (10000 iterations); total time 9.209010 sec test_dbapi_raw_w_connect : Individual INSERT/COMMIT pairs w/ DBAPI + connection each time (10000 iterations); total time 9.551103 sec test_dbapi_raw_w_pool : Individual INSERT/COMMIT pairs w/ DBAPI + connection pool (10000 iterations); total time 8.001813 sec
为个别测试输出简要概要
可以为所有测试或更常见的是个别测试转储 Python 分析输出:
$ python -m examples.performance single_inserts --test test_core --num 1000 --dump Tests to run: test_core test_core : Individual INSERT/COMMIT pairs using Core. (1000 iterations); total fn calls 186109 186109 function calls (186102 primitive calls) in 1.089 seconds Ordered by: internal time, call count ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.634 0.001 0.634 0.001 {method 'commit' of 'sqlite3.Connection' objects} 1000 0.154 0.000 0.154 0.000 {method 'execute' of 'sqlite3.Cursor' objects} 1000 0.021 0.000 0.074 0.000 /Users/classic/dev/sqlalchemy/lib/sqlalchemy/sql/compiler.py:1950(_get_colparams) 1000 0.015 0.000 0.034 0.000 /Users/classic/dev/sqlalchemy/lib/sqlalchemy/engine/default.py:503(_init_compiled) 1 0.012 0.012 1.091 1.091 examples/performance/single_inserts.py:79(test_core) ...
编写你自己的测试套件
性能分析套件系统是可扩展的,并且可以应用于您自己的一组测试。这是在决定某些性能关键例程的正确方法时使用的宝贵技术。例如,如果我们想要分析几种加载之间的差异,我们可以创建一个名为test_loads.py
的文件,其中包含以下内容:
from examples.performance import Profiler from sqlalchemy import Integer, Column, create_engine, ForeignKey from sqlalchemy.orm import relationship, joinedload, subqueryload, Session from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() engine = None session = None class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) children = relationship("Child") class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parent_id = Column(Integer, ForeignKey('parent.id')) # Init with name of file, default number of items Profiler.init("test_loads", 1000) @Profiler.setup_once def setup_once(dburl, echo, num): "setup once. create an engine, insert fixture data" global engine engine = create_engine(dburl, echo=echo) Base.metadata.drop_all(engine) Base.metadata.create_all(engine) sess = Session(engine) sess.add_all([ Parent(children=[Child() for j in range(100)]) for i in range(num) ]) sess.commit() @Profiler.setup def setup(dburl, echo, num): "setup per test. create a new Session." global session session = Session(engine) # pre-connect so this part isn't profiled (if we choose) session.connection() @Profiler.profile def test_lazyload(n): "load everything, no eager loading." for parent in session.query(Parent): parent.children @Profiler.profile def test_joinedload(n): "load everything, joined eager loading." for parent in session.query(Parent).options(joinedload("children")): parent.children @Profiler.profile def test_subqueryload(n): "load everything, subquery eager loading." for parent in session.query(Parent).options(subqueryload("children")): parent.children if __name__ == '__main__': Profiler.main()
我们可以直接运行我们的新脚本:
$ python test_loads.py --dburl postgresql+psycopg2://scott:tiger@localhost/test Running setup once... Tests to run: test_lazyload, test_joinedload, test_subqueryload test_lazyload : load everything, no eager loading. (1000 iterations); total time 11.971159 sec test_joinedload : load everything, joined eager loading. (1000 iterations); total time 2.754592 sec test_subqueryload : load everything, subquery eager loading. (1000 iterations); total time 2.977696 sec
太空侵略者
使用 SQLite 作为状态机的太空侵略者游戏。
最初开发于 2012 年。已适配为在 Python 3 中运行。
在文本控制台中使用 ASCII 艺术运行。
运行:
python -m examples.space_invaders.space_invaders
在运行时,观察日志中的 SQL 输出:
tail -f space_invaders.log
尽情享受!
文件清单:
- space_invaders.py
SqlAlchemy 2.0 中文文档(三十三)(5)https://developer.aliyun.com/article/1562721