SQLAlchemy automap:性能的最佳实践

时间:2017-01-09 12:08:26

标签: sqlalchemy

我正在围绕现有(mysql)数据库构建一个python应用程序,并使用automap来推断表和关系:

    base = automap_base()

    self.engine = create_engine(
        'mysql://%s:%s@%s/%s?charset=utf8mb4' % (
            config.DB_USER, config.DB_PASSWD, config.DB_HOST, config.DB_NAME
        ), echo=False
    )

    # reflect the tables
    base.prepare(self.engine, reflect=True)

    self.TableName = base.classes.table_name

使用这个我可以做session.query(TableName)之类的事情...... 但是,我担心性能,因为每次应用程序运行它都会再次进行整个推理。

  • 这是合法的关注吗?
  • 如果是这样,是否有可能“缓存”Automap的输出?

3 个答案:

答案 0 :(得分:4)

我认为"反映"数据库的结构不是要走的路。除非您的应用尝试推断"结构中的东西,如源代码文件的静态代码分析,那就没必要了。在运行时反映它的另一个原因是缩短了开始时间"使用"使用SQLAlchemy的数据库。但是:

另一种选择是使用类似SQLACodegen(https://pypi.python.org/pypi/sqlacodegen)的东西:

它将反映"您的数据库一次,并创建一个99.5%准确的声明性SQLAlchemy模型集供您使用。但是,这确实要求您随后使模型与数据库的结构保持同步。我认为这并不是一个大问题,因为您已经使用的表格足够稳定,因此运行时反映其结构不会对您的程序造成太大影响。

生成声明性模型本质上是一个"缓存"反思。只是SQLACodegen将其保存为一组非常易读的类+字段而不是内存中的数据。即使有不断变化的结构,我自己的改变"对于生成的声明性模型,每当我进行数据库更改时,我仍会在项目中使用SQLACodegen。这意味着我的模型通常在彼此之间保持一致,并且由于复制粘贴,我不会遇到拼写错误和数据不匹配等问题。

答案 1 :(得分:3)

你的第一个问题的答案主要是主观的。您正在添加数据库查询以将反射元数据提取到应用程序加载时。该开销是否显着取决于您的项目要求。

作为参考,我有一个使用反射模式的内部工具,因为我们的团队可以接受加载时间。如果它是一个面向外部的产品,情况可能并非如此。我的预感是,对于大多数应用程序而言,反射开销不会占用整个应用程序加载时间。

如果您认为它对您的目的很重要,那么question有一个有趣的答案,用户在那里挑选数据库元数据以便在本地缓存它。

答案 2 :(得分:1)

性能可以合理考虑。如果数据库架构没有更改,则每次运行脚本时都要反映数据库很耗时。在开发过程中,这不是一个长期运行的应用程序,而是更多的问题。如果您的数据库位于远程服务器上(同样,尤其是在开发过程中),这也可以节省大量时间。

我使用与答案here类似的代码(如@ACV所示)。总体计划是第一次执行反射,然后使元数据对象腌制。下次运行脚本时,它将查找pickle文件并使用它。该文件可以在任何位置,但是我将其放在~/.sqlalchemy_cache中。这是一个基于您的代码的示例。

import os
from sqlalchemy.ext.declarative import declarative_base

self.engine = create_engine(
    'mysql://%s:%s@%s/%s?charset=utf8mb4' % (
        config.DB_USER, config.DB_PASSWD, config.DB_HOST, config.DB_NAME
    ), echo=False
)

metadata_pickle_filename = "mydb_metadata"
cache_path = os.path.join(os.path.expanduser("~"), ".sqlalchemy_cache")
cached_metadata = None
if os.path.exists(cache_path):
try:
    with open(os.path.join(cache_path, metadata_pickle_filename), 'rb') as cache_file:
        cached_metadata = pickle.load(file=cache_file)
except IOError:
    # cache file not found - no problem, reflect as usual
    pass

if cached_metadata:
    base = declarative_base(bind=self.engine, metadata=cached_metadata)
else:
    base = automap_base()
    base.prepare(self.engine, reflect=True) # reflect the tables

    # save the metadata for future runs
    try:
        if not os.path.exists(cache_path):
            os.makedirs(cache_path)
        # make sure to open in binary mode - we're writing bytes, not str
        with open(os.path.join(cache_path, metadata_pickle_filename), 'wb') as cache_file:
            pickle.dump(Base.metadata, cache_file)
    except:
        # couldn't write the file for some reason
        pass

self.TableName = base.classes.table_name

对于使用声明性表类定义的任何人,假设将Base对象定义为例如

Base = declarative_base(bind=engine)

metadata_pickle_filename = "ModelClasses_trilliandb_trillian.pickle"

# ------------------------------------------
# Load the cached metadata if it's available
# ------------------------------------------
# NOTE: delete the cached file if the database schema changes!!
cache_path = os.path.join(os.path.expanduser("~"), ".sqlalchemy_cache")
cached_metadata = None
if os.path.exists(cache_path):
    try:
        with open(os.path.join(cache_path, metadata_pickle_filename), 'rb') as cache_file:
            cached_metadata = pickle.load(file=cache_file)
    except IOError:
        # cache file not found - no problem
        pass
# ------------------------------------------

# define all tables
#
class MyTable(Base):
    if cached_metadata:
        __table__ = cached_metadata.tables['my_schema.my_table']
    else:
        __tablename__ = 'my_table'
        __table_args__ = {'autoload':True, 'schema':'my_schema'}

...
# ----------------------------------------
# If no cached metadata was found, save it
# ----------------------------------------
if cached_metadata is None:
    # cache the metadata for future loading
    # - MUST DELETE IF THE DATABASE SCHEMA HAS CHANGED
    try:
        if not os.path.exists(cache_path):
            os.makedirs(cache_path)
        # make sure to open in binary mode - we're writing bytes, not str
        with open(os.path.join(cache_path, metadata_pickle_filename), 'wb') as cache_file:
            pickle.dump(Base.metadata, cache_file)
    except:
        # couldn't write the file for some reason
        pass

重要提示!如果数据库架构发生更改,则必须删除缓存的文件以强制代码自动加载并创建新的缓存。如果您不这样做,更改将反映在代码中。这是一件容易忘记的事情。

相关问题