全局数据库连接和每次打开连接之间的性能差异在Golang中是什么?

huangapple go评论76阅读模式
英文:

Performance difference between global database connection and opening connection everytime on Golang

问题

在我的当前项目中,每当用户发出请求时,我都会打开一个新的数据库连接。例如:

func login(w http.ResponseWriter, r *http.Request) {
    ...
    db, err := sqlx.Connect("postgres", "user=postgres password=*** dbname=postgres")
    if err != nil {
        ErrorWithJSON(w, err.Error(), http.StatusBadRequest)
        return
    }
    db.SetMaxIdleConns(0)
    db.SetConnMaxLifetime(time.Second * 30)
    user, err := loginManager(db, m)
    ...
    err = db.Close()
}

当我搜索其他人的代码时,我发现大多数开发人员都会为数据库连接创建一个全局变量,在主函数中设置它,并在整个项目中使用这个变量。

我想知道这些方法之间是否有任何区别?如果我使用全局变量,当5个不同的用户发出注册/登录等请求时,是否会有延迟?如果有延迟,我应该创建多个数据库连接并将它们存储在一个切片中以供将来的请求使用,这样当用户发出请求时我可以随机选择一个。就像一个简单的负载均衡器,我不知道?

对于多个问题,我很抱歉。谢谢!

英文:

In my current project I was opening a new database connection every time when user makes request. For example:

func login(w http.ResponseWriter, r *http.Request) {

...

db, err := sqlx.Connect("postgres", "user=postgres password=*** dbname=postgres")

if err != nil {
	ErrorWithJSON(w, err.Error(), http.StatusBadRequest)
	return
}

db.SetMaxIdleConns(0)
db.SetConnMaxLifetime(time.Second * 30)

user, err := loginManager(db, m)

...

err = db.Close()

}

When I searched for other people's code, I've seen that most of the developers create a global variable for database connection, set it on the main and use this variable on entire project.

I was wondering is there any difference between these approaches? If I use global variable will there be any latency when 5 different users makes requests for register/login etc. If there will be latency, should I create multiple database connections and store them in a slice for future requests so I can pick randomly when users make request. Like a simple load balancer, I don't know?

Sorry for multiple questions. Thank you!

答案1

得分: 5

是的,性能差异可能很大(取决于你运行的查询的性质以及系统和服务器配置),可能相差几个数量级。

sqlx.DB 类型包装(嵌入)了 sql.DB 类型,后者管理一个连接池:

> DB 是表示零个或多个底层连接的数据库句柄。它可以被多个 goroutine 并发使用。
>
> sql 包会自动创建和释放连接;它还维护一个空闲连接的自由池。如果数据库有每个连接状态的概念,那么只能在事务中可靠地观察到该状态。

每次打开一个新连接时,都会发生许多事情:连接字符串必须被解析,TCP 连接必须被建立,必须执行身份验证/授权,必须在客户端和服务器两端分配资源等等。这些只是主要的、显而易见的事情。即使其中一些可能已经提供/实现了优化、缓存,与具有多个已建立、经过身份验证的连接准备在池中等待使用/利用的单个 DB 实例相比,仍然存在显着的开销。

此外,引用自 sql.Open()

> 返回的 DB 可以被多个 goroutine 并发使用,并且维护其自己的空闲连接池。因此,应该只调用一次 Open 函数。很少需要关闭 DB。

你使用的 sqlx.Connect() 调用了 sqlx.Open(),它是 "与 sql.Open 相同,但返回 *sqlx.DB"

总之,使用单个全局的 sqlx.DBsql.DB 实例,并在各处共享/使用它。它为你提供了自动的连接和连接池管理。这将为你提供最佳的性能。你可以使用 DB.SetConnMaxLifetime()DB.SetMaxIdleConns()DB.SetMaxOpenConns() 方法来微调连接池。

空闲连接(DB.SetMaxIdleConns())是当前未使用但在池中等待被使用的连接。你应该确保有一些这样的连接,例如 5 或 10 个,甚至更多。DB.SetConnMaxLifetime() 控制新连接可以使用的时间长度。一旦它的使用时间超过这个时间,它将被关闭(如果需要,将打开一个新连接)。你不应该更改这个值,默认行为是不会过期连接。基本上所有的默认值都是合理的,只有在遇到性能问题时才需要调整它们。此外,阅读这些方法的文档以获得清晰的图片。

请参阅类似的、可能重复的问题:

https://stackoverflow.com/questions/40999637/mgo-query-performance-seems-consistently-slow-500-650ms/41000876#41000876

英文:

Yes, there might be a huge performance difference (might be several order of magnitude depending on the nature of queries you run and on system and server configuration).

The sqlx.DB type wraps (embeds) an sql.DB type, which manages a pool of connections:

> DB is a database handle representing a pool of zero or more underlying connections. It's safe for concurrent use by multiple goroutines.
>
> The sql package creates and frees connections automatically; it also maintains a free pool of idle connections. If the database has a concept of per-connection state, such state can only be reliably observed within a transaction.

Every time you open a new connection, a lot of things have to happen in the "background": connection string has to be parsed, a TCP connection has to be estabilished, authentication / authorization must be performed, resources must be allocated at both sides (client and server) etc. These are just the main, obvious things. Even though some of these may be provided / implemented optimized, cached, there is still a significant overhead compared to having a single DB instance which might have multiple established, authenticated connections ready in a pool, waiting to be used / utilized.

Also quoting from sql.Open():

> The returned DB is safe for concurrent use by multiple goroutines and maintains its own pool of idle connections. Thus, the Open function should be called just once. It is rarely necessary to close a DB.

sqlx.Connect() which you used calls sqlx.Open() which is "the same as sql.Open, but returns an *sqlx.DB instead".

So all in all, use a single, global sqlx.DB or sql.DB instance, and share / use that everywhere. It provides you automatic connection- and connection pool management. This will provide you the best performance. You may fine-tune the connection pool with the DB.SetConnMaxLifetime(), DB.SetMaxIdleConns() and DB.SetMaxOpenConns() methods.

Idle connections (DB.SetMaxIdleConns()) are those that are not in-use currently, but sitting in the pool, waiting for someone to pick them up. You should definitely have some of these, e.g. 5 or 10 of them, or even more. DB.SetConnMaxLifetime() controls how long a new connection may be used. Once it grows older than this, it will be closed (and a new one will be opened if needed). You shouldn't change this, default behavior is never to expire connections. Basically all defaults are sensible, you should only play with them if you experience performance problems. Also, read docs of these methods to have a clear picture.

See this similar, possible duplicate question:

https://stackoverflow.com/questions/40999637/mgo-query-performance-seems-consistently-slow-500-650ms/41000876#41000876

huangapple
  • 本文由 发表于 2017年6月15日 16:53:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/44563029.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定