Golang中事务实现遇到的问题

Golang中事务实现遇到的问题 我正在尝试实现事务处理,但遇到了一些问题。根据我的业务逻辑,在处理列表中的第二个用户时应该会出现错误,事务不应该完成,但不知为何它却执行成功了……

func closeTransaction(tx *sql.Tx, commit *bool) {
	if *commit {
		log.Info("Commit sql transaction")
		if err := tx.Commit(); err != nil {
			log.Warning(err)
		}
	} else {
		log.Warning("Rollback sql transcation")
		if err := tx.Rollback(); err != nil {
			log.Warning(err)
		}
	}
}

	...
	tx, _ := db.Begin()
	commitTx := false
	defer closeTransaction(tx, &commitTx)
	log.Info("Begin sql transaction")

	for _, user := range userlist {
		// 这里我实现的逻辑是在处理**userlist**中的第二个**用户**时
		// 会出现错误,即事务不应该完成
		// 但当我检查数据库记录时,不知为何它却执行成功了...
		...
		if err != nil {
			log.Warning(err)
			http.Error(w, "Unable to persist user - wrong user data", 405)
			return 
			// 应该是失败,即整个事务未完成
			// 但不知为何第一个用户却被持久化了
		}
	}

	commitTx = true
	return // 应该是成功,即整个事务完成

这段代码有什么问题?顺便说一下,我使用**db.Exec(sqlStatement, …)**来写入数据库——这与这个问题有关吗?


更多关于Golang中事务实现遇到的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

你好,@cinematik,能否将此问题标记为已解决?

更多关于Golang中事务实现遇到的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好,@skillian,我已将其标记为已解决。

解决方案:该问题是由于使用了 db.Exec 而不是 tx.Prepare 和随后的 stmt.Exec 导致的。

你的代码中存在一个关键问题:在错误处理中使用了return语句,但没有显式调用Rollback()。虽然defer closeTransaction(tx, &commitTx)会在函数返回时执行,但commitTx变量在错误发生时仍然是false,所以会执行回滚。

问题可能出现在以下几个方面:

  1. 事务隔离级别:检查数据库的事务隔离级别,某些隔离级别可能导致你看到未提交的数据
  2. 自动提交:确认数据库连接是否启用了自动提交
  3. 错误处理逻辑:确保错误条件确实被触发

这里是一个修复后的示例:

func processUsers(db *sql.DB, userlist []User) error {
    tx, err := db.Begin()
    if err != nil {
        return fmt.Errorf("failed to begin transaction: %w", err)
    }
    
    commitTx := false
    defer func() {
        if !commitTx {
            if rollbackErr := tx.Rollback(); rollbackErr != nil {
                log.Printf("rollback failed: %v", rollbackErr)
            }
        }
    }()

    for i, user := range userlist {
        // 使用事务的Exec方法
        result, err := tx.Exec(
            "INSERT INTO users (name, email) VALUES (?, ?)", 
            user.Name, user.Email,
        )
        if err != nil {
            log.Printf("failed to insert user %d: %v", i, err)
            return fmt.Errorf("user insertion failed: %w", err)
        }
        
        // 检查影响的行数
        rowsAffected, _ := result.RowsAffected()
        log.Printf("Inserted user %d, rows affected: %d", i, rowsAffected)
        
        // 模拟第二个用户出错
        if i == 1 {
            return fmt.Errorf("simulated error for second user")
        }
    }

    if err := tx.Commit(); err != nil {
        return fmt.Errorf("commit failed: %w", err)
    }
    commitTx = true
    return nil
}

关键改进:

  • 使用tx.Exec()而不是db.Exec()来确保在事务中执行
  • 移除了复杂的commit标志逻辑
  • 添加了更明确的错误处理
  • 在defer中直接处理回滚

检查你的实际代码是否确实使用了事务的Exec方法:

// 正确:使用事务的Exec
_, err := tx.Exec("INSERT INTO users ...", params...)

// 错误:使用数据库的Exec(这会在事务外执行)
_, err := db.Exec("INSERT INTO users ...", params...)

确保你使用的是tx.Exec(),这样SQL语句才会在事务上下文中执行。

回到顶部