Golang slow scan() 多行

Golang slow scan() for multiple rows

提问人:Weilson Wonder 提问时间:5/26/2017 最后编辑:Weilson Wonder 更新时间:6/19/2021 访问量:4321

问:

我在 Golang 中运行一个查询,我从我的 Postgresql 数据库中选择多行。

我在查询中使用以下导入

"database/sql"
"github.com/lib/pq"

我已经缩小到我的循环,以便将结果扫描到我的结构中。

// Returns about 400 rows
rows, err = db.Query('SELECT * FROM infrastructure')
if err != nil {
    return nil, err
}

var arrOfInfra []model.Infrastructure
for rows.Next() {
    obj, ptrs := model.InfrastructureInit()
    rows.Scan(ptrs...)
    arrOfInfra = append(arrOfInfra, *obj)
}
rows.Close()

上面的代码运行大约需要 8 秒,虽然查询速度很快,但循环成行。Next() 需要整整 8 秒才能完成。

有什么想法吗?是我做错了什么,还是有更好的方法?

我的数据库配置

// host, port, dbname, user, password masked for obvious reasons
db, err := sql.Open("postgres", "host=... port=... dbname=... user=... password=... sslmode=require")
if err != nil {
    panic(err)
}

// I have tried using the default, or setting to high number (100), but it doesn't seem to help with my situation
db.SetMaxIdleConns(1)
db.SetMaxOpenConns(1)

更新1:

我将 print 语句放在 for 循环中。以下是我更新的片段

for rows.Next() {
    obj, ptrs := model.InfrastructureInit()
    rows.Scan(ptrs...)
    arrOfInfra = append(arrOfInfra, *obj)
    fmt.Println("Len: " + fmt.Sprint(len(arrOfInfra)))
    fmt.Println(obj)
}

我注意到,在这个循环中,它实际上会中途暂停,并在短暂休息后继续。它看起来像这样:

Len: 221
Len: 222
Len: 223
Len: 224
<a short pause about 1 second, then prints Len: 225 and continues>
Len: 226
Len: 227
...
..
.

稍后,在另一行计数时,它会再次发生,并在几百条记录后再次发生。


更新2:

下面是我的 InfrastructureInit() 方法的片段

func InfrastructureInit() (*Infrastructure, []interface{}) {
    irf := new(Infrastructure)
    var ptrs []interface{}
    ptrs = append(ptrs,
        &irf.Base.ID,
        &irf.Base.CreatedAt,
        &irf.Base.UpdatedAt,
        &irf.ListingID,
        &irf.AddressID,
        &irf.Type,
        &irf.Name,
        &irf.Description,
        &irf.Details,
        &irf.TravellingFor,
    )
    return irf, ptrs
}

我不完全确定是什么导致了这种缓慢,但我目前在我的服务器上放置了一个快速补丁,以使用 redis 数据库并预缓存我的基础设施,将其保存为字符串。现在似乎还可以,但我现在必须同时维护 redis 和我的 postgres。

我仍然对这种奇怪的行为感到困惑,但我并不完全是行的。Next() 工作 - 每次我调用行时,它都会对数据库进行查询。Next()?

PostgreSQL 循环 go pq

评论

2赞 Gavin 5/26/2017
model.InfrastructureInit()- 这条线在做什么?您扫描了多少列?也可能来自~400行的扫描。真的有必要收回那么多记录吗?
0赞 Weilson Wonder 5/26/2017
@gavin init 语句只是创建一个新模型,并创建一个数组,其中包含指向新对象变量的指针。准备 Scan() 的便捷方法。每条记录有 12 列。记录的数量实际上各不相同,但平均为 200-400 条记录。
1赞 jrefior 5/26/2017
如果您想要更多的性能详细程度,则可以使用 time pkg 来查看导致延迟的确切调用。鉴于您看到随着切片的增长偶尔会出现延迟,似乎可能是底层数组的大小调整 - 请参阅附加:一个示例,但我认为它不会那么慢。
1赞 jrefior 5/26/2017
如果需要更多帮助,可能需要发布函数的代码。另外,表中有多少列?运行查询并在 psql 提示符下获取输出需要多长时间?model.InfrastructureInit()infrastructure
1赞 kpimov 5/27/2017
在 for 循环中运行 goroutine 会有所帮助吗?您可以将函数范围之外的任何变量作为参数传递给 goroutine,并确保在使用通道调用之前所有变量都已完成运行。如果我太天真了,请原谅我rows.Close()

答:

0赞 ThanhHH 6/20/2018 #1

你怎么想像这样做?

defer rows.Close()

var arrOfInfra []*Infrastructure
for rows.Next() {
    irf := &Infrastructure{}

    err = rows.Scan(
        &irf.Base.ID,
        &irf.Base.CreatedAt,
        &irf.Base.UpdatedAt,
        &irf.ListingID,
        &irf.AddressID,
        &irf.Type,
        &irf.Name,
        &irf.Description,
        &irf.Details,
        &irf.TravellingFor,
    ) 

    if err == nil {
        arrOfInfra = append(arrOfInfra, irf)
    }
}

希望这有帮助。

0赞 draganstankovic 4/13/2021 #2

我自己走了一条奇怪的道路,同时巩固了我对工作方式以及可能影响绩效的因素的理解,所以考虑在这里为后代分享这个(尽管很久以前就问过这个问题)。rows.Next()

相关:

我仍然对这种奇怪的行为感到困惑,但我不完全是怎么做到的 行。Next() 工作 - 每次我都会对数据库进行查询吗 调用行。Next()?

它不会进行“查询”,但它会在每次迭代时通过驱动程序从数据库读取(传输)数据,这意味着它可能会受到例如网络性能不佳的影响。例如,如果您的数据库不是运行 Go 代码的本地位置,则尤其如此。 确认网络性能是否存在问题的一种方法是在数据库所在的同一台计算机上运行 go 应用程序(如果可能)。

假设在上述代码中扫描的列不是非常大或具有自定义转换 - 读取 ~400 行最多应花费 100 毫秒(在本地设置中)。

例如 - 我有一个案例,我需要读取大约 100k 行,每行大约 300B,这需要 ~4 秒(本地设置)。