使用database/sql查询比直接查询数据库慢很多

发布于 2025-01-12 10:29:12 字数 1831 浏览 0 评论 0原文

我使用 golang 应用程序并使用 psql 对本地 postgresql 实例运行相同的查询。时间差异很大,我想知道为什么。使用explain/analyze查询花费了1ms,在golang中使用database/sql,花费了24ms。我在下面添加了我的代码片段。我意识到解释/分析可能并不等同于直接查询数据库,并且可能还涉及一些网络延迟,但是差异仍然很大。为什么会出现这样的差异呢?

编辑:我已经尝试过使用 10 个以上查询的样本大小进行上述操作,并且差异仍然存在。

postgres=# \timing
Timing is on.
postgres=# select 1;
 ?column?
----------
        1
(1 row)

Time: 2.456 ms
postgres=# explain analyze select 1;
                                     QUERY PLAN
------------------------------------------------------------------------------------
 Result  (cost=0.00..0.01 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=1)
 Planning Time: 0.017 ms
 Execution Time: 0.012 ms
(3 rows)

Time: 3.748 ms
package main

    import (
        "database/sql"
        "fmt"
        _ "github.com/lib/pq"
        "time"
    )

    func main() {
        // setup database connection
        db, err := sql.Open("postgres", "host='localhost' port=5432 user='postgres' password='' dbname='postgres' sslmode=disable")
        if err != nil {
            panic(err)
        }

        // query database
        firstQueryStart := time.Now()
        _, err = db.Query("select 1;")
        firstQueryEnd := time.Now()
        if err != nil {
            panic(err)
        }
        fmt.Println(fmt.Sprintf("first query took %s", firstQueryEnd.Sub(firstQueryStart).String()))

        //run the same query a second time and measure the timing
        secondQueryStart := time.Now()
        _, err = db.Query("select 1;")
        secondQueryEnd := time.Now()
        if err != nil {
            panic(err)
        }
        fmt.Println(fmt.Sprintf("second query took %s", secondQueryEnd.Sub(secondQueryStart).String()))
    }
first query took 13.981435ms
second query took 13.343845ms

I'm running the same query against a local postgresql instance using a golang application, and using psql. The timings differ greatly and I'm wondering why. Using explain/analyze the query took 1ms, using database/sql in golang, it took 24ms. I've added my code snippets below. I realize that explain/analyze may not be equivalent to querying the database directly, and there may be some network latency involved as well, however the discrepancy is still significant. Why is there such a discrepancy?

edit: I've tried the above with a sample size of 10+ queries, and the discrepancy still holds true.

postgres=# \timing
Timing is on.
postgres=# select 1;
 ?column?
----------
        1
(1 row)

Time: 2.456 ms
postgres=# explain analyze select 1;
                                     QUERY PLAN
------------------------------------------------------------------------------------
 Result  (cost=0.00..0.01 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=1)
 Planning Time: 0.017 ms
 Execution Time: 0.012 ms
(3 rows)

Time: 3.748 ms
package main

    import (
        "database/sql"
        "fmt"
        _ "github.com/lib/pq"
        "time"
    )

    func main() {
        // setup database connection
        db, err := sql.Open("postgres", "host='localhost' port=5432 user='postgres' password='' dbname='postgres' sslmode=disable")
        if err != nil {
            panic(err)
        }

        // query database
        firstQueryStart := time.Now()
        _, err = db.Query("select 1;")
        firstQueryEnd := time.Now()
        if err != nil {
            panic(err)
        }
        fmt.Println(fmt.Sprintf("first query took %s", firstQueryEnd.Sub(firstQueryStart).String()))

        //run the same query a second time and measure the timing
        secondQueryStart := time.Now()
        _, err = db.Query("select 1;")
        secondQueryEnd := time.Now()
        if err != nil {
            panic(err)
        }
        fmt.Println(fmt.Sprintf("second query took %s", secondQueryEnd.Sub(secondQueryStart).String()))
    }
first query took 13.981435ms
second query took 13.343845ms

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

樱花坊 2025-01-19 10:29:12

注意#1sql.DB< /a> 不代表一个连接,而是代表一个连接。

注意#2sql.Open< /a> 初始化池,但它不必实际打开连接,它只允许验证 dsn 输入,然后连接的打开将由池延迟处理。

您的 1st db.Query 速度慢的原因是您从一个新的连接池开始,该连接池有 0 个空闲(但打开)的连接,因此1st db.Query 需要首先与服务器建立新的连接,然后才能执行 sql 语句。

2nd db.Query 速度也很慢的原因是 1st db.Query 创建的连接已尚未释放回池中,因此您的 2nd db.Query 还需要首先与服务器建立新连接,然后才能执行 sql 语句。


要将连接释放回池,您需要首先保留 db.Query 的主要返回值,然后调用 Close 方法就可以了。

要从至少有一个可用连接的池开始,请调用 Ping< /code>初始化池后。


示例:

func main() {
    // setup database connection
    db, err := sql.Open("postgres", "postgres:///?sslmode=disable")
    if err != nil {
        panic(err)
    } else if err := db.Ping(); err != nil {
        panic(err)
    }

    for i := 0; i < 5; i++ {
        // query database
        firstQueryStart := time.Now()
        rows, err := db.Query("select 1;")
        firstQueryEnd := time.Now()
        if err != nil {
            panic(err)
        }

        // put the connection back to the pool so
        // that it can be reused by next iteration
        rows.Close()

        fmt.Println(fmt.Sprintf("query #%d took %s", i, firstQueryEnd.Sub(firstQueryStart).String()))
    }
}

