go-sql-driver/mysql 为什么不死锁?

2019-06-20 23:18:33 +08:00
 hzj629206

事务 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")
}
3371 次点击
所在节点    Go 编程语言
5 条回复
hzj629206
2019-06-21 10:53:38 +08:00
顶一下,大家快来讨论讨论。我用 Python Django ORM 试过了是正常死锁的。MySQL 5.5, 8.0 都有这个问题。
bzeron
2019-06-21 14:46:52 +08:00
Php 也是这样
hzj629206
2019-06-21 16:36:14 +08:00
@bzeron 你测试过 PHP 吗?我用 Python 测试是会保报死锁错误的。的
aliipay
2019-06-26 00:19:31 +08:00
为啥你的代码能写这么复杂?
hzj629206
2019-06-27 18:29:33 +08:00
找到原因了,rows. Next()会因为有错误而直接返回 false,所以是 0 条结果,需要在 for rows. Next()结束后检查 rows.Err().

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/576017

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX