Psycopg2 db连接在丢失的网络连接上挂起

时间:2014-11-04 17:19:11

标签: python postgresql tcp timeout psycopg2

问题描述

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

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

详细说明:

  • 它至少挂了2个小时(我不能再等了)
  • “网络故障”情况实际上是VPN下降(数据库主机只能通过VPN访问)
  • 我不能使用异步连接,因为我需要交易
  • python 2.6
  • psycopg 2.2.1
  • debian linux 6和7,64bit
  • postgresql 8.4和9.1

我想要/需要什么

在运行查询之前,我需要一些可靠的方法来检测连接失败,因此我的程序不会挂起,或者使cursor.execute(..)在失败的连接上引发异常的方法。

实施例

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

4 个答案:

答案 0 :(得分:2)

经过长期的艰苦奋斗,我认为我只是通过执行其他人正在谈论的策略来解决此问题,而是使用psycopg2 connect函数本身:


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在长时间运行的查询上始终挂起,但是现在问题似乎已完全解决。

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

答案 1 :(得分:1)

看一下套接字超时,看完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)

答案 2 :(得分: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)

答案 3 :(得分:0)

为了确保连接仍然有效,请阅读属性connection.isolation_level。如果连接已经死亡,这将使OperationalError引发pgcode == "57P01"

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