我的机器上的时间(没有 db.Ping 只有 #0 很慢)

query #0 took 6.312676ms
query #1 took 102.88µs
query #2 took 66.702µs
query #3 took 64.694µs
query #4 took 208.016µs

我的机器上的时间(有 db.Ping #0 比没有时快很多)

query #0 took 284.846µs
query #1 took 78.349µs
query #2 took 76.518µs
query #3 took 81.733µs
query #4 took 103.862µs

A关于准备好的语句的注意事项:

如果您正在执行一个不带参数的简单查询,例如db.Query("select 1 where true"),那么您实际上只是在执行一个简单的查询。

但是,如果您正在使用参数执行查询,例如db.Query("select 1 where $1", true),那么实际上您正在创建并执行准备好的语句。

请参阅4.2。值表达式,它说:

值表达式是以下之一:...

  • 位置参数引用,在函数定义或预备语句的主体中
    ...

还有 位置参数 说:

位置参数引用用于指示一个值
从外部提供给 SQL 语句。 参数在SQL中使用
函数定义和准备好的查询
。一些客户端库
还支持与 SQL 命令分开指定数据值
字符串,在这种情况下参数用于引用外线
数据值。

postgres 的消息流协议如何指定 简单查询扩展查询

扩展查询协议打破了上述简单的
查询协议分为多个步骤。准备步骤的结果
可以多次重复使用以提高效率。此外,
还有其他功能可用,例如
将数据值作为单独的参数提供,而不必
将它们直接插入到查询字符串中。

最后,在 lib/pq 驱动程序:

    ...

    // Check to see if we can use the "simpleQuery" interface, which is
    // *much* faster than going through prepare/exec
    if len(args) == 0 {
        return cn.simpleQuery(query)
    }

    if cn.binaryParameters {
        cn.sendBinaryModeQuery(query, args)

        cn.readParseResponse()
        cn.readBindResponse()
        rows := &rows{cn: cn}
        rows.rowsHeader = cn.readPortalDescribeResponse()
        cn.postExecuteWorkaround()
        return rows, nil
    }
    st := cn.prepareTo(query, "")
    st.exec(args)
    return &rows{
        cn:         cn,
        rowsHeader: st.rowsHeader,
    }, nil

    ...

Note #1: sql.DB does not represent a connection, instead it represents a pool of connections.

Note #2: sql.Open initializes the pool but it does not have to actually open a connection, it's allowed to only validate the dsn input and then the opening of the connections will be handled lazily by the pool.

The reason your 1st db.Query is slow is because you're starting off with a fresh connection pool, one that has 0 idle (but open) connections, and therefore the 1st db.Query will need to first establish a new connection to the server and only after that will it be able to execute the sql statement.

The reason your 2nd db.Query is also slow is because the connection created by the 1st db.Query has not been released back to the pool, and therefore your 2nd db.Query will also need to first establish a new connection to the server before it can execute the sql statement.


To release a connection back to the pool you need to first retain the primary return value of db.Query and then invoke the Close method on it.

To start off with a pool that has at least one available connection, call Ping right after initializing the pool.


Example:

func main() {
    // setup database connection
    db, err := sql.Open("postgres", "postgres:///?sslmode=disable")
    if err != nil {
        panic(err)
    } else if err := db.Ping(); err != nil {
        panic(err)
    }

    for i := 0; i < 5; i++ {
        // query database
        firstQueryStart := time.Now()
        rows, err := db.Query("select 1;")
        firstQueryEnd := time.Now()
        if err != nil {
            panic(err)
        }

        // put the connection back to the pool so
        // that it can be reused by next iteration
        rows.Close()

        fmt.Println(fmt.Sprintf("query #%d took %s", i, firstQueryEnd.Sub(firstQueryStart).String()))
    }
}

Times on my machine (without db.Ping only #0 is slow)

query #0 took 6.312676ms
query #1 took 102.88µs
query #2 took 66.702µs
query #3 took 64.694µs
query #4 took 208.016µs

Times on my machine (with db.Ping #0 is a lot faster than without)

query #0 took 284.846µs
query #1 took 78.349µs
query #2 took 76.518µs
query #3 took 81.733µs
query #4 took 103.862µs

A note on prepared statements:

If you're executing a simple query with no arguments e.g. db.Query("select 1 where true") then you really are executing just a simple query.

If, however, you're executing a query with arguments e.g. db.Query("select 1 where $1", true) then, in actuality, you are creating and executing a prepared statement.

See 4.2. Value Expressions, it says:

A value expression is one of the following: ...

  • A positional parameter reference, in the body of a function definition or prepared statement
    ...

Also Positional Parameters says:

A positional parameter reference is used to indicate a value that is
supplied externally to an SQL statement. Parameters are used in SQL
function definitions and in prepared queries
. Some client libraries
also support specifying data values separately from the SQL command
string, in which case parameters are used to refer to the out-of-line
data values.

How the postgres' message-flow protocol specifies simple queries and extended queries

The extended query protocol breaks down the above-described simple
query protocol into multiple steps. The results of preparatory steps
can be re-used multiple times for improved efficiency. Furthermore,
additional features are available, such as the possibility of
supplying data values as separate parameters instead of having to
insert them directly into a query string.

And finally, under the covers of the lib/pq driver:

    ...

    // Check to see if we can use the "simpleQuery" interface, which is
    // *much* faster than going through prepare/exec
    if len(args) == 0 {
        return cn.simpleQuery(query)
    }

    if cn.binaryParameters {
        cn.sendBinaryModeQuery(query, args)

        cn.readParseResponse()
        cn.readBindResponse()
        rows := &rows{cn: cn}
        rows.rowsHeader = cn.readPortalDescribeResponse()
        cn.postExecuteWorkaround()
        return rows, nil
    }
    st := cn.prepareTo(query, "")
    st.exec(args)
    return &rows{
        cn:         cn,
        rowsHeader: st.rowsHeader,
    }, nil

    ...
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文