Golang Go语言中 go-sql-driver/mysql 为什么不死锁?
Golang Go语言中 go-sql-driver/mysql 为什么不死锁?
事务 1:
begin;
select * from table1 where id = 1 for update;
sleep(1)
select * from table2 where id = 1 for update;
commit;
事务 2:
begin;
select * from table2 where id = 1 for update;
sleep(1)
select * from table1 where id = 1 for update;
commit;
两个表在同一个 database 里面,且两个表中记录都存在。在 MySQL 客户端肯定报死锁错误。 但是用 go-sql-driver/mysql 实现直连 MySQL,用两个 goroutine 来跑这两个事务,几乎不报死锁(加长 sleep 时间偶尔能出现 Deadlock 错误),而且 sleep 后查出来的结果行数会为 0。
在 go-sql-driver/mysql 里加日志,发现 MySQL Server 响应包里确实不是 error。实在不知为何。请指教。
代码如下:
func Atomic(ctx context.Context, db *sql.DB, txFunc func(tx *sql.Tx)error) (err error) {
var tx *sql.Tx
if tx, err = db.Begin(); err != nil {
clog.Errorf(ctx, "begin error: %v", err)
return
}
defer func() {
if err == nil {
if err = tx.Commit(); err != nil {
clog.Errorf(ctx, "commit error: %v", err)
return
}
} else {
_ = tx.Rollback()
}
}()
err = txFunc(tx)
return
}
func testDBLocal(ctx context.Context) {
dsn := “abc:abc@tcp(127.0.0.1:3306)/account_db?timeout=10s&readTimeout=10s&allowNativePasswords=True”
wDB , _ := sql.Open(“mysql”, dsn)
wDB2, _ := sql.Open(“mysql”, dsn)
if wDB == nil || wDB2 == nil {
panic(errors.New("config error"))
}
wDB.SetMaxOpenConns(100)
wDB.SetMaxIdleConns(100)
wDB.SetConnMaxLifetime(time.Hour)
wDB2.SetMaxOpenConns(100)
wDB2.SetMaxIdleConns(100)
wDB2.SetConnMaxLifetime(time.Hour)
var wg sync.WaitGroup
wg.Add(2)
clog.Infof(ctx, "start")
go func() {
defer wg.Done()
ctx := clog.GetCtxWithLogid(context.Background(), "first")
_ = Atomic(ctx, wDB, func(tx *sql.Tx) error {
// A
if rows, err := tx.Query("select * from account_tab where userid = 203802 for update"); err != nil {
clog.Errorf(ctx, "query account_tab error: %v", err)
return err
} else {
if e := rows.Close(); e != nil {
clog.Errorf(ctx, "close rows error: %v", e)
}
if e := rows.Close(); e != nil {
clog.Errorf(ctx, "close rows error: %v", e)
}
clog.Infof(ctx, "A done")
}
time.Sleep(5*time.Second)
// B
if rows, err := tx.Query("select * from account_audit_tab where userid = 203802 for update"); err != nil {
clog.Errorf(ctx, "query account_audit_tab error: %v", err)
return err
} else {
cnt := 0
for rows.Next() {
cnt += 1
}
if e := rows.Close(); e != nil {
clog.Errorf(ctx, "close rows error: %v", e)
}
if e := rows.Close(); e != nil {
clog.Errorf(ctx, "close rows error: %v", e)
}
clog.Infof(ctx, "B done, query account_audit_tab count: %v", cnt)
}
return nil
})
clog.Infof(ctx, "first done")
}()
go func() {
defer wg.Done()
ctx := clog.GetCtxWithLogid(context.Background(), "second")
_ = Atomic(ctx, wDB2, func(tx *sql.Tx) error {
// B
if rows, err := tx.Query("select * from account_audit_tab where userid = 203802 for update"); err != nil {
clog.Errorf(ctx, "query account_audit_tab error: %v", err)
return err
} else {
cnt := 0
for rows.Next() {
cnt += 1
}
if e := rows.Close(); e != nil {
clog.Errorf(ctx, "close rows error: %v", e)
}
if e := rows.Close(); e != nil {
clog.Errorf(ctx, "close rows error: %v", e)
}
clog.Infof(ctx, "B done, query account_audit_tab count: %v", cnt)
}
time.Sleep(5*time.Second)
// A
if rows, err := tx.Query("select * from account_tab where userid = 203802 for update"); err != nil {
clog.Errorf(ctx, "query account_tab error: %v", err)
return err
} else {
if e := rows.Close(); e != nil {
clog.Errorf(ctx, "close rows error: %v", e)
}
if e := rows.Close(); e != nil {
clog.Errorf(ctx, "close rows error: %v", e)
}
clog.Infof(ctx, "A done")
}
return nil
})
clog.Infof(ctx, "second done")
}()
wg.Wait()
clog.Infof(ctx, "all done")
}
更多关于Golang Go语言中 go-sql-driver/mysql 为什么不死锁?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
顶一下,大家快来讨论讨论。我用 Python Django ORM 试过了是正常死锁的。MySQL 5.5, 8.0 都有这个问题。
更多关于Golang Go语言中 go-sql-driver/mysql 为什么不死锁?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Php 也是这样
你测试过 PHP 吗?我用 Python 测试是会保报死锁错误的。的
为啥你的代码能写这么复杂?
找到原因了,rows. Next()会因为有错误而直接返回 false,所以是 0 条结果,需要在 for rows. Next()结束后检查 rows.Err().
在Golang(Go语言)生态系统中,go-sql-driver/mysql
是一个广泛使用的 MySQL 驱动。关于为什么这个驱动在使用过程中不容易导致死锁,可以从以下几个方面进行解释:
-
连接池管理:
go-sql-driver/mysql
实现了高效的连接池管理,确保数据库连接的合理分配和回收。通过连接池,驱动能够减少因连接竞争导致的锁等待,从而降低死锁发生的概率。 -
事务处理:在事务处理上,驱动遵循ACID(原子性、一致性、隔离性、持久性)原则,确保事务的正确执行。同时,它鼓励开发者使用合理的隔离级别(如READ COMMITTED、REPEATABLE READ等),以减少锁冲突。
-
SQL优化:虽然驱动本身不直接优化SQL语句,但它通过提供标准的数据库接口,使得开发者能够利用MySQL的查询优化器和锁机制。通过编写高效的SQL语句和索引优化,可以显著减少锁争用。
-
错误处理和重试机制:驱动提供了丰富的错误处理选项,允许开发者在遇到锁冲突等错误时,实施适当的重试策略,从而避免死锁。
-
并发控制:Go语言的并发模型(goroutines和channel)天然适合处理高并发场景。
go-sql-driver/mysql
充分利用这一优势,通过合理的并发控制减少锁等待时间。
综上所述,go-sql-driver/mysql
之所以不容易导致死锁,主要得益于其高效的连接池管理、合理的事务处理、对SQL优化的支持、健全的错误处理机制以及Go语言的并发优势。