【问题标题】:Psycopg2 db connection hangs on lost network connectionPsycopg2 数据库连接在丢失的网络连接上挂起
【发布时间】:2014-12-31 16:46:40
【问题描述】:

问题描述

我正在使用 psycopg2 连接到远程主机上的 PostgreSQL 数据库。我打开一个连接并等待请求,然后对于每个请求,我对连接运行查询并返回数据。

但是当连接已经打开后网络连接丢失时,下一个数据库查询挂起,我必须手动终止程序。

详情:

  • 它至少挂了 2 个小时(我等不及了)
  • “网络宕机”的情况实际上是 VPN 宕机(db 主机只能通过 VPN 访问)
  • 我不能使用异步连接,因为我需要事务
  • python 2.6
  • psycopg 2.2.1
  • debian linux 6 和 7、64 位
  • postgresql 8.4 和 9.1

我想要/需要什么

在运行查询之前,我需要一些可靠的方法来检测失败的连接,这样我的程序就不会挂起,或者让cursor.execute(..) 在连接失败时引发异常。 p>

示例:

import psycopg2
import time

conn = psycopg2.connect("host='dbs' dbname='foo' user='joe' password='x'")
time.sleep(10) # I manually turn VPN off during this sleep..
cu = conn.cursor()
cu.execute('SELECT 1') # <- hangs here
print cu.fetchone()
cu.commit()

我尝试了什么(什么没用):

  • “全局”设置 TCP 超时 - 在 psycopg2 导入之前,我添加了:

    import socket
    socket.setdefaulttimeout(10)
    
  • psycopg.connection 的套接字上设置 TCP 超时:

    ..
    conn = psycopg2.connect(...
    s = socket.fromfd(conn.fileno(), socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(5)
    ..
    
  • psycopg.connection的套接字启用keepalive:

    ...
    conn = psycopg2.connect(...
    s = socket.fromfd(conn.fileno(), socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(5)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
    s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
    s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 3)
    s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)
    ...
    

【问题讨论】:

  • Jan,你找到解决办法了吗?我们在 2016 年也面临同样的问题:)
  • @scythargon:嗯,有点……我重写了所有需要在 plpgsql 中进行事务的东西,并切换到异步连接。我不会称其为解决方案..更像是一种解决方法..我花了比我感到舒服的时间更多的时间,但我没有其他办法..

标签: python postgresql tcp timeout psycopg2


【解决方案1】:

配置 KEEPALIVE 的 OP 和 Gabriel Salla 的解决方案不完整。此解决方案仅在连接空闲(在网络关闭之前没有发送数据)并且网络关闭时才有效。

如果某些数据已经通过网络发送,但已经关闭但尚未被 KEEPALIVE 功能检测到,则会出现挂起。发生这种情况是因为在发送某些数据时使用了 RTO 机制而不是 KEEPALIVE。

要为 RTO 设置超时,您必须为套接字设置 TCP_USER_TIMEOUT 超时(以毫秒为单位)。

完整的解决方案是(KEEPALIVE 和 RTO 超时都配置为 10 秒):

s = socket.fromfd(conn.fileno(), socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 6)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 2)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, 10000)

【讨论】:

    【解决方案2】:

    经过漫长而残酷的斗争,我想我通过简单地执行其他人正在谈论的策略解决了这个问题,但使用了 psycopg2 连接函数本身:

    
    from psycopg2 import connect
    
    
    conn = connect(
            database=database,
            user=username,
            password=password,
            host=hostname,
            port=port,
            connect_timeout=3,
            # https://www.postgresql.org/docs/9.3/libpq-connect.html
            keepalives=1,
            keepalives_idle=5,
            keepalives_interval=2,
            keepalives_count=2)
    

    我看到 psycopg2 在长时间运行的查询中一直挂起,但现在问题似乎已完全解决。

    请注意,这可能是新功能,因为这个问题很旧。

    【讨论】:

    【解决方案3】:

    查看了套接字超时,在阅读了 thisthis 之后,这些设置对我有用

    s = socket.fromfd(connection.fileno(),
                      socket.AF_INET, socket.SOCK_STREAM)
    # Enable sending of keep-alive messages
    s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
    # Time the connection needs to remain idle before start sending
    # keepalive probes
    s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(ceil(time)))
    # Time between individual keepalive probes
    s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1)
    # The maximum number of keepalive probes should send before dropping
    # the connection
    s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
    

    【讨论】:

    • 这似乎与我 3 年前尝试的方法太相似了——问题的最后一段——但是因为我已经重写了整个数据库代码,所以我无法测试那些细微的差异(5 代替TCP_KEEPCNT 中的 3 个,TCP_KEEPINTVL 中的 1 而不是 3 以及 TCP_KEEPIDLE 中完全未知的int(ceil(time)))确实使它起作用。如果 scythargon 或其他人可以确认这行得通,我会接受。
    • 您最终找到解决方案了吗?根据我的测试,我认为只有在 VPN 连接丢失并重新启动时才会发生这种情况,在真正的“网络关闭”解决方案中,一旦网络再次启动,套接字似乎又可以运行了 - 你能确认一下吗?
    • @cp2587 此配​​置仅适用于空闲连接。在连接丢失并且在 KEEPIDLE 间隔持续之前发送的一些数据的情况下,连接将挂起几分钟或几小时。要解决这个问题,必须设置 TCP_USER_TIMEOUT
    【解决方案4】:

    为了确保连接仍然有效,请阅读属性connection.isolation_level。这将引发OperationalErrorpgcode == "57P01",以防连接失效。

    try: connection.isolation_level except OperationalError as oe: conn = psycopg2.connect(dsn)

    【讨论】:

    • 不幸的是,它没有 - 我得到 conn.isolation_level 的有效值 (1),没有例外。
    猜你喜欢
    • 2013-09-02
    • 1970-01-01
    • 2015-12-27
    • 2014-09-03
    • 1970-01-01
    • 1970-01-01
    • 2012-04-06
    • 2020-05-08
    • 2015-07-27
    相关资源
    最近更新 更多