golang数据库事务

255 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

事务

事务操作是通过三个方法实现:

  • Begin():开启事务
  • Commit():提交事务(执行sql)
  • Rollback():回滚

在创建一个表,并插入两条数据:

CREATE TABLE `account` (
  `id` int(4) NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  `money` float(8,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
)
INSERT INTO `account` VALUES ('1', 'aa', '3000.00');
INSERT INTO `account` VALUES ('2', 'bb', '1000.00');

现在我们想让aa转账给bb2000元。

如果转账成功,修改aa和bb的金额。否则aa和bb的金额保持不变。

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

func main() {
    /*
    事务:
        4大特性:ACID
        原子性:
        一致性:
        隔离性:
        永久性:
     */
    //aa-->bb,2000元
    db, _ := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/my1802?charset=utf8")
    //开启事务
    tx, _ := db.Begin()
    //提供一组sql操作
    var aff1, aff2 int64 = 0, 0
    result1, _ := tx.Exec("UPDATE account SET money=3000 WHERE id=?", 1)
    result2, _ := tx.Exec("UPDATE account SET money=2000 WHERE id=?", 2)
    //fmt.Println(result2)
    if result1 != nil {
        aff1, _ = result1.RowsAffected()
    }
    if result2 != nil {
        aff2, _ = result2.RowsAffected();
    }
    fmt.Println(aff1)
    fmt.Println(aff2)

    if aff1 == 1 && aff2 == 1 {
        //提交事务
        tx.Commit()
        fmt.Println("操作成功。。")
    } else {
        //回滚
        tx.Rollback()
        fmt.Println("操作失败。。。回滚。。")
    }

}

image-20211004223949397

由于事务是一个一直连接的状态,所以Tx对象必须绑定和控制单个连接。一个Tx会在整个生命周期中保存一个连接,然后在调用commit()或Rollback()的时候释放掉。在调用这几个函数的时候必须十分小心,否则连接会一直被占用直到被垃圾回收。

使用事务需要注意事务的连接生命周期从Beigin函数调用起,直到Commit和Rollback函数的调用结束。事务也提供了prepare语句的使用方式,但是需要使用Tx.Stmt方法创建。prepare设计的初衷是多次执行,对于事务,有可能需要多次执行同一个sql。然而无论是正常的prepare和事务处理,prepare对于连接的管理都有点小复杂。因此私以为尽量避免在事务中使用prepare方式。