Golang慢扫描()多行

Posted

技术标签:

【中文标题】Golang慢扫描()多行【英文标题】:Golang slow scan() for multiple rows 【发布时间】:2017-10-26 09:29:22 【问题描述】:

我在 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 秒,虽然查询速度很快,但 rows.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:

我将打印语句放在 for 循环中。下面是我更新的sn-p

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() 方法的 sn-p

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。

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

【问题讨论】:

model.InfrastructureInit() - 这条线在做什么?您要扫描多少列?也可能来自扫描约 400 行。真的有必要拉回那么多记录吗? @gavin init 语句只是创建一个新模型,并创建一个包含指向新对象变量的指针的数组。一种准备 Scan() 的便捷方法。每条记录有 12 列。并且记录的数量实际上是不同的,但平均是 200-400 条记录。 如果你想要更多的性能细节,你可以使用the time pkg 来查看究竟是哪个调用导致了延迟。鉴于您看到切片增长时偶尔会出现延迟,这似乎可能是底层数组的大小调整——请参阅Append: An example,但我认为它不会那么慢。 如果您需要更多帮助,您可能需要发布 model.InfrastructureInit() 函数的代码。另外,infrastructure 表中有多少列?以及在 psql 提示符下运行查询并获得输出需要多长时间? 在 for 循环中运行 goroutine 会有帮助吗?您可以将函数范围之外的任何变量作为参数传递给 goroutine,并确保在使用通道调用 rows.Close() 之前所有变量都已完成运行。如果我太天真了,请原谅我 【参考方案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)
    

希望对您有所帮助。

【讨论】:

【参考方案2】:

在巩固对rows.Next() 工作原理以及可能影响性能的因素的理解的同时,我自己也走了一条奇怪的道路,因此考虑在此分享此内容以供后人参考(尽管这个问题是很久以前提出的)。

相关:

我仍然对这种奇怪的行为感到困惑,但我并不完全了解 rows.Next() 工作 - 每次我都会查询数据库吗 调用 rows.Next()?

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

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

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

【讨论】:

以上是关于Golang慢扫描()多行的主要内容,如果未能解决你的问题,请参考以下文章

golang高性能端口扫描

手撸golang 仿spring ioc/aop 之5 如何扫描

Golang fasthttp请求非常慢

golang 端口扫描工具

golang 扫描文本文件Go

golang 去言语の扫描仪の使い方