Golang中支持并发安全的数据库驱动及ORM映射方案有哪些

Golang中支持并发安全的数据库驱动及ORM映射方案有哪些 大家好,

基本上,我想了解还有哪些其他数据库驱动程序具备这些或类似的功能。

例如,官方的 MongoDB 驱动程序 GitHub - mongodb/mongo-go-driver: MongoDB 官方 Golang 驱动程序 在某种程度上两者都提供了。

此致

6 回复

感谢您的回复!

更多关于Golang中支持并发安全的数据库驱动及ORM映射方案有哪些的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


非常感谢您的回复。遗憾的是,GitHub - mattn/go-sqlite3: sqlite3 driver for go using database/sql 不支持并发安全使用。

(MongoDB 不是关系型数据库,因此 Go 包的创建者可能觉得有必要在他们的驱动程序中提供文档到对象的映射。但这只是我的猜测。)

这非常有道理,我完全忘记了这一点。

我遇到的并发使用问题是,它支持多个写入操作,但不支持多个读取操作。基本上,复杂性被转移给了开发者,而开发者通常通过使用互斥锁来解决这个问题,在我看来,这增加了样板代码。另一方面,数据库驱动程序只能支持底层数据库的规范,并且也必须遵守数据库施加的限制。

你好 @hueuebi

通常,SQL 驱动程序完全不涉及对象关系映射。这部分功能留给了独立的 ORM 库,例如 GORM、Ent 等。我认为这是一个很好的方法,因为它将不同的关注点清晰地分离开来。

(MongoDB 不是关系型数据库,因此 Go 包的创建者可能觉得有必要在其驱动程序中提供文档到对象的映射功能。但这只是我的猜测。)

使用标准库包 database/sql 创建的 DB 对象是并发安全的。因此,所有为 database/sql 包使用而编写的 SQL 驱动程序,其编写方式都不应破坏这一断言。

DB

如果你打算使用 jackc/pgx(一个流行的 PostgreSQL 驱动),请注意。README 中的示例不是并发安全的:

// urlExample := "postgres://username:password@localhost:5432/database_name"
conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
if err != nil {
	fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
	os.Exit(1)
}
defer conn.Close(context.Background())

你必须查看 入门指南 页面才能看到以下说明:

pgx.Connect() 返回的 *pgx.Conn 表示单个连接,不是并发安全的。这对于像上面这样的简单命令行示例是完全合适的。然而,对于许多用途,例如 Web 应用服务器,需要并发。要使用连接池,请将导入 github.com/jackc/pgx/v5 替换为 github.com/jackc/pgx/v5/pgxpool,并使用 pgxpool.New() 进行连接,而不是 pgx.Connect()

我之所以指出这一点,是因为让我困惑的是,主要的 README 文件没有一开始就介绍连接池,或者至少在 Gopher 们习惯于 默认情况下使用并发安全的数据库句柄 时没有注明:

DB 是一个数据库句柄,代表零个或多个底层连接的池。它可以被多个 goroutine 并发安全地使用。

总之,这只是一个需要注意的潜在陷阱。如果使用 pgx,请务必使用 pgxpool.New()

hueuebi:

GitHub - mattn/go-sqlite3: sqlite3 driver for go using database/sql 不是并发安全的。

从不会导致数据竞争或数据损坏的角度来说,它是安全的。但你是对的,SQLite 本身不允许超过一个并发写入。

有一些方法可以缓解这个限制,例如,可以参见 issue #1179 中的这条评论

不幸的是,README 有点误导人。这个库已经是并发安全的。然而,有一些注意事项需要了解。

SQLite 在任何模式下都支持任意数量的并发读取。在默认的回滚日志模式下,它不支持对同一个数据库文件进行并发读写,但在 WAL 模式下支持。在任何模式下,它都不支持对同一个数据库文件进行并发写入。通常建议使用 WAL 模式。请注意,“不支持”意味着它将串行执行这些操作,等待/重试周期由你的繁忙处理程序(或 busy_timeout 编译指示)定义。如果在操作可以继续之前等待周期到期,那么你将得到 SQLITE_BUSY(又名“数据库被锁定”)。

