为了账号安全,请及时绑定邮箱和手机立即绑定

使用 `database/sql` 查询比直接查询数据库要慢得多

使用 `database/sql` 查询比直接查询数据库要慢得多

Go
慕莱坞森 2022-07-11 16:48:52
我正在使用 golang 应用程序对本地 postgresql 实例运行相同的查询,并使用psql. 时间差别很大,我想知道为什么。使用解释/分析查询需要 1 毫秒,database/sql在 golang 中使用需要 24 毫秒。我在下面添加了我的代码片段。我意识到解释/分析可能不等同于直接查询数据库,并且可能还涉及一些网络延迟,但是差异仍然很大。为什么会有这样的差异?编辑:我已经用 10 多个查询的样本大小尝试了上述方法,但差异仍然存在。postgres=# \timingTiming is on.postgres=# select 1; ?column?----------        1(1 row)Time: 2.456 mspostgres=# 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 mspackage 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.981435mssecond query took 13.343845ms
查看完整描述

1 回答

?
holdtom

TA贡献1805条经验 获得超10个赞

注意#1:sql.DB不代表连接,而是代表连接池。


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


你的第 db.Query一个慢的原因是因为你从一个新的连接池开始,一个有 0 个空闲(但打开)连接的连接池,因此第一个 db.Query需要首先建立与服务器的新连接,然后才会它能够执行sql语句。


你的第二个 db.Query也很慢的原因是因为第一个创建的连接还 db.Query没有释放回池中,因此你的第二个 db.Query也需要先与服务器建立新的连接才能执行sql语句。


要释放与池的连接,您需要首先保留 的主要返回值,db.Query然后调用其Close上的方法。


要从至少有一个可用连接的池开始,请Ping在初始化池后立即调用。


例子:


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

关于准备好的陈述的说明:

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


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


见4.2。值表达式,它说:


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


位置参数引用,在函数定义或准备好的语句的主体中

......

位置参数也说:


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


postgres 的消息流协议如何指定simple queries和extended queries


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


最后,在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


    ...


查看完整回答
反对 回复 2022-07-11
  • 1 回答
  • 0 关注
  • 173 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信