首页 > 解决方案 > 如何让 rows.Scan 更有效率?

问题描述

我有一个用 Go 编写的 Web 应用程序,这个应用程序对 Postgres 数据库进行查询。当我取回我的记录时,我正在使用 rows.Next 遍历记录,并使用 rows.Scan 将每一行扫描到一个结构。

我怎样才能使整个过程更快?

我认为这个程序效率不高,因为随着数据库的每条新记录,扫描所有记录的时间也会增加。我考虑过使用 goroutine,但我担心可能两个 goroutine 会扫描相同的数据。我可以通过使用互斥锁来防止这种情况吗?但是,如果我们通过使用互斥锁来阻止其他 goroutine 访问数据,那么使用并发有什么意义呢?

这是我计划改进的代码:

func GetUsers() ([]publicUser, error) {
    query := `select user_id, first_name, last_name, registered_at from users;`
    rows, err := db.Query(query)
    if err != nil {
        return nil, err
    }

    var us []publicUser

    for rows.Next() {
        var u publicUser
        if err = rows.Scan(&u.UserId, &u.FirstName, &u.LastName, &u.RegisteredAt); err != nil {
            log.Println(err, "GetUsers")
            return nil, err
        }
        us = append(us, u)
    }
    if err := rows.Err(); err != nil {
        log.Println(err, "GetUsers2")
        return nil, err
    }
    return us, nil
}

我应该像这样启动一个新的 goroutine 吗?

func GetUsers() ([]publicUser, error) {
    query := `select user_id, first_name, last_name, registered_at from users;`
    rows, err := db.Query(query)
    if err != nil {
        return nil, err
    }

    var us []publicUser

    for rows.Next() {
        go func() {
             var u publicUser
             if err = rows.Scan(&u.UserId, &u.FirstName, &u.LastName, &u.RegisteredAt); err != nil 
             {
                 log.Println(err, "GetUsers")
                 return nil, err
             }
             us = append(us, u)
       }()
    }
    if err := rows.Err(); err != nil {
        log.Println(err, "GetUsers2")
        return nil, err
    }
    return us, nil
}

标签: sqldatabasegoconcurrency

解决方案


人们通常通过使用分页来解决这个问题。基本思想是客户端一次可以请求n多条记录,并且每个后续请求都返回n偏移量n*的记录i + 1i您当前正在进行的循环迭代在哪里)。

例如,如果您通过前端 GUI 显示此用户列表,您可能希望有一个表一次显示 50 个结果,而不是整个数据库中的所有用户,因为这会减慢您的前端速度。每当用户点击查看下一页时,您将向服务器发出一个新请求,请求 50 个结果,偏移 50(因为我们现在请求第 2 页)。这种方法解决了这种情况下的真正瓶颈,即您的数据库,而不是您的服务器代码。有关更多信息,请参阅此资源

可列出的集合应该支持分页,即使结果通常很小。

另请参阅此 SO 答案以获取对 SQL 查询结果进行分页的示例。


推荐阅读