SQLAlchemy-处理与数据库和会话的连接(行为不明确,并且在文档中有介绍)

时间:2018-07-01 15:41:40

标签: python postgresql sqlalchemy

我使用SQLAlchemy(确实不错的ORM,但文档不够清楚)与PostgreSQL通信
直到出现一种情况,即达到最大连接限制的postgres“崩溃”原因:不允许更多连接(max_client_conn)
这种情况使我认为我做错了。经过几次实验,我弄清楚了如何不再面对这个问题,但是还有一些问题

在下面,您将看到没有提到问题的代码示例(在Python 3+中是默认的PostgreSQL设置),最终我想听到的是以下问题的答案:

  1. 上下文管理器对连接和会话有何作用?关闭会话并处理连接还是什么?
  2. 在“连接”方法中,为什么没有 NullPool 作为poolclass的问题的第一个可行示例的行为举止如此?
  3. 为什么在第一个示例中,对于所有查询,我只有一个到db的连接,但是在第二个示例中,每个查询都得到了单独的连接? (如果我理解错了,请更正我,正在使用“ pgbouncer” 进行检查)
  4. 当您将SQLAlchemy和PostgreSQL DB用于侦听请求且必须具有单独会话的脚本的多个实例(或脚本中的单独线程)时,打开和关闭连接(和/或使用Session)的最佳实践是什么?他们每个人? (我的意思是原始SQLAlchemy不是Flask-SQLAlchemy或类似的东西)

    没有问题的有效代码示例:

建立与数据库的连接

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()


希望你有主意

3 个答案:

答案 0 :(得分:3)

首先,您不应该在connect()函数中重复创建引擎。您的应用程序中的usual practice is to have a single global Engine instance per database URL。由Session创建的sessionmaker()类也是如此。

  
      
  1. 上下文管理器对连接和会话有何作用?关闭会话并处理连接还是什么?
  2.   

您已对其进行编程,如果不清楚,请read about context managers in general。在这种情况下,如果在由with语句控制的块内引发了异常,则commitsrolls back会话。这两个操作都将会话使用的连接返回到池(在您的情况下为NullPool),因此仅关闭了连接。

  
      
  1. 为什么在“ connect”方法中没有NullPool作为poolclass的情况下,第一个可行的代码示例就具有问题的表现?
  2.   

from sqlalchemy.pool import NullPool  # does not work without NullPool, why?

在没有NullPool的情况下,您反复创建的引擎也会建立池连接,因此,如果由于某种原因它们没有超出范围,或者其引用计数未归零,即使会话进行会话,它们也将保持连接还给他们。在第二个示例中,尚不清楚会话是否及时超出范围,因此它们可能还会保留连接。

  
      
  1. 为什么在第一个示例中,对于所有查询,我只有一个到db的连接,但是在第二个示例中,每个查询都得到了单独的连接? (如果我理解不对,请更正我,正在使用“ pgbouncer”进行检查)
  2.   

由于使用了可正确处理事务的上下文管理器和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)

  
      
  1. 上下文管理器对连接和会话有何作用?   关闭会话并处理连接还是什么?
  2.   

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文档,它将“刷新未决的更改并提交当前事务。”

  
      
  1. 为什么第一个有效的代码示例会像带有问题的示例那样运行   没有在“连接”方法中将NullPool作为poolclass?
  2.   

poolclass参数只是告诉SQLAlchemy为什么要使用Pool的子类。但是,在此处传递NullPool的情况下,您告诉SQLAlchemy不要使用池,则在传递NullPool时可以有效地禁用池连接。从文档中:“要禁用池,请改为将poolclass设置为NullPool。”我不能肯定地说,但是使用NullPool可能会导致您的max_connection问题。

  
      
  1. 为什么在第一个示例中,对于所有查询,我只有一个与db的连接   但是在第二个示例中,每个查询都有单独的连接?   (如果我理解错了,请更正我,正在与   “ pgbouncer”)
  2.   

我不确定。我认为这与第一个示例中的上下文管理器有关,因此with块中的所有内容都将使用session生成器。在第二个示例中,您创建了一个函数,该函数初始化一个新的Session并返回它,因此您无需找回生成器。我还认为这与您的NullPool使用有关,它可以防止连接池。使用NullPool,每个查询执行都可以自己获取连接。

  
      
  1. 打开和关闭连接(和/或工作)的最佳实践是什么?   会话),当您将SQLAlchemy和PostgreSQL DB用于多个   侦听的脚本实例(或脚本中的单独线程)   请求,并且每个请求都有单独的会话? (我的意思是原始   SQLAlchemy而不是Flask-SQLAlchemy或类似的东西)
  2.   

为此,请参见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()