你可能遇到过一些建议,使用共享缓存模式来避免 SQLITE_BUSY。不要遵循这些建议。SQLite 的作者们自己在任何情况下都不鼓励使用这种模式。如果你使用它,它可能看起来解决了问题,但随后你可能会开始遇到看起来类似的 SQLITE_LOCKED(又名“数据库表被锁定”)。

如果你的代码要在同一个操作中读取和写入数据库,你必须为整个操作使用单个事务。否则,由于 sql.DB 的工作方式,你可能会遇到问题,即一个连接上正在进行的读取会阻塞另一个连接上尝试的写入。

每当你启动一个可能写入数据库的事务时,总是使用 BEGIN IMMEDIATE,而不是默认的 (BEGIN DEFERRED)。否则,如果另一个连接也在你的事务中间写入数据库,由于 SQLite 需要强制执行数据库引擎的 ACID 要求,你可能会在没有触发繁忙处理程序的情况下就得到 SQLITE_BUSY。如果你的事务确定只进行读取,那么你应该继续使用 BEGIN DEFERRED,以允许并发读取功能。

我的一般建议是拥有两个连接池(即 sql.DB 实例)。一个应该是读写池,配置为使用 BEGIN IMMEDIATE,并通过 SetMaxOpenConnsSetMaxIdleConnsSetConnMaxLifetimeSetConnMaxIdleTime 限制为单个连接。另一个应该是只读池,配置为使用 BEGIN DEFERRED(这是默认值),并且可以根据系统限制调整为任意数量的连接。

在Golang中,支持并发安全的数据库驱动和ORM映射方案主要有以下几种:

  1. database/sql标准库驱动 - 原生支持并发安全
import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

// 连接池默认就是并发安全的
db, err := sql.Open("mysql", "user:password@/dbname")
defer db.Close()

// 多个goroutine可以安全地并发使用
go func() {
    rows, _ := db.Query("SELECT * FROM users")
    // 处理结果
}()

go func() {
    _, _ = db.Exec("INSERT INTO users(name) VALUES(?)", "test")
}()
  1. GORM - 最流行的ORM,支持并发安全
import "gorm.io/gorm"

// 使用连接池,并发安全
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    PrepareStmt: true, // 预编译语句提升并发性能
})

// 并发查询示例
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        var user User
        db.First(&user, id)
    }(i)
}
wg.Wait()
  1. sqlx - 扩展database/sql,保持并发安全
import "github.com/jmoiron/sqlx"

db := sqlx.MustConnect("mysql", "user:password@/dbname")

// 并发安全的连接池操作
go func() {
    var users []User
    db.Select(&users, "SELECT * FROM users WHERE active=?", true)
}()

go func() {
    db.MustExec("UPDATE users SET status=? WHERE id=?", "active", 1)
}()
  1. ent - Facebook开发的ORM,设计为并发安全
import "<project>/ent"

client, err := ent.Open("mysql", dsn)
defer client.Close()

// ent的Client是并发安全的
go func() {
    user := client.User.Query().Where(user.ID(1)).OnlyX(ctx)
}()

go func() {
    client.User.Create().
        SetName("John").
        SetAge(30).
        SaveX(ctx)
}()
  1. pgx - PostgreSQL专用驱动,高性能且并发安全
import "github.com/jackc/pgx/v4/pgxpool"

pool, err := pgxpool.Connect(context.Background(), "postgresql://user:password@localhost/db")
defer pool.Close()

// 连接池并发安全
go func() {
    rows, _ := pool.Query(ctx, "SELECT * FROM users")
    rows.Close()
}()

go func() {
    pool.Exec(ctx, "INSERT INTO users(name) VALUES($1)", "test")
}()
  1. go-redis - Redis客户端,连接池并发安全
import "github.com/go-redis/redis/v8"

rdb := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
    PoolSize: 100, // 连接池大小
})

// 并发安全操作
go func() {
    rdb.Set(ctx, "key1", "value1", 0)
}()

go func() {
    val, _ := rdb.Get(ctx, "key1").Result()
}()

这些驱动和ORM都通过连接池机制实现了并发安全,可以在多个goroutine中安全共享使用。其中GORM和ent提供了更完整的ORM功能,而sqlx和pgx则在保持轻量级的同时提供了更好的性能。

回到顶部