apache / incubator-seata-go

Go Implementation For Seata
https://seata.apache.org/
Apache License 2.0
1.56k stars 283 forks source link

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

Closed smiletrl closed 8 months ago

smiletrl commented 8 months ago

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 commented 8 months ago

这个问题可以通过将 db.ExecContext(ctx, sql, "NO-100001", "C100000", 100, nil, "init desc")这里的sql改为多条sql 语句即可。