SQLAlchemy对Postgres Schemas的支持

ele*_*ddy 53 postgresql sqlalchemy

我们使用SQLAlchemy和postgres托管多租户应用程序.我正在考虑从每个租户的单独数据库迁移到具有多个模式的单个数据库.SQLAlchemy本机支持吗?我基本上只是希望每个查询都以预定的模式为前缀...例如

select * from client1.users
Run Code Online (Sandbox Code Playgroud)

而不仅仅是

select * from users
Run Code Online (Sandbox Code Playgroud)

请注意,我想切换特定请求/请求集中的所有表的模式,而不仅仅是单个表.

我想这可以通过自定义查询类来完成,但我无法想象已经有了这样的事情.

zzz*_*eek 51

那么有几种方法可以解决这个问题,这取决于你的应用程序的结构.这是最基本的方式:

meta = MetaData(schema="client1")
Run Code Online (Sandbox Code Playgroud)

如果你的应用程序运行的方式是整个应用程序中的一个"客户端",那么你就完成了.

但是,这可能是错误的,该MetaData中的每个表都在该模式上.如果您希望一个应用程序同时支持多个客户端(通常是"多租户"的意思),这将是不实用的,因为您需要创建MetaData的副本并欺骗每个客户端的所有映射.这种方法可以完成,如果你真的想,它的工作方式是你用特定的映射类访问每个客户端,如:

client1_foo = Client1Foo()
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您将与http://www.sqlalchemy.org/trac/wiki/UsageRecipes/EntityName一起使用"实体名称"食谱sometable.tometadata()(参见http://docs.sqlalchemy.org/en) /latest/core/metadata.html#sqlalchemy.schema.Table.tometadata).

因此,让我们说它真正有效的方式是应用程序中的多个客户端,但每个线程一次只有一个.实际上,在Postgresql中最简单的方法是在开始使用连接时设置搜索路径:

# start request

# new session
sess = Session()

# set the search path
sess.execute("SET search_path TO client1")

# do stuff with session

# close it.  if you're using connection pooling, the
# search path is still set up there, so you might want to 
# revert it first
sess.close()
Run Code Online (Sandbox Code Playgroud)

最后的方法是使用@compiles扩展来覆盖编译器,以将"模式"名称粘贴在语句中.这是可行的,但是因为没有一致的钩子可以生成"Table",所以这很麻烦.您最好的选择可能是在每个请求上设置搜索路径.

  • 顺便说一句,我设法为声明性语法执行此操作,如:``Base = declarative_base(); Base.metadata.schema ='ebay'``.不过,可能有更好的方法. (4认同)
  • 重要:回滚还将回滚set set_path_path。随后的命令将针对默认模式发出。为了避免这种情况,请在设置之后发出明确的提交。http://www.postgresql.org/docs/current/static/sql-set.html (2认同)

小智 27

如果要在连接字符串级别执行此操作,请使用以下命令:

dbschema='schema1,schema2,public' # Searches left-to-right
engine = create_engine(
    'postgresql+psycopg2://dbuser@dbhost:5432/dbname',
    connect_args={'options': '-csearch_path={}'.format(dbschema)})
Run Code Online (Sandbox Code Playgroud)

但是,针对多客户端(多租户)应用程序的更好解决方案是为每个客户端配置不同的db用户,并为每个用户配置相关的search_path:

alter role user1 set search_path = "$user", public
Run Code Online (Sandbox Code Playgroud)

  • 虽然这适用于 sqlalchemy,但它不适用于 alembic(本机迁移工具)。如果将“search_path”用于模式控制,则修订生成脚本会完全混乱。根据 https://github.com/sqlalchemy/alembic/issues/569 的建议方法是不要触及“search_path”,而是在模型和迁移中明确定义架构。 (2认同)

Chr*_*ook 9

您可以使用sqlalchemy事件界面来管理它.因此,在创建第一个连接之前,请按照以下方式设置一个侦听器

from sqlalchemy import event
from sqlalchemy.pool import Pool

def set_search_path( db_conn, conn_proxy ):
    print "Setting search path..."
    db_conn.cursor().execute('set search_path=client9, public')

event.listen(Pool,'connect', set_search_path )
Run Code Online (Sandbox Code Playgroud)

显然,这需要在创建第一个连接之前执行(例如,在应用程序初始化中)

我在session.execute(...)解决方案中看到的问题是,它在会话使用的特定连接上执行.但是我在sqlalchemy中看不到任何可以保证会话将无限期地继续使用相同连接的内容.如果它从连接池中获取新连接,则它将丢失搜索路径设置.

我需要这样的方法来设置应用程序search_path,它与数据库或用户搜索路径不同.我希望能够在引擎配置中设置它,但是看不到这样做的方法.使用connect事件确实有效.如果有人有一个更简单的解决方案,我会感兴趣.

另一方面,如果您想要在应用程序中处理多个客户端,那么这将无法工作 - 我想session.execute(...)方法可能是最好的方法.


ech*_*cho 6

从 sqlalchemy 1.1 开始,可以使用 schema_translation_map 轻松完成此操作。

https://docs.sqlalchemy.org/en/11/changelog/migration_11.html#multi-tenancy-schema-translation-for-table-objects

connection = engine.connect().execution_options(
    schema_translate_map={None: "user_schema_one"})

result = connection.execute(user_table.select())
Run Code Online (Sandbox Code Playgroud)

以下是所有可用选项的详细评论: https://github.com/sqlalchemy/sqlalchemy/issues/4081


小智 5

表定义中有一个schema属性

我不确定它是否有效,但您可以尝试:

Table(CP.get('users', metadata, schema='client1',....)
Run Code Online (Sandbox Code Playgroud)


Abh*_*raj 5

It can now be done using schema translation map in Sqlalchemy 1.1.

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)

    __table_args__ = {'schema': 'per_user'}
Run Code Online (Sandbox Code Playgroud)

On each request, the Session can be set up to refer to a different schema each time:

session = Session()
session.connection(execution_options={
    "schema_translate_map": {"per_user": "account_one"}})

# will query from the ``account_one.user`` table

session.query(User).get(5)
Run Code Online (Sandbox Code Playgroud)

Referred it from the SO answer here.

Link to the Sqlalchemy docs.

  • 如果需要在一个应用程序中使用许多模式并在类级别定义它们,它特别有用 - 我一直在努力解决这个问题,直到找到这个答案,谢谢 (4认同)

Kon*_*nin 5

可以在数据库级别解决这个问题。我想您的应用程序有一个专用用户,该用户被授予了架构的一些权限。为他设置search_path这个模式:

ALTER ROLE your_user IN DATABASE your_db SET search_path TO your_schema;
Run Code Online (Sandbox Code Playgroud)