【问题标题】:Is connection pool in sqlalchemy thread-safe?sqlalchemy 中的连接池是线程安全的吗?
【发布时间】:2019-01-17 01:17:13
【问题描述】:

文档说连接池也不是为多线程设计的:

在使用连接池时很关键,并且通过扩展,当 使用通过 create_engine() 创建的引擎,池化 连接不共享给分叉的进程。 TCP 连接是 表示为文件描述符,通常跨进程工作 边界,这意味着这将导致对文件的并发访问 代表两个或多个完全独立的 Python 的描述符 解释器状态。

据我了解,如果我创建连接池:

self.engine = create_engine('postgresql://{user}:{password}@{host}:{port}/{db}'.format(
    user=Configuration().get(section='repository', option='user'),
    password=Configuration().get(section='repository', option='password'),
    host=Configuration().get(section='repository', option='host'),
    port=Configuration().get(section='repository', option='port'),
    db=Configuration().get(section='repository', option='database')
), echo=False, pool_size=3)

self.session = sessionmaker(self.engine, expire_on_commit=False)

然后在不同的线程中调用self.session(),我将有3个不同的连接用于N个不同的线程。 这是否意味着只有 3 个并发线程会做一些工作,而其他线程会等到一个或多个线程调用session.close()?或者有可能>2个线程同时使用同一个连接?

NullPool 是否更安全(因为每个新会话都是一个新连接)?

self.engine = create_engine('postgresql://{user}:{password}@{host}:{port}/{db}'.format(
            user=Configuration().get(section='repository', option='user'),
            password=Configuration().get(section='repository', option='password'),
            host=Configuration().get(section='repository', option='host'),
            port=Configuration().get(section='repository', option='port'),
            db=Configuration().get(section='repository', option='database')
        ), echo=False, poolclass=NullPool)

一般问题:在这种情况下是否可以使用相同的连接池:

engine = create_engine('connection_string', echo=False, pool_size=3)
Session = sessionmaker(engine)

def some_function():
    session = Session()
    ...

pool = Pool(processes=10)
pool.map(some_function)
pool.close()
pool.join()

【问题讨论】:

标签: python multithreading sqlalchemy


【解决方案1】:

总而言之,线程和进程之间似乎混合在一起。该问题首先询问 SQLAlchemy 连接池是否是线程安全的,但以使用 multiprocessing 的代码示例结束。 “一般问题”的简短回答是:不,如果使用分叉,则不应在进程边界上共享引擎及其关联的连接池。不过也有例外。

池实现本身是线程安全的,并且通过代理Engine is thread-safe as well,因为除了保持对池的引用之外,引擎不保持状态。另一方面,从池中签出的连接是not thread-safeneither is a Session

文档说连接池也不是为多线程设计的:

有一点误读,因为文档中的原始引用是关于在 process 边界上共享连接池(如果使用分叉)。这可能会导致麻烦,因为在 SQLAlchemy 和 DB-API 层之下通常有一个 TCP/IP 套接字或文件句柄,它们不应该同时操作。

在这种特殊情况下,使用NullPool 是安全的,而其他情况则不是,因为它根本不池化,因此进程之间不会共享连接,除非有人特意这样做。

这是否意味着只有 3 个并发 线程 会做一些工作,而其他线程会等到一个或多个线程调用session.close()

假设 QueuePool 正在使用中,则设置大小不是硬性限制,并且有一些溢出空间。大小决定了要在池中永久保留的连接数。如果达到溢出限制,调用将等待timeout 秒,然后放弃并引发TimeoutError,如果没有可用的连接。

或者有可能>2个线程会同时使用同一个连接?

除了StaticPool 之外,两个或多个线程 不会意外地从池中签出同一个连接,但之后可以在线程之间显式共享它(不要)。


最后,"Working with Engines and Connections - Basic Usage" 涵盖了问题的主要部分:

单个 Engine 代表进程管理许多单独的 DBAPI 连接,并且旨在以并发方式调用 [强调添加]。

...

对于使用os.fork 系统调用的多进程应用程序,或者例如Python multiprocessing 模块,通常需要为每个子进程使用单独的Engine。这是因为Engine 维护对连接池的引用,该连接池最终引用 DBAPI 连接 - 这些往往不能跨进程边界移植。配置为不使用池的Engine(通过使用NullPool 实现)没有此要求。

【讨论】:

  • 惊人的答案!谢谢! The question begins by asking if an SQLAlchemy connection pool is thread-safe, but ends with a code example that uses multiprocessing 可能我还是混淆了这个术语
  • 一个process 可以包含一个或多个threads,同时运行,也可能并行运行。两个may seem a bit blurred at times之间的界线。
【解决方案2】:

以防万一这对其他人有所帮助——这实际上是对另一个问题的答案,那就是:

SQLAlchemy 是否对同一线程中的所有引擎使用相同的连接池?

答案是否定的。正如@ilja-everila 指出的那样,SQLA expects 您可以为每个进程使用一个 engine。所以如果你这样做了

engine1 = create_engine(...)
engine2 = create_engine(...)
engine1.pool is engine2.pool   # <- False

# so although pool_size=5, you can open more than 5 total connections
# because each engine has separate pools
connections1 = [engine1.connect() for _ in range(5)]
connections2 = [engine1.connect() for _ in range(5)]

因此,如果您来到这里想知道为什么要最大化您的 max_connections,并且您的代码使用了许多单独的 engine 实例,即使它在同一个线程中,您也不能指望它们共享一个连接池。

连接池可能是线程安全的,但它们对于每个引擎实例都是唯一的。

因此,您的目标应该是为您的应用程序提供一个全局/单例引擎实例。

从我的失败中吸取教训!

【讨论】:

    猜你喜欢
    • 2019-07-31
    • 1970-01-01
    • 2010-11-15
    • 1970-01-01
    • 2017-03-12
    • 2016-05-16
    • 1970-01-01
    • 1970-01-01
    • 2021-10-12
    相关资源
    最近更新 更多