fix(db): 修复事务模式下的错误处理与回滚机制
- 在 `queryWithRetry` 和 `execWithRetry` 方法中添加对 `txFailed` 的状态管理,确保在 SQL 错误时正确标记事务失败 - 增加新的测试用例以验证事务模式下的错误处理逻辑,确保在 SQL 错误发生时能够正确回滚 - 优化测试数据库的初始化方法,支持事务测试场景
This commit is contained in:
@@ -77,6 +77,9 @@ func (that *HoTimeDB) queryWithRetry(query string, retried bool, args ...interfa
|
||||
resl, err = that.testTx.Query(query, processedArgs...)
|
||||
that.LastErr.SetError(err)
|
||||
if err != nil {
|
||||
if that.txFailed != nil {
|
||||
*that.txFailed = true
|
||||
}
|
||||
if that.testMu != nil {
|
||||
that.testMu.Unlock()
|
||||
}
|
||||
@@ -172,6 +175,9 @@ func (that *HoTimeDB) execWithRetry(query string, retried bool, args ...interfac
|
||||
if e != nil {
|
||||
// testTx 模式下不走连接池重试,避免 busy buffer
|
||||
if that.testTx != nil {
|
||||
if that.txFailed != nil {
|
||||
*that.txFailed = true
|
||||
}
|
||||
return resl, that.LastErr
|
||||
}
|
||||
// 事务内不做连接级重试:死锁等错误会导致 MySQL 自动回滚事务,
|
||||
|
||||
@@ -3,6 +3,7 @@ package db
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"database/sql"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -34,6 +35,18 @@ func newTestDB(t *testing.T) *HoTimeDB {
|
||||
return db
|
||||
}
|
||||
|
||||
func newTestDBWithTestTx(t *testing.T) *HoTimeDB {
|
||||
t.Helper()
|
||||
db := newTestDB(t)
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
db.testTx = tx
|
||||
db.testMu = &sync.Mutex{}
|
||||
return db
|
||||
}
|
||||
|
||||
func TestAction_TxFailedForceRollback(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
defer db.DB.Close()
|
||||
@@ -133,3 +146,105 @@ func TestAction_SqlErrorThenReturnTrue_MustRollback(t *testing.T) {
|
||||
t.Fatalf("value should be rolled back to 100 (original), got %d", val)
|
||||
}
|
||||
}
|
||||
|
||||
// --- testTx (SAVEPOINT) 模式测试 ---
|
||||
|
||||
func TestAction_TestTx_SqlErrorForceRollbackSavepoint(t *testing.T) {
|
||||
db := newTestDBWithTestTx(t)
|
||||
defer db.testTx.Rollback()
|
||||
defer db.DB.Close()
|
||||
|
||||
db.testTx.Exec("UPDATE test_item SET value = 100 WHERE id = 1")
|
||||
|
||||
result := db.Action(func(tx HoTimeDB) (isSuccess bool) {
|
||||
tx.Update("test_item", Map{"value": 888}, Map{"id": 1})
|
||||
|
||||
tx.Exec("THIS IS INVALID SQL")
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if result != false {
|
||||
t.Fatalf("Action (testTx mode) should return false when SQL error occurred, got true")
|
||||
}
|
||||
|
||||
db.testMu.Lock()
|
||||
rows, err := db.testTx.Query("SELECT value FROM test_item WHERE id = 1")
|
||||
db.testMu.Unlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
t.Fatal("no row found")
|
||||
}
|
||||
var val int64
|
||||
rows.Scan(&val)
|
||||
if val != 100 {
|
||||
t.Fatalf("value should be rolled back to 100 via SAVEPOINT, got %d", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAction_TestTx_NormalCommitSavepoint(t *testing.T) {
|
||||
db := newTestDBWithTestTx(t)
|
||||
defer db.testTx.Rollback()
|
||||
defer db.DB.Close()
|
||||
|
||||
result := db.Action(func(tx HoTimeDB) (isSuccess bool) {
|
||||
tx.Update("test_item", Map{"value": 777}, Map{"id": 1})
|
||||
return true
|
||||
})
|
||||
|
||||
if result != true {
|
||||
t.Fatalf("Action (testTx mode) should return true on success, got false")
|
||||
}
|
||||
|
||||
db.testMu.Lock()
|
||||
rows, err := db.testTx.Query("SELECT value FROM test_item WHERE id = 1")
|
||||
db.testMu.Unlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
t.Fatal("no row found")
|
||||
}
|
||||
var val int64
|
||||
rows.Scan(&val)
|
||||
if val != 777 {
|
||||
t.Fatalf("value should be 777 after RELEASE SAVEPOINT, got %d", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAction_TestTx_NormalRollbackSavepoint(t *testing.T) {
|
||||
db := newTestDBWithTestTx(t)
|
||||
defer db.testTx.Rollback()
|
||||
defer db.DB.Close()
|
||||
|
||||
db.testTx.Exec("UPDATE test_item SET value = 100 WHERE id = 1")
|
||||
|
||||
result := db.Action(func(tx HoTimeDB) (isSuccess bool) {
|
||||
tx.Update("test_item", Map{"value": 666}, Map{"id": 1})
|
||||
return false
|
||||
})
|
||||
|
||||
if result != false {
|
||||
t.Fatalf("Action (testTx mode) should return false, got true")
|
||||
}
|
||||
|
||||
db.testMu.Lock()
|
||||
rows, err := db.testTx.Query("SELECT value FROM test_item WHERE id = 1")
|
||||
db.testMu.Unlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
t.Fatal("no row found")
|
||||
}
|
||||
var val int64
|
||||
rows.Scan(&val)
|
||||
if val != 100 {
|
||||
t.Fatalf("value should be rolled back to 100 via SAVEPOINT, got %d", val)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user