From a2e6e5f84d6a8566924c1c0cf3c7bf34b1f71431 Mon Sep 17 00:00:00 2001 From: zhx Date: Sun, 12 Jul 2026 00:59:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=BC=E5=AE=B9=E9=92=B1=E5=8C=85?= =?UTF-8?q?=E5=85=83=E6=95=B0=E6=8D=AE=E6=97=A0=E5=8F=98=E5=8C=96=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/storage/mysql/transaction.go | 5 ++- .../storage/mysql/transaction_test.go | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 services/wallet-service/internal/storage/mysql/transaction_test.go diff --git a/services/wallet-service/internal/storage/mysql/transaction.go b/services/wallet-service/internal/storage/mysql/transaction.go index 005e7a1c..812df32a 100644 --- a/services/wallet-service/internal/storage/mysql/transaction.go +++ b/services/wallet-service/internal/storage/mysql/transaction.go @@ -119,7 +119,10 @@ func (r *Repository) updateTransactionMetadata(ctx context.Context, tx *sql.Tx, if err != nil { return err } - if affected != 1 { + // go-sql-driver/mysql 默认返回 changed rows,不返回 matched rows。当最终 metadata + // 与插入时的预测值完全一致时,这条 UPDATE 合法命中主键但 affected=0;交易行已在 + // 同一事务中由 insertTransaction 成功插入,因此只能把多行更新视为不变量破坏。 + if affected > 1 { return xerr.New(xerr.Internal, "wallet transaction metadata update lost its transaction") } return nil diff --git a/services/wallet-service/internal/storage/mysql/transaction_test.go b/services/wallet-service/internal/storage/mysql/transaction_test.go new file mode 100644 index 00000000..d5510920 --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/transaction_test.go @@ -0,0 +1,39 @@ +package mysql + +import ( + "context" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + + "hyapp/pkg/appcode" +) + +func TestUpdateTransactionMetadataAcceptsMatchedButUnchangedRow(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("create sqlmock: %v", err) + } + defer db.Close() + + mock.ExpectBegin() + tx, err := db.Begin() + if err != nil { + t.Fatalf("begin transaction: %v", err) + } + mock.ExpectExec(regexp.QuoteMeta(`UPDATE wallet_transactions + SET metadata_json = ?, updated_at_ms = ? + WHERE app_code = ? AND transaction_id = ?`)). + WithArgs(`{"balance_version":9}`, int64(1234), "lalu", "tx-1"). + WillReturnResult(sqlmock.NewResult(0, 0)) + + repo := &Repository{} + ctx := appcode.WithContext(context.Background(), "lalu") + if err := repo.updateTransactionMetadata(ctx, tx, "tx-1", map[string]int64{"balance_version": 9}, 1234); err != nil { + t.Fatalf("unchanged matched row must not fail: %v", err) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet SQL expectations: %v", err) + } +}