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

6 回复

顶一下,大家快来讨论讨论。我用 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 驱动。关于为什么这个驱动在使用过程中不容易导致死锁,可以从以下几个方面进行解释:

  1. 连接池管理go-sql-driver/mysql 实现了高效的连接池管理,确保数据库连接的合理分配和回收。通过连接池,驱动能够减少因连接竞争导致的锁等待,从而降低死锁发生的概率。

  2. 事务处理:在事务处理上,驱动遵循ACID(原子性、一致性、隔离性、持久性)原则,确保事务的正确执行。同时,它鼓励开发者使用合理的隔离级别(如READ COMMITTED、REPEATABLE READ等),以减少锁冲突。

  3. SQL优化:虽然驱动本身不直接优化SQL语句,但它通过提供标准的数据库接口,使得开发者能够利用MySQL的查询优化器和锁机制。通过编写高效的SQL语句和索引优化,可以显著减少锁争用。

  4. 错误处理和重试机制:驱动提供了丰富的错误处理选项,允许开发者在遇到锁冲突等错误时,实施适当的重试策略,从而避免死锁。

  5. 并发控制:Go语言的并发模型(goroutines和channel)天然适合处理高并发场景。go-sql-driver/mysql 充分利用这一优势,通过合理的并发控制减少锁等待时间。

综上所述,go-sql-driver/mysql 之所以不容易导致死锁,主要得益于其高效的连接池管理、合理的事务处理、对SQL优化的支持、健全的错误处理机制以及Go语言的并发优势。

回到顶部