incubator-seata-go icon indicating copy to clipboard operation
incubator-seata-go copied to clipboard

XA 相同的DB执行两条sql时报错

Open smiletrl opened this issue 1 year ago • 0 comments

What happened: XA 一个事务里跑多个sql不能正常执行 What you expected to happen: XA 一个事务里跑多个sql可以正常执行

How to reproduce it (as minimally and precisely as possible): 使用xa basic sample里代码, 重复执行 db.ExecContext两次, 报错

func insertData(ctx context.Context) error {
	fmt.Printf("insert start \n\n\n")

	sql := "INSERT INTO `order_tbl` ( `user_id`, `commodity_code`, `count`, `money`, `descs`) VALUES (?, ?, ?, ?, ?);"
	ret, err := db.ExecContext(ctx, sql, "NO-100001", "C100000", 100, nil, "init desc")
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return err
	}
	rows, err := ret.RowsAffected()
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return err
	}
	fmt.Printf("insert success 1: %d.\n\n\n", rows)

	sql = "INSERT INTO `order_tbl` ( `user_id`, `commodity_code`, `count`, `money`, `descs`) VALUES (?, ?, ?, ?, ?);"
	ret, err = db.ExecContext(ctx, sql, "NO-100001", "C100000", 100, nil, "init desc")
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return err
	}
	rows, err = ret.RowsAffected()
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return err
	}
	fmt.Printf("insert success 2: %d.\n\n\n", rows)
	return nil
}

func sampleInsert(ctx context.Context) {
	tm.WithGlobalTx(ctx, &tm.GtxConfig{
		Name:    "XASampleLocalGlobalTx_Insert",
		Timeout: time.Second * 30,
	}, insertData)
}

报错, panic

should NEVER happen: setAutoCommit from true to false while xa branch is active

Anything else we need to know?:

// BeginTx like common transaction. but it just exec XA START
func (c *XAConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
	if !tm.IsGlobalTx(ctx) {
		tx, err := c.Conn.BeginTx(ctx, opts)
		return tx, err
	}

	c.autoCommit = false

	c.txCtx = types.NewTxCtx()
	c.txCtx.DBType = c.res.dbType
	c.txCtx.TxOpt = opts
	c.txCtx.ResourceID = c.res.resourceID
	c.txCtx.XID = tm.GetXID(ctx)
	c.txCtx.TransactionMode = types.XAMode

	tx, err := c.Conn.BeginTx(ctx, opts)
	if err != nil {
		return nil, err
	}
	c.tx = tx


	if !c.autoCommit {
		if c.xaActive {
			return nil, errors.New("should NEVER happen: setAutoCommit from true to false while xa branch is active")
		}

		baseTx, ok := tx.(*Tx)
		if !ok {
			return nil, fmt.Errorf("start xa %s transaction failure for the tx is a wrong type", c.txCtx.XID)
		}

		c.branchRegisterTime = time.Now()
		if err := baseTx.register(c.txCtx); err != nil {
			c.cleanXABranchContext()
			return nil, fmt.Errorf("failed to register xa branch %s, err:%w", c.txCtx.XID, err)
		}

		c.xaBranchXid = XaIdBuild(c.txCtx.XID, c.txCtx.BranchID)
		c.keepIfNecessary()

		if err = c.start(ctx); err != nil {
			c.cleanXABranchContext()
			return nil, fmt.Errorf("failed to start xa branch xid:%s err:%w", c.txCtx.XID, err)
		}
		c.xaActive = true
	}

	return &XATx{tx: tx.(*Tx)}, nil
}

这里的逻辑好像定死了一个XAConn 不能区分两次不同的sql tx. c.autoCommit = false 默认为false,c.xaActive = true 执行一次,这个(c *XAConn) BeginTx 就无法再次执行。

smiletrl avatar Mar 03 '24 09:03 smiletrl