Go database thread pool operation problem

        Recently, I encountered a hidden problem when using the Go language to operate the database. This kind of problem is difficult to locate. Finally, after multiple tests, attempts, and guesses, the problem was finally confirmed. The problem is as follows:

        At that time, we used the go gin framework for background services and mysql as the background database. Then we set up the go database operation thread pool during initialization. This thread pool is the thread pool that comes with the go language. We only need to set some parameters. You can implement a database thread pool. Then the final test revealed that the reason was unknown. After a period of testing, the service was stuck and all accesses could not be accessed normally. At first, it was a problem with the routing framework. After searching for a long time, I could not find the problem. Later I guessed that it was Regarding the problem of illegal character operations, after various tests, it was finally discovered that there was a system function in the function function that was useless, causing the thread pool to reach the upper limit and all accesses to be stuck. The specific causes and solutions are as follows:

        We set up the thread pool like this:

const (
	dbMaxLiftTime  = 2 * time.Hour
	dbMaxIdleConns = 30
	dbMaxOpenConns = 100
)

G_dbpap, err := sql.Open("mysql", mysqlurl)
if err != nil {
	golog.Error(err)
	return err
}
G_dbpap.SetMaxIdleConns(dbMaxIdleConns)   //连接池的空闲数大小
G_dbpap.SetMaxOpenConns(dbMaxOpenConns)   //设置最大打开连接数
G_dbpap.SetConnMaxLifetime(dbMaxLiftTime) //设置连接的最大生存时间

In the above code, we opened a G_dbpap global operation variable of the mysql database, and then we set the thread pool. The maximum number of open access connections is 100. This setting is the key to my problem. The other two are literally I can understand the meaning, remember this 100

Then there is an API interface function in my code written like this:

sql_cmd := `DELETE FROM TABLEDATA`
_,err := G_dbpap.Query(sql_cmd)
if err != nil{
    return err
}

Pay attention to this G_dbpap.Query. I looked at its function source code and its first return value is the Rows pointer. The source code is attached below.

func (db *DB) Query(query string, args ...any) (*Rows, error) {
	return db.QueryContext(context.Background(), query, args...)
}

I have read a lot of information before and know that the rows must be closed manually when the return value is used up. However, it is convenient for me because it is a delete statement. I do not need to process the return value, so I use underscores to skip the return value. In fact, it is There is a problem. If this rows is not closed, the request thread this time will always be occupied and will not be released. Therefore, if the interface is tested more than 100 times, the occupation of this thread will reach 100 times. Then the maximum number of connections in the thread pool set above is 100 times, so after 100 times of interface testing, all access requests using the G_dbpap handle to operate the database will be stuck. Because the maximum thread pool size is set, after reaching the maximum, the access request will wait until the previous thread is released before responding.

I was really excited at that time. The cause of the problem that had been tortured for a long time was finally located, and then how to solve it. There are two methods:

1. For database operation requests like the one above, since there is no need to return a value, all you need to do is replace Query with Exec to execute the database, as shown below

sql_cmd := `DELETE FROM TABLEDATA`
_,err := G_dbpap.Exec(sql_cmd)
if err != nil{
    return err
}

2. If unlike the above, you need to process the return value of the database operation, such as select, you need to close it after using the rows, as shown below

sql_cmd := `SELECT * FROM TABLEDATA`
rows,err := G_dbpap.Query(sql_cmd)
if err != nil{
    return err
}

for rows.Next() {
    //获取rows的返回值操作

}

rows.Close()

This way there won't be any problems if you finish the operation and then Close it.

Note: Judging from the above situation, the cause is actually my own problem. The operation function of deleting the database should not use the Query function, but in fact I am also very grateful that I used it wrong. If I don’t use it wrong, I will not have a deep understanding of its use. Query without Close will lead to serious consequences. Also, some people may say that if the database thread pool operation does not set the limit of 100, this problem should not occur. In fact, if you think about it carefully, if the limit of 100 is not set before (it seems that the default is unlimited ), then the final problem may be catastrophic. It is assumed that more and more threads are not released, and eventually the system crashes. By then, it may be even more difficult to locate the problem.

The above are the problems and solutions I encountered this time, recorded here, I hope it will be helpful to future friends.

Guess you like

Origin blog.csdn.net/u013896064/article/details/127902403