使用Golang协程执行多查询的实现方法

使用Golang协程执行多查询的实现方法 如何使用 Go 协程执行多个 SQL 查询

8 回复

如今,所有的笔记本电脑和服务器都拥有核心和线程。

如果我们在不同的线程上运行独立的查询,速度会更快。这是我的思考过程。

更多关于使用Golang协程执行多查询的实现方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Goroutine 1 从 emp 表中选择所有数据 Goroutine 2 从 dep 表中选择所有数据

除非 Goroutine 1 和 2 都执行完毕,否则应该在此处停止。

Goroutine 3 我将处理数据

这似乎是一个关于Go语言并发性的相当宽泛的问题。请参阅Go语言之旅的并发章节:

https://go.dev/tour/concurrency/1

以及Go by Example中的goroutines示例:

https://gobyexample.com/goroutines

可以说,正确的答案(正如 @Dean_Davidson 所说)确实是:

  • 使用主例程

但同时也要:

  • 使用适当的 SQL 查询 进行 数据处理(仅使用 Go 进行最终步骤)

毕竟,SQL 基本上 就是 一种特定领域的数据处理语言。

此外,数据库系统有它们自己的并发机制(事务、ACID 等),因此你可能不需要为此依赖 Go 的机制。

事情并非那么简单。

  1. 瓶颈(例如,可能你的数据库正在等待前一个请求完成,那么你的 goroutine 就只是在等待)
  2. 竞态条件:每当多个线程访问相同的数据时,就存在损坏数据的巨大风险(一个正确的程序胜过一个快速错误的程序)

Go 语言凭借其独特的通道(以及 WaitGroup 等其他特性)使得避免竞态条件比大多数其他语言更容易,但这并不意味着你应该总是编写多线程程序。它仍然比单线程程序更复杂,因为你必须注意线程/goroutine 之间的同步问题。

让多个 goroutine 运行任务并同步它们是一个相对容易解决的问题。既然你提到了阻塞直到两个任务完成,可以查看一下 sync.WaitGroup

sync package - sync - Go Packages

sync package - sync - Go Packages

sync 包提供了基本的同步原语,例如互斥锁。

从文档中:

除了 Once 和 WaitGroup 类型,大部分原语是供底层库例程使用的。更高级别的同步最好通过通道和通信来完成。

因此,为此目的,请参考我上面链接的 Go 并发之旅部分,其中演示了通道的使用。话虽如此,我倾向于认为你过早地进行了优化。如果我是你,我会先尝试运行查询,只有在你有具体问题需要解决时,才开始考虑并发。

这取决于您想使用哪个数据库。

这里有一个教程:http://go-database-sql.org/ 还有这个教程:https://go.dev/doc/tutorial/database-access 这两个教程都使用了这个 mysql 驱动:https://github.com/go-sql-driver/mysql

您也可以尝试使用 sqlite。 那么您需要这个驱动:https://github.com/mattn/go-sqlite3,但它有一个问题,即依赖于 C 编译器(cgo),因此它并不总是在每个系统上都能编译(Linux 可能可以,但 macOS 或 Windows 会困难一些)。

另一个流行的数据库是 postgresql,那么我推荐这个驱动:https://github.com/jackc/pgx,它也可以与标准的 import "database/sql" 一起使用。 该库还提供了对 pg 数据库的低级访问,这使得它速度更快一些,并支持更多 postgresql 特有的功能,但那样您就不是在使用 database/sql 了。

如果您想要 sqlite 但不想依赖 C 编译器(cgo),您可以使用 https://pkg.go.dev/modernc.org/sqlite,它使用 modernc 将原始的 sqlite 转换为完全在纯 Go 环境中运行的东西。

您也可以使用专门为 sqlite 设计的库,它们支持所有(或大部分)sqlite 的功能,但那样您就又脱离了 Go 的标准 database/sql 包。有 https://github.com/crawshaw/sqlite,但它又依赖于 cgo;还有 https://pkg.go.dev/zombiezen.com/go/sqlite,它受 crawshaw 启发,但也使用了 modernc,因此不需要 cgo

编辑:使链接使用 URL

在Go中利用协程并发执行多个SQL查询可以有效提升数据库操作的效率。以下是一个典型实现示例:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "sync"
    _ "github.com/go-sql-driver/mysql"
)

type QueryResult struct {
    ID    int
    Name  string
    Error error
}

func executeQuery(db *sql.DB, query string, id int, results chan<- QueryResult, wg *sync.WaitGroup) {
    defer wg.Done()
    
    var name string
    err := db.QueryRow(query, id).Scan(&name)
    
    results <- QueryResult{
        ID:    id,
        Name:  name,
        Error: err,
    }
}

func main() {
    db, err := sql.Open("mysql", "user:password@/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    queries := []struct {
        SQL string
        ID  int
    }{
        {"SELECT name FROM users WHERE id = ?", 1},
        {"SELECT name FROM products WHERE id = ?", 2},
        {"SELECT name FROM orders WHERE id = ?", 3},
    }

    results := make(chan QueryResult, len(queries))
    var wg sync.WaitGroup

    for _, q := range queries {
        wg.Add(1)
        go executeQuery(db, q.SQL, q.ID, results, &wg)
    }

    go func() {
        wg.Wait()
        close(results)
    }()

    for result := range results {
        if result.Error != nil {
            fmt.Printf("查询ID %d 失败: %v\n", result.ID, result.Error)
        } else {
            fmt.Printf("查询ID %d 结果: %s\n", result.ID, result.Name)
        }
    }
}

关键要点:

  1. 每个查询在独立的goroutine中执行
  2. 使用sync.WaitGroup等待所有查询完成
  3. 通过channel安全地收集结果
  4. 共享单个数据库连接(需确保驱动支持并发)

对于需要不同参数的查询,可以使用以下变体:

func executeDynamicQuery(db *sql.DB, query string, args []interface{}, resultChan chan<- interface{}) {
    rows, err := db.Query(query, args...)
    if err != nil {
        resultChan <- err
        return
    }
    defer rows.Close()
    
    // 处理结果集
    var results []map[string]interface{}
    // ... 解析逻辑
    resultChan <- results
}

注意:实际使用时应添加连接池配置、超时控制和错误处理机制。

回到顶部