Documentation ¶
Overview ¶
Package dbcontrol limits the number of active connections for database/sql.
Implementations of Go's database/sql package prior to Go 1.2, don't let the user put a limit on the number of active connections to the underlying DB. If enough concurrent requests are made, so that the package runs out of available connections, then more are requested from the driver no matter how many you have at the pool already. Hence, unless you take precautions in your design/code, or there's specific support from the actual DB driver you're using, you are likely to hit some other limit (DB engine itself, OS) or simply run out of resources.
None of those situations are desirable, of course. If you hit DB or OS limits on the number of connections, then many of your statements will start failing cause no connection is available for them to use. You can get around it if you're lucky enough to have driver support, but then you are tied to a particular DB.
This package is a wrapper on Go's standard database/sql, providing a general mechanism so that you're free to use statements as usual, yet have the number of active connections limited. A wrapper DB type is declared, that supports all standard operations from database/sql. To use it, you should set the maximum number of connections you want to allow, just like:
dbcontrol.SetConcurrency(10)
All databases opened by the package afterwards will use a maximum of 10 connections. You can change this setting as often as you wish, but keep in mind that the number is bound to databases as they are opened, i.e., changing this concurrency setting has no effect on already-opened databases. Note also that you can get the default non-limited behavior by setting concurrency to zero. To open a DB you proceed just like with the database/sql package, like so:
db, err := dbcontrol.Open("mysql", dsn)
Note that sql.Row, sql.Rows and sql.Stmt types are overridden by this package, but that's probably transparent unless you declare the types explicitly. If you declare variables using the := operator you'll be fine. Usage now follows the expected pattern from database/sql:
rows, err := db.Query("select id, name from customers") if err != nil { log.Fatal(err) } for rows.Next() { var id int var name string if err := rows.Scan(&id, &name); err != nil { log.Fatal(err) } fmt.Println(id, name) }
The full set of features at database/sql is supported, including transactions, even though not all functions need to be overridden. This package was designed to provide the feature with minimum overhead, and thus uses knowledge of database/sql internals to know when locking is required/appropriate. As an extension, you can set a channel to receive the locking duration each time a connection has to be waited for. This can work as an aid to help you tune the pool size or otherwise work around concurrency problems. You can also set a channel where notifications will be sent every time a connection is held for longer than a certain timeout. The notification includes the full stack trace of the caller at the time the connection was requested. This can prove useful to identify long-running queries that are locking connections, and possibly impeding others from running. The feature can be turned on and off at will. A small performance penalty will be paid if on (that of retrieving the caller's stack), but none if the feature is off (the default).
Note that only functions specific to this package or with altered semantics are documented. Please refer to the database/sql package documentation for more information.
Index ¶
- func Concurrency() int
- func SetConcurrency(count int)
- type DB
- func (db *DB) Begin() (*Tx, error)
- func (db *DB) Exec(query string, args ...interface{}) (sql.Result, error)
- func (db *DB) MaxConns() int
- func (db *DB) Ping() error
- func (db *DB) Prepare(query string) (*Stmt, error)
- func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
- func (db *DB) QueryRow(query string, args ...interface{}) *Row
- func (db *DB) SetBlockDurationCh(c chan<- time.Duration)
- func (db *DB) SetMaxIdleConns(n int)
- func (db *DB) SetUsageTimeout(c chan<- string, timeout time.Duration)
- type Row
- type Rows
- type Stmt
- type Tx
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Concurrency ¶
func Concurrency() int
Concurrency returns the concurrency setting. See SetConcurrency().
func SetConcurrency ¶
func SetConcurrency(count int)
SetConcurrency sets the maximum number of simultaneous connections per database, for all subsequent DBs that are opened. (Note that this doesn't affect databases that are already open.) No capping on the number of connections is performed if concurrency is set to a non-possitive value.
Types ¶
type DB ¶
DB is the main type wrapping up sql.DB. You should use it just like you would sql.DB. If a connection is required and not available, the statement using the type will block until another connection is returned to the pool.
func (*DB) SetBlockDurationCh ¶
SetBlockDurationCh sets a channel used to report blocks on connections. Each time a connection has to be waited for due to the limit imposed by SetConcurrency(), this channel will receive the duration for that wait as soon as the connection becomes available. Setting the channel to nil (i.e., calling SetBlockDurationCh(nil)) will close the previously assigned channel, if any. Note also that this operation is safe to be called in parallel with other database requests.
func (*DB) SetMaxIdleConns ¶
SetMaxIdleConns sets the maximum number of idle connections to the database. However, note that this only makes sense if you're not limiting the number of concurrent connections. Databases opened under SetConcurrency(n) for n>0 will silently ignore this call. (The maximum number of connections in that case will match the concurrency value n.)
func (*DB) SetUsageTimeout ¶
SetUsageTimeout sets a maximum time for connection usage since it was granted to the caller (i.e., usage starts when a spare connection could be withdrawn from the pool, in case connection limiting is in use; see SetConcurrency()). After the time has elapsed a notification will be sent to the provided channel including the stack trace for the offending consumer (at the time the connection was requested). Setting the timeout to zero (the default) disables this feature. Note that this function is safe to be called anytime. Changes in the timeout will take effect for new requests; pending timers will still use the previous value. Changing the channel takes effect immediately, though. The previously set channel is guaranteed not to be used again after SetUsageTimeout() returns, thus allowing to safely close it if appropriate. Setting the channel to nil disables all pending and future notifications, until set to another valid channel. Note though that, in order to avoid needless resource usage, setting the channel to nil implies that no further timers will be started. (That is, you won't get a notification for a long running consumer that requested a connection when the channel was nil, even if you set the channel to a non-nil value before the timeout would expire. Consider fixing the channel and filtering events when reading from it if you're looking for that effect.) Note also that the timer expiring, whether notified or not, has no effect whatsoever on the routine using the connection.