database/sql query 超时设置

参考文章:Query

问题描述

接上文,mysql的同步访问问题解决后,继续压测,发现访问mysql的耗时逐渐增大,影响模块整体处理能力。我设置了readTimeout,没有任何反应,请求耗时还是会超过readTimeout所设置的值。因为:

  • readTimeout只能限制连接数据读取时间,如果程序发生在获取连接前等待时间过长,无法通过这个参数设置,且readTimeout*3才是真正的读取超时时间,因为会重试3次。
  • 当设置的MaxOpenConn消耗殆尽时,再次进入的请求会夯死在database/sql内部,直到有空闲连接可操作。

显然Query方法是无法满足我们的需求。

解决方法

翻源码,发现了一个新方法QueryContext,通过传入context,该context有超时时间,那么就达到了该查询请求有了超时设置,nice。

// conn returns a newly-opened or cached *driverConn.
func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error) {
    db.mu.Lock()
    if db.closed {
        db.mu.Unlock()
        return nil, errDBClosed
    }
    // Check if the context is expired.
    select {
    default:
    case <-ctx.Done():
        db.mu.Unlock()
        return nil, ctx.Err()
    }
    lifetime := db.maxLifetime

    // Prefer a free connection, if possible.
    numFree := len(db.freeConn)
    if strategy == cachedOrNewConn && numFree > 0 {
        conn := db.freeConn[0]
        copy(db.freeConn, db.freeConn[1:])
        db.freeConn = db.freeConn[:numFree-1]
        conn.inUse = true
        db.mu.Unlock()
        if conn.expired(lifetime) {
            conn.Close()
            return nil, driver.ErrBadConn
        }
        // Lock around reading lastErr to ensure the session resetter finished.
        conn.Lock()
        err := conn.lastErr
        conn.Unlock()
        if err == driver.ErrBadConn {
            conn.Close()
            return nil, driver.ErrBadConn
        }
        return conn, nil
    }

    // Out of free connections or we were asked not to use one. If we're not
    // allowed to open any more connections, make a request and wait.
    if db.maxOpen > 0 && db.numOpen >= db.maxOpen {
        // Make the connRequest channel. It's buffered so that the
        // connectionOpener doesn't block while waiting for the req to be read.
        req := make(chan connRequest, 1)
        reqKey := db.nextRequestKeyLocked()
        db.connRequests[reqKey] = req
        db.waitCount++
        db.mu.Unlock()

        waitStart := time.Now()

        // Timeout the connection request with the context.
        select {
        case <-ctx.Done():
            // Remove the connection request and ensure no value has been sent
            // on it after removing.
            db.mu.Lock()
            delete(db.connRequests, reqKey)
            db.mu.Unlock()

            atomic.AddInt64(&db.waitDuration, int64(time.Since(waitStart)))

            select {
            default:
            case ret, ok := <-req:
                if ok && ret.conn != nil {
                    db.putConn(ret.conn, ret.err, false)
                }
            }
            return nil, ctx.Err()
        case ret, ok := <-req:
            atomic.AddInt64(&db.waitDuration, int64(time.Since(waitStart)))

            if !ok {
                return nil, errDBClosed
            }
            if ret.err == nil && ret.conn.expired(lifetime) {
                ret.conn.Close()
                return nil, driver.ErrBadConn
            }
            if ret.conn == nil {
                return nil, ret.err
            }
            // Lock around reading lastErr to ensure the session resetter finished.
            ret.conn.Lock()
            err := ret.conn.lastErr
            ret.conn.Unlock()
            if err == driver.ErrBadConn {
                ret.conn.Close()
                return nil, driver.ErrBadConn
            }
            return ret.conn, ret.err
        }
    }

    db.numOpen++ // optimistically
    db.mu.Unlock()
    ci, err := db.connector.Connect(ctx)
    if err != nil {
        db.mu.Lock()
        db.numOpen-- // correct for earlier optimism
        db.maybeOpenNewConnections()
        db.mu.Unlock()
        return nil, err
    }
    db.mu.Lock()
    dc := &driverConn{
        db:        db,
        createdAt: nowFunc(),
        ci:        ci,
        inUse:     true,
    }
    db.addDepLocked(dc, dc)
    db.mu.Unlock()
    return dc, nil
}

使用该访问进行query查询

sqlCtx, _ := context.WithTimeout(context.Background(), time.Duration(p.queryTimeout)*time.Millisecond)
     //defer cancel()
 rows, err := p.orderClient.QueryContext(sqlCtx, sqlStr)

压测数据

时间单位都是ms

thread_nummysql_query_timeoutmax_conn_nummax_idle_connqpsmysql_err_nummysql_err_max_timemysql_succ_nummysql_succ_max_time
1000100005040001015006280317
100010005050350013510205370833
100020005050330039202050401172
400200050502800320154620640
400100050502800910204646484

数据显示SetMaxOpenConns为0的情况下,设置context是没有效果的。但是>0后,该timeout设置有效,原因也很好理解,因为maxOpenConn=0时,请求不会等待空闲连接。所以也不会有ctx.Done()逻辑存在。

可以通过使用JDBC API中的Statement接口的getQueryTimeout()方法来获取JDBC查询执行所在的SQL进程ID。查询超时时间是指JDBC查询在等待响应时的最长时间。在等待超时时,查询将自动终止,并且对应的SQL进程ID将被释放。以下是一个示例代码: ```java import java.sql.*; public class SQLProcessID { public static void main(String[] args) { String url = "jdbc:sqlserver://localhost:1433;databaseName=AdventureWorks"; String user = "myUserName"; String password = "myPassword"; String query = "SELECT * FROM Sales.SalesOrderHeader"; try (Connection conn = DriverManager.getConnection(url, user, password); Statement stmt = conn.createStatement()) { // 设置查询超时时间 stmt.setQueryTimeout(30); // 设置30秒超时 // 执行查询 ResultSet rs = stmt.executeQuery(query); ResultSetMetaData md = rs.getMetaData(); // 获取SQL进程ID int processId = ((SQLServerStatement) stmt).getProcessID(); System.out.println("SQL进程ID:" + processId); // 处理结果集 while (rs.next()) { for (int i = 1; i <= md.getColumnCount(); i++) { System.out.print(rs.getString(i) + "\t"); } System.out.println(); } rs.close(); } catch (SQLException e) { System.out.println("Error message: " + e.getMessage()); System.out.println("Error code: " + e.getErrorCode()); System.out.println("SQL state: " + e.getSQLState()); } } } ``` 在上面的示例中,我们通过使用JDBC API连接到SQL Server,并使用Statement接口的setQueryTimeout()方法设置查询超时时间。然后,我们执行查询,并从结果集中获取ResultSetMetaData。最后,我们使用SQLServerStatement接口的getProcessID()方法获取查询执行所在的SQL进程ID。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值