-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
database/sql/v2: what would a Go 2 version of database/sql look like? #22697
Comments
database/sql Go2 wishlist:
database/sql suggested exploration topics prior to Go2:
Experience Reports |
About sql.Stmt
|
"Some database servers have limits on how many prepared statements can be prepared at once." I need gc the Here is the func IsErrorMsgTooManyPreparedStmt(errMsg string) bool{
// Error 1461: Can't create more than max_prepared_stmt_count statements (current value: 16382)
return strings.Contains(errMsg,"Error 1461: Can't create more than max_prepared_stmt_count statements")
}
type Ci struct{
Query func(query string, args ...interface{}) (*sql.Rows, error)
Exec func(query string, args ...interface{}) (sql.Result, error)
Prepare func(query string) (*sql.Stmt, error)
db *Db
IsInTransaction bool
stmtCacheMap map[string]*stmtCacheEntry
lastStmtCacheTime uint64
stmtCacheLocker sync.Mutex
}
type stmtCacheEntry struct{
stmt *sql.Stmt
key string
lastUseTime uint64
}
func (ci *Ci) cleanPrepareCache(){
ci.stmtCacheLocker.Lock()
ci.stmtCacheMap = map[string]*stmtCacheEntry{}
ci.stmtCacheLocker.Unlock()
}
func (ci *Ci) prepareWithCache(query string) (stmt *sql.Stmt, err error){
ci.stmtCacheLocker.Lock()
ci.lastStmtCacheTime++
thisStmtCacheTime:=ci.lastStmtCacheTime
if ci.stmtCacheMap==nil{
ci.stmtCacheMap = map[string]*stmtCacheEntry{}
}
entry:= ci.stmtCacheMap[query]
// 已经有缓存,直接使用。
if entry!=nil{
entry.lastUseTime = thisStmtCacheTime
ci.stmtCacheLocker.Unlock()
return entry.stmt,nil
}
ci.stmtCacheLocker.Unlock()
for i:=0;i<3;i++{
stmt,err=ci.Prepare(query)
if err!=nil{
if IsErrorMsgTooManyPreparedStmt(err.Error()){
// 可能客户端实例太多,此处遇见太多stmt的报错后,就关闭自己的一般stmt,以便后面的程序可以继续进行。
// 由于每一个连接会把用到过的 stmt 都创建一遍。所以即使把 stmt限制设置的很小。也依然会 报这个问题。
ci.stmtCacheLocker.Lock()
ci.gcNotUsePrepare__NOLOCK(len(ci.stmtCacheMap)/2)
ci.stmtCacheLocker.Unlock()
continue
}
return nil,err
}
}
if err!=nil{
return nil,err
}
ci.stmtCacheLocker.Lock()
oldEntry:=ci.stmtCacheMap[query]
if oldEntry==nil{
if len(ci.stmtCacheMap)>= ci.db.req.StmtCacheMaxQueryNum{
ci.gcNotUsePrepare__NOLOCK(ci.db.req.StmtCacheMaxQueryNum/2)
}
entry = &stmtCacheEntry{
stmt: stmt,
key: query,
lastUseTime: thisStmtCacheTime,
}
ci.stmtCacheMap[query] = entry
ci.stmtCacheLocker.Unlock()
return entry.stmt,nil
}else{
// 可能另一个线程已经初始化好了,此处直接使用。
ci.stmtCacheLocker.Unlock()
stmt.Close()
entry = oldEntry
entry.lastUseTime = thisStmtCacheTime
return entry.stmt,nil
}
}
func (ci *Ci) gcNotUsePrepare__NOLOCK(toNumber int){
currentNumber:=len(ci.stmtCacheMap)
if currentNumber==0 || currentNumber<=toNumber{
return
}
stmtCacheList:=make([]*stmtCacheEntry, len(ci.stmtCacheMap))
i:=0
for _,entry:=range ci.stmtCacheMap{
stmtCacheList[i] = entry
i++
}
// 时间越早越放在后面。
kmgSort.InterfaceCallbackSortWithIndexLess(stmtCacheList,func(i int,j int)bool{
return stmtCacheList[i].lastUseTime>stmtCacheList[j].lastUseTime
})
for _,entry:=range stmtCacheList[toNumber:]{
entry.stmt.Close()
delete(ci.stmtCacheMap,entry.key)
}
}
|
Scan query results directly to a struct |
Doesn't work in the general case with NULL values. |
@cznic RE struct NULLs: Yes, we would need some type of strategy for handling that, but I could envision the actual struct scan code provided by a user settable interface, so rather then trying to cover all scanning cases, we provide one or two default ones, but then allows users to provide their own specific one as well. |
It seems like database/sql has had a lot of issues over the past few years around data races and goroutine leaks, more so than the other standard library packages. I worry that there's something fundamental in the design of the package as it's currently constructed that makes it difficult to write correct code. Maybe it's just that we're asking it to do a lot and support a lot of different use cases and backends. But I am wondering if the design or API's could be altered to make it easier to write a correct implementation. I have long wished for some type of unified, cross-database API for reporting the constraint failures that are features of most databases:
The fact that each database reports these differently makes it difficult for e.g. ORM's to properly handle constraint failures. Instead of just issuing an INSERT and waiting for the constraint failure, Rails starts a transaction, issues a SELECT, checks the number of rows, and throws if a row already exists. It would be cool if database/sql just returned you the uniqueness constraint on an INSERT failure instead of an err you had to cast to a I tried to address this a little bit with https://github.com/shyp/go-dberror, which attempted to wrap the existing database error in a more friendly message. Presumably that could be extended to handle more backends and more classes of errors. |
Without knowing any other context about the data model, there's only one safe strategy in this case: refuse to continue scanning and panic. |
@kevinburke Re database/sql reliability: yes, the pool and stmt code are too interconnected with everything else, it would be great to be able to have some of that code in internal sub-packages. |
Indeed. MS SQL Server and Oracle (probably others) don't benefit from a separate prepare step; you send the SQL text and parameters in the same message and will reuse recent cached query plans by matching similar SQL text. Even postgresql can have an anonymous prepared query that can be prepared and executed in a single round trip. MySQL now has the protocol X that probably supports similar. This is why I never personally use *sql.Stmt; it adds a real cost without offering a benefit (for me and the systems I use). *sql.Stmt also add significant complication to the internal database/sql code base. |
I tried doing it the regular way and with *sql.Stmt. When I benchmarked one of my main joins, *sql.Stmt ended up being 30% faster on MySQL. I'm curious as to what it would be like on this new protocol. |
@Azareal
From what I benched with same data and ssd disk and table size bigger than the machine memory.
You should benchmark all four ways if you want to increase your database speed. |
@kardianos MySQL's X Protocol does parameterized queries without preparing, Turns out whilst the protobufs for the protocol supports binding arrays to parameters, MySQL (5.7.20) doesn't like it. |
I've updated my previous comment with wishlist: #22697 (comment) . |
Ah, I should add that values that can be null in the database are a particular problem. Assuming you need to distinguish between null values and the empty value (which I do), either you have to have one version of your struct that talks to the database and has e.g. sql.NullTime, and then another one for serializing to JSON, or you use something like e.g. https://godoc.org/github.com/kevinburke/go-types#NullTime that has MarshalJSON support. (or Protobuf support, or whatever you're using...) |
@kevinburke Yeah, you would need to be able to encode null-state into a separate struct field or within the existing value space (null==-1). But I would envision having an user settable interface that would enable something like that. |
@kevinburke, Embedding sql.Null* types in a struct seems unnecessary. Just scan into a local sql.Null* type variable and map it manually into a struct instance. |
For DB fields that allow NULL, and mapping a record to a struct, couldn't you just require that field to be a pointer? e.g. |
I want to stop the bike shedding about scanning from NULLABLE columns now. There are many valid strategies each with different trade offs. I don't want conversation about different strategies for scanning from nullable columns. Here is what I do want: proposal kardianos/1// This is an example proposal of about what I'd like to see discussed here. Note, no mention of actual package sql
package mypackage
In other words, a specific NULL strategy doesn't matter any more. We define an interface for scanning, then users can plug in their own strategy, as they effectively do anyway today. |
@kardianos |
@bronze1man Are the arguments to fn a row of |
@kardianos When I need a no-type database interface, Just let me do the type transfer job.
The caller can also put less stuff in his select sql. If you want less data, you can just ask less data. |
@bronze1man That is a layer violation. If change is made, it would be allowing various row scanning strategies (into struct, into value, into table, etc) and it would not include decoding the value itself. I won't discuss that point further here (feel free to email me or discuss on golang-sql if you disagree and would like to show your point). |
I’ve noticed that there may be a possibility that I’m changing my SQL statements or scan variable data types depending on the actual database I’m using for the same application. Switching is something I'll be doing in the future. There seems to be a lot of implementation at the database/sql level above the driver, maybe we’re losing capability for specific database systems that would have been exposed in independent packages without the database/sql constraints. I don't have an example. For my first round with https://github.com/lib/pq I've had no problems. But maybe a driver client library instead of a generic library would make more sense for features and performance? |
In #21659 I said:
This pattern means the API surface is minimized while allowing for context-like additions in the future without API growth besides an exposed struct field or getter/setter pair. |
sql.DB is currently thread safe and I don’t believe that your proposed
design would be.
On Sat, Dec 16, 2017 at 15:03 Matt Juran ***@***.***> wrote:
In #21659 <#21659> I said:
My thought is change *DB to a struct of reference/pointer types and call
methods on an sql.DB instead, where the context is an optional field
assigned before making these calls to Exec, Ping, Prepare, Query, and
QueryRow in which there’s an if DB.context != nil { block with the context
handling behavior.
This pattern means the API surface is minimized while allowing for
context-like additions in the future without API growth besides an exposed
struct field or getter/setter pair.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#22697 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAOSI_OHb9QSmzBlgfGtW5eNPAKbaHePks5tBExFgaJpZM4Qcdex>
.
--
…--
Kevin Burke
925.271.7005 | kev.inburke.com
|
Change https://golang.org/cl/106659 mentions this issue: |
Change https://golang.org/cl/126416 mentions this issue: |
Please read this article. http://techblog.en.klab-blogs.com/archives/31093990.html In short, MaxLifetime is much better than MaxIdleConns about closing idle connection efficiently, and solves many other problems too. MaxIdleTime may be litter better than MaxLifetime about closing idle connections. But it doesn't solve many other problems MaxLifetime solves. And I'm not sure MaxLifetime is not enough for closing idle connections and MaxIdleTime is really needed. I hadn't seen any benchmark demonstrate MaxLifetime is not enough for closing idle connection in real world apps. If benefit of MaxIdleTime compared MaxLifetime is not enough, I don't think we shouldn't add it. Writing connection pool right is hard. Adding more option make it more hard. |
I have wroten a mysql connection pool with MaxOpenConns and MaxConnectionIdleTime and wait_timeout parameters. It is not so hard as long as those parameters is set at the time the db object is created, and never change after it created. With my own mysql connection pool , the avg connection number of my busiest mysql server reduce more than 30%.
The mysql connection pool in golang package |
Compared with what? MaxLifetime? Of course, MaxLifetime cause close&reconnect even for non-idle connections. It is good for load-balanced DB setup, and MySQL variables. There are overhead for reconnection, but I think the overhead should be negligible if MaxLifetime is configured to appropriate value. I don't know any rational for MaxIdleTime is required "in addition to" MaxLifetime. Why aren't you satisfied with MaxLifetime?
I agree about this. Prohibiting dynamic configuration will make connection pool much simple and robust, maintainable. |
Sorry, I do not compare MaxConnectionIdleTime to MaxLifetime , I just compare it to not set them both. I just "think" MaxConnectionIdleTime may be better than MaxLifetime, because MaxConnectionIdleTime create less connections and it can reduce some stmt create delay with stmt cache which only works on one connection, but i do not have evidence. Maybe they both have the same qps. |
When is Go 2 version of database/sql going to be released? |
@smithaitufe There is no schedule or plan. This issue exists to gather ideas to create a plan. |
I perfer go style rather rust style, go style is simple. |
Explore the idea changing the definition of driver.Value from interface{} to an union type.
Seems protobuf is going that direction too. https://github.com/protocolbuffers/protobuf-go/tree/master/reflect/protoreflect |
fetchALL() would be a critical feature, so that an entire table can be brought into memory for advanced processing. I needed this for some NLP project once. The default way of scanning each row means Cgo and reflect get called every row! this meant it took 8 seconds to read the database table into memory. Pythons fetchall took only 4 seconds. |
Proposal 1: pass context to all driver methods that might involve blocking network operations. Proposal 2: remove the background goroutine that opens and closes connections and do it in the request goroutine instead. I described this in more detail here: #38185 |
Is there any reason sql.DB, sql.Tx, sql.Stmt are structs instead of interfaces? In the current situation it is impossible to write a "middleware" like nap and use it in any existing code which uses standard database/sql (like sqlx). Also opencensus had to write a complicated driver to get telemetry, see ocsql. I understand that |
Since Go 1.13 errors can be wrapped. This can give some ideas to improve the error API. For instance, with the addition of For instance
A driver could then do something like:
Building on the above, we can also start to define other error types, signaling different levels of error in a standard way. For instance:
For example, if we look to If one is developer that builds a product that is supposed to be SQL idempotent, it can get messy very quickly. Drivers know best what a certain error signifies. And my idea would be to define these 3 (or perhaps more) levels of Error types. So that a driver can wrap its current errors into them and thus providing a unified error interface to consumers. Whom could then do:
On a general note, currently it is hard to predict what errors to expect and look out for. This is no unification across drivers. For instance, in case of canceled context. I really hope we can improve this by providing boiler plate error types and wrapping. |
I would like to have a connection pool like https://github.com/jackc/pgx/blob/master/pgxpool/pool.go in the standard library, in particular these features:
It would also help if the standard API was based on interfaces (as suggested above in #22697 (comment)) so that third parties could implement connection pool libraries that fit into the ecosystem. |
How about standardizing on a standard type for (nullable) unique identifier. |
Authentication callback would be nice to have to cover pools with token based authentication like AWS RDS. |
Since Nov 13, 2017... |
We're wondering what the story for database/sql might be for Go 2. We agree it's clearly important to be maintained (including ongoing maintenance including but definitely not limited to Google support).
Whether the package lives in std or x/foo is out of scope for this bug. (We'll wait until package management is figured out more first.)
But given what we know now, what would a fresh database/sql look like for Go 2?
Experience reports, docs, API proposals welcome.
@kardianos, could you write up your wishlist?
The text was updated successfully, but these errors were encountered: