为了账号安全,请及时绑定邮箱和手机立即绑定

回滚不适用于 Go 语言事务包装器

回滚不适用于 Go 语言事务包装器

Go
慕勒3428872 2022-12-19 10:53:12
我最近开始学习围棋。我发现了以下用于数据库事务处理的包装器的 Github 实现,并决定尝试一下。(来源)https://github.com/oreilly-japan/practical-go-programming/blob/master/ch09/transaction/wrapper/main.go我使用 PostgreSQL 作为数据库。最初,它包含以下数据。testdb=> select * from products; product_id | price------------+------- 0001       |   200 0002       |   100 0003       |   150 0004       |   300(4 rows)进程A成功后,故意让进程B失败,期望事务A回滚。然而,当我们运行它时,回滚并没有发生,我们最终得到以下结果实际上,由于 B 失败,进程 A 应该回滚,数据库值应该没有变化。我已经在一些地方插入了日志来确认这一点,但我不确定。为什么回滚没有执行?
查看完整描述

1 回答

?
红糖糍粑

TA贡献1815条经验 获得超6个赞

正如@Richard Huxton 所说,传递tx给一个函数f

以下是步骤:

  1. 添加一个字段struct txAdmin以容纳*sql.Tx,所以txAdminDBTx字段

  2. 里面Transaction设置tx*txAdmin.Tx

  3. 每个查询的内部UpdateProduct使用*Service.tx.Tx

所以最后的代码是这样的:

package main


import (

    "context"

    "database/sql"

    "fmt"

    "log"


    _ "github.com/jackc/pgx/v4/stdlib"

)


// transaction-wrapper-start

type txAdmin struct {

    *sql.DB

    *sql.Tx

}


type Service struct {

    tx txAdmin

}


func (t *txAdmin) Transaction(ctx context.Context, f func(ctx context.Context) (err error)) error {

    log.Printf("transaction")

    tx, err := t.DB.BeginTx(ctx, nil)

    if err != nil {

        return err

    }


    // set tx to Tx

    t.Tx = tx


    defer tx.Rollback()

    if err := f(ctx); err != nil {

        log.Printf("transaction err")

        return fmt.Errorf("transaction query failed: %w", err)

    }


    log.Printf("commit")

    return tx.Commit()

}


func (s *Service) UpdateProduct(ctx context.Context, productID string) error {

    updateFunc := func(ctx context.Context) error {

        log.Printf("first process")

        // Process A

        if _, err := s.tx.Tx.ExecContext(ctx, "UPDATE products SET price = 200 WHERE product_id = $1", productID); err != nil {

            log.Printf("first err")

            return err

        }

        log.Printf("second process")


        // Process B(They are intentionally failing.)

        if _, err := s.tx.Tx.ExecContext(ctx, "...", productID); err != nil {

            log.Printf("second err")

            return err

        }

        return nil

    }

    log.Printf("update")

    return s.tx.Transaction(ctx, updateFunc)

}


// transaction-wrapper-end

func main() {

    data, err := sql.Open("pgx", "host=localhost port=5432 user=testuser dbname=testdb password=password sslmode=disable")

    if nil != err {

        log.Fatal(err)

    }


    database := Service{tx: txAdmin{DB: data}}


    ctx := context.Background()


    database.UpdateProduct(ctx, "0004")

}


查看完整回答
反对 回复 2022-12-19
  • 1 回答
  • 0 关注
  • 66 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信