我使用SQLAlchemy(确实不错的ORM,但文档不够清楚)与PostgreSQL通信
直到出现一种情况,即达到最大连接限制的postgres“崩溃”原因:不允许更多连接(max_client_conn)。
这种情况使我认为我做错了。经过几次实验,我弄清楚了如何不再面对这个问题,但是还有一些问题
在下面,您将看到没有提到问题的代码示例(在Python 3+中是默认的PostgreSQL设置),最终我想听到的是以下问题的答案:
建立与数据库的连接:
from sqlalchemy.pool import NullPool # does not work without NullPool, why?
def connect(user, password, db, host='localhost', port=5432):
"""Returns a connection and a metadata object"""
url = 'postgresql://{}:{}@{}:{}/{}'.format(user, password, host, port, db)
temp_con = sqlalchemy.create_engine(url, client_encoding='utf8', poolclass=NullPool)
temp_meta = sqlalchemy.MetaData(bind=temp_con, reflect=True)
return temp_con, temp_meta
用于使会话与数据库一起工作的功能:
from contextlib import contextmanager
@contextmanager
def session_scope():
con_loc, meta_loc = connect(db_user, db_pass, db_instance, 'localhost')
Session = sessionmaker(bind=con_loc)
"""Provide a transactional scope around a series of operations."""
session = Session()
try:
yield session
session.commit()
except:
session.rollback()
raise
查询示例:
with session_scope() as session:
entity = session.query(SomeEntity).first()
用于使会话与数据库一起工作的功能:
def create_session():
# connect method the same as in first example
con, meta = connect(db_user, db_pass, db_instance, 'localhost')
Session = sessionmaker(bind=con)
session = Session()
return session
查询示例:
session = create_session()
entity = session.query(SomeEntity).first()
希望你有主意
答案 0 :(得分:3)
首先,您不应该在connect()
函数中重复创建引擎。您的应用程序中的usual practice is to have a single global Engine
instance per database URL。由Session
创建的sessionmaker()
类也是如此。
- 上下文管理器对连接和会话有何作用?关闭会话并处理连接还是什么?
您已对其进行编程,如果不清楚,请read about context managers in general。在这种情况下,如果在由with语句控制的块内引发了异常,则commits或rolls back会话。这两个操作都将会话使用的连接返回到池(在您的情况下为NullPool
),因此仅关闭了连接。
- 为什么在“ connect”方法中没有NullPool作为poolclass的情况下,第一个可行的代码示例就具有问题的表现?
和
from sqlalchemy.pool import NullPool # does not work without NullPool, why?
在没有NullPool
的情况下,您反复创建的引擎也会建立池连接,因此,如果由于某种原因它们没有超出范围,或者其引用计数未归零,即使会话进行会话,它们也将保持连接还给他们。在第二个示例中,尚不清楚会话是否及时超出范围,因此它们可能还会保留连接。
- 为什么在第一个示例中,对于所有查询,我只有一个到db的连接,但是在第二个示例中,每个查询都得到了单独的连接? (如果我理解不对,请更正我,正在使用“ pgbouncer”进行检查)
由于使用了可正确处理事务的上下文管理器和NullPool
,第一个示例最终关闭了连接,因此该连接返回到保镖器,后者是另一个池层。
第二个示例可能从未关闭连接,因为它缺少事务处理,但是由于给出的示例,目前尚不清楚。 也可能在您创建的单独引擎中保持连接。
"Session Basics",尤其是"When do I construct a Session, when do I commit it, and when do I close it?"和"Is the session thread-safe?"的官方文档中已经涵盖了问题集的第4点。
有一个例外:脚本的多个实例。您不应在进程之间共享引擎,因此,为了在它们之间建立连接池,您需要一个外部池,例如PgBouncer。
答案 1 :(得分:1)
- 上下文管理器对连接和会话有何作用? 关闭会话并处理连接还是什么?
Python中的上下文管理器用于创建与with语句一起使用的运行时上下文。简单来说,当您运行代码时:
with session_scope() as session:
entity = session.query(SomeEntity).first()
session是成品会话。因此,对于您的问题,上下文管理器如何处理连接和会话。您所要做的就是看收益率发生了什么,看看会发生什么。在这种情况下,它只是要看:
try:
yield session
session.commit()
except:
session.rollback()
raise
如果您不触发任何异常,它将是session.commit(),根据SQLAlchemy文档,它将“刷新未决的更改并提交当前事务。”
- 为什么第一个有效的代码示例会像带有问题的示例那样运行 没有在“连接”方法中将NullPool作为poolclass?
poolclass参数只是告诉SQLAlchemy为什么要使用Pool
的子类。但是,在此处传递NullPool
的情况下,您告诉SQLAlchemy不要使用池,则在传递NullPool
时可以有效地禁用池连接。从文档中:“要禁用池,请改为将poolclass设置为NullPool。”我不能肯定地说,但是使用NullPool
可能会导致您的max_connection
问题。
- 为什么在第一个示例中,对于所有查询,我只有一个与db的连接 但是在第二个示例中,每个查询都有单独的连接? (如果我理解错了,请更正我,正在与 “ pgbouncer”)
我不确定。我认为这与第一个示例中的上下文管理器有关,因此with
块中的所有内容都将使用session
生成器。在第二个示例中,您创建了一个函数,该函数初始化一个新的Session
并返回它,因此您无需找回生成器。我还认为这与您的NullPool
使用有关,它可以防止连接池。使用NullPool
,每个查询执行都可以自己获取连接。
- 打开和关闭连接(和/或工作)的最佳实践是什么? 会话),当您将SQLAlchemy和PostgreSQL DB用于多个 侦听的脚本实例(或脚本中的单独线程) 请求,并且每个请求都有单独的会话? (我的意思是原始 SQLAlchemy而不是Flask-SQLAlchemy或类似的东西)
为此,请参见Is the session thread-safe?部分,但您需要对并发采取“不共享”方法。因此,在您的情况下,您需要脚本的每个实例在彼此之间不共享任何内容。
您可能想签出Working with Engines and Connections,如果并发是您正在从事的工作,那么我不希望使会话混乱。那里有有关NullPool和并发性的更多信息:
对于使用os.fork系统调用的多进程应用程序, 或例如Python多处理模块,通常是 要求为每个子进程使用单独的Engine。这个 是因为引擎维护了对连接池的引用, 最终引用DBAPI连接-这些往往不是 跨过程边界可移植。未配置的引擎 使用池化(通过使用NullPool实现)不会 有这个要求。
答案 2 :(得分:1)
@IljaEverilä的回答基本上很有帮助
我将在这里保留编辑后的代码,也许会对某人有所帮助
建立与数据库的连接::
from sqlalchemy.pool import NullPool # will work even without NullPool in code
def connect(user, password, db, host='localhost', port=5432):
"""Returns a connection and a metadata object"""
url = 'postgresql://{}:{}@{}:{}/{}'.format(user, password, host, port, db)
temp_con = sqlalchemy.create_engine(url, client_encoding='utf8', poolclass=NullPool)
temp_meta = sqlalchemy.MetaData(bind=temp_con, reflect=True)
return temp_con, temp_meta
每个应用的一个连接和sessionmaker实例,例如您的主要功能所在的地方:
from sqlalchemy.orm import sessionmaker
# create one connection and Sessionmaker to each instance of app (to avoid creating it repeatedly)
con, meta = connect(db_user, db_pass, db_instance, db_host)
session_maker = sessionmaker(bind=con) enter code here
使用with
语句进行会话的功能:
from contextlib import contextmanager
from some_place import session_maker
@contextmanager
def session_scope() -> Session:
"""Provide a transactional scope around a series of operations."""
session = session_maker() # create session from SQLAlchemy sessionmaker
try:
yield session
session.commit()
except:
session.rollback()
raise
包装交易并使用会话:
with session_scope() as session:
entity = session.query(SomeEntity).first()