如果不使用,数据库会挂起

Database hangs if not used

我有一个正在启动的 Web 应用程序。启动时工作正常,但如果我离开它(比如一个小时)并用另一个请求点击它,查询将挂起。我考虑过在每次查询后关闭它,然后打开一个新连接,但文档明确表示 "It is rare to Close a DB, as the DB handle is meant to be long-lived and shared between many goroutines."。我做错了什么?

package main

import (
  "database/sql"
  "log"
  "net/http"
  _ "github.com/lib/pq"
)

var Db *sql.DB

func main() {
  var err error
  Db, err = sql.Open("postgres", "user=me password=openupitsme host=my.host.not.yours dbname=mydb sslmode=require")
  if err != nil {
    log.Fatal("Cannot connect to db: ", err)
  }
  http.HandleFunc("/page", myHandler)
  http.ListenAndServe(":8080", nil)
}

func myHandler(w http.ResponseWriter, r *http.Request) {
  log.Println("Handling Request....", r)
  query := `SELECT pk FROM mytable LIMIT 1`
  rows, err := Db.Query(query)
  if err != nil {
    log.Println(err)
  }
  defer rows.Close()
  for rows.Next() {
    var pk int64
    if err := rows.Scan(&pk); err != nil {
      log.Println(err)
    }
    log.Println(pk)
  }
  log.Println("Request Served...")
}

编辑#1: 我的 postgres 日志显示:

2015-07-08 18:10:01 EDT [7710-1] user@here LOG:  could not receive data from client: Connection reset by peer
2015-07-08 18:20:01 EDT [7756-1] user@here LOG:  could not receive data from client: Connection reset by peer

我也遇到过类似的问题。在我们的案例中,问题是由位于客户端计算机和数据库之间的连接跟踪防火墙引起的。

此类防火墙会跟踪 TCP 级别的连接,并且为了限制资源使用,然后会使对它们来说长时间处于非活动状态的连接超时。我们在这种情况下观察到的症状与您的非常相似:在客户端,连接似乎挂起,而在服务器端您可以看到 connection reset by peer.

防止这种情况的一种方法是确保 TCP Keepalives are enabled, and that the keepalive interval is less than the timeout of the firewalls, routers, etc which are causing your connection issue. This is controlled by the libpq connection parameters keepalives, keepalives_idle, keepalives_interval and keepalives_count which you can set in the connection string. See the manual 对这些参数进行描述。

  • keepalive判断保活功能是否开启。它默认为 1(启用),因此您可能不需要指定它。
  • keepalives_idle 决定发送保活前的空闲时间。如果您不指定此项,它将默认为操作系统的默认值。

    在 Linux 系统中,您可以通过检查 /proc/sys/net/ipv4/tcp_keepalive_time 查看默认值 - 在我的服务器中,它设置为 7200 秒,这对您来说太长了,因为您的观察是约 1 小时后连接断开。

    您可以尝试将其设置为 2500 秒。

Linux 文档项目提供了一个有用的 TCP Keepalive HOWTO 文档,详细描述了它们的工作原理。

请注意,并非所有操作系统都支持 TCP 保活。如果您无法启用 keepalive,这里有一些您可能想要考虑的其他选项:

  1. 如果它在您的控制之下,请重新配置正在断开连接的 firewall/router,这样它就不会对 Postgresql 客户端连接这样做

  2. 在应用程序级别,您可能能够发送一些流量来保持数据库句柄处于活动状态 - 例如,每隔一小时左右发送一条 SELECT 1; 之类的语句。如果您的编程环境提供连接缓存(根据我收集到的评论),那么这可能很棘手。