澳门游艺场9159-9159金沙游戏场

golang的mysql驱动也封装好了事务相关的操作,则只

作者: 澳门游艺场  发布:2019-09-26
USE [TestDB]
GO
/****** 对象:  Table [dbo].[Person]    脚本日期: 11/23/2008 13:37:48 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Person](
    [PersonId] [nchar](18) NOT NULL,
    [PersonName] [nchar](20) NOT NULL,
 CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED 
(
    [PersonId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

 

事务处理是数据的重要特性。尤其是对于一些支付系统,事务保证性对业务逻辑会有重要影响。golang的mysql驱动也封装好了事务相关的操作。我们已经学习了db的Query和Exec方法处理查询和修改数据库。

默认情况下如果执行一个事务中出现错误,则只回滚错误操作语句(就是说这句不执行了,算不上回滚),错误处之前或之后的正确操作语句还是会被提交。如:

使用的表结构如下:

tx对象

一般查询使用的是db对象的方法,事务则是使用另外一个对象。sql.Tx对象。使用db的Begin方法可以创建tx对象。tx对象也有数据库交互的Query,Exec和Prepare方法。用法和db的相关用法类似。查询或修改的操作完毕之后,需要调用tx对象的Commit提交或者Rollback方法回滚。

一旦创建了tx对象,事务处理都依赖与tx对象,这个对象会从连接池中取出一个空闲的连接,接下来的sql执行都基于这个连接,直到commit或者rollback调用之后,才会把连接释放到连接池。

在事务处理的时候,不能使用db的查询方法,虽然后者可以获取数据,可是这不属于同一个事务处理,将不会接受commit和rollback的改变,一个简单的事务例子如下:

tx, err := db.Begin()tx.Exectx.Exectx.commit()

在tx中使用db是错误的:

tx, err := db.Begin()db.Exectx.Exectx.commit()

上述代码在调用db的Eexc方法的时候,tx会绑定连接到事务中,db则是额外的一个连接,两者不是同一个事务。需要注意,Begin和Commit方法,与sql语句中的BEGIN或COMMIT语句没有关系。

Use TestDB

Begin TransAction
    Insert Into Person(PersonId,PersonName)
                Values('1','Name1')
    Insert Into Person(PersonId,PersonName)
                Values('1','Name1')
    Insert Into Person(PersonId,PersonName)
                Values('3','Name3')
Commit TransAction
/*
    Select 一下 有'1','Name1'和'3','Name3',
    说明只有第二句的错误被取消了
*/

9159金沙游戏场 1;)

事务与连接

创建Tx对象的时候,会从连接池中取出连接,然后调用相关的Exec方法的时候,连接仍然会绑定在改事务处理中。在实际的事务处理中,go可能创建不同的连接,但是那些其他连接都不属于该事务。例如上面例子中db创建的连接和tx的连接就不是一回事。

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

tx, _ := db.Begin()defer tx.Rollback()stmt, _ tx.Prepare("INSERT ...")defer stmt.Close()tx.Commit()

因为stmt.Close使用defer语句,即函数退出的时候再清理stmt,可是实际执行过程的时候,tx.Commit就已经释放了连接。当函数退出的时候,再执行stmt.Close的时候,连接可能有被使用了。

全部回滚的方法1:打开 XACT_ABORT

USE [TestDB]
GO
/****** 对象:  Table [dbo].[Person]    脚本日期: 11/23/2008 13:37:48 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Person](
    [PersonId] [nchar](18) NOT NULL,
    [PersonName]9159金沙游戏场 , [nchar](20) NOT NULL,
澳门游艺场9159 , CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED 
(
    [PersonId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

事务并发

对于sql.Tx对象,因为事务过程只有一个连接,事务内的操作都是顺序执行的,在开始下一个数据库交互之前,必须先完成上一个数据库交互。例如下面的例子:

rows, _ := db.Query("SELECT id FROM user") for rows.Next() { var mid, did int rows.Scan db.QueryRow("SELECT id FROM detail_user WHERE master = ?", mid).Scan }

调用了Query方法之后,在Next方法中取结果的时候,rows是维护了一个连接,再次调用QueryRow的时候,db会再从连接池取出一个新的连接。rows和db的连接两者可以并存,并且相互不影响。

可是,这样逻辑在事务处理中将会失效:

rows, _ := tx.Query("SELECT id FROM user")for rows.Next() { var mid, did int rows.Scan tx.QueryRow("SELECT id FROM detail_user WHERE master = ?", mid).Scan}

tx执行了Query方法后,连接转移到rows上,在Next方法中,tx.QueryRow将尝试获取该连接进行数据库操作。因为还没有调用rows.Close,因此底层的连接属于busy状态,tx是无法再进行查询的。上面的例子看起来有点傻,毕竟涉及这样的操作,使用query的join语句就能规避这个问题。例子只是为了说明tx的使用问题。

Use TestDB
SET XACT_ABORT ON -- 打开
Begin TransAction
    Insert Into Person(PersonId,PersonName)
                Values('1','Name1')
    Insert Into Person(PersonId,PersonName)
                Values('1','Name1')
    Insert Into Person(PersonId,PersonName)
                Values('3','Name3')
Commit TransAction
/*
    当 SET XACT_ABORT 为 ON 时,
    如果执行 Transact-SQL 语句产生运行时错误,
    则整个事务将终止并回滚。 
    默认情况下它是OFF状态。
*/

9159金沙游戏场 2;)

实践

前面对事务解释了一堆,说了那么多,其实还不如share的code。下面就事务的使用做简单的介绍。因为事务是单个连接,因此任何事务处理过程的出现了异常,都需要使用rollback,一方面是为了保证数据完整一致性,另一方面是释放事务绑定的连接。

func doSomething(){ panic("A Panic Running Error")}func clearTransaction(tx *sql.Tx){ err := tx.Rollback() if err != sql.ErrTxDone && err != nil{ log.Fatalln }}func main() { db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true") if err != nil { log.Fatalln } defer db.Close() tx, err := db.Begin() if err != nil { log.Fatalln } defer clearTransaction rs, err := tx.Exec("UPDATE user SET gold=50 WHERE real_name='vanyarpy'") if err != nil { log.Fatalln } rowAffected, err := rs.RowsAffected() if err != nil { log.Fatalln } fmt.Println(rowAffected) rs, err = tx.Exec("UPDATE user SET gold=150 WHERE real_name='noldorpy'") if err != nil { log.Fatalln } rowAffected, err = rs.RowsAffected() if err != nil { log.Fatalln } fmt.Println(rowAffected) doSomething() if err := tx.Commit(); err != nil { // tx.Rollback() 此时处理错误,会忽略doSomthing的异常 log.Fatalln }}

我们定义了一个clearTransaction函数,该函数会执行rollback操作。因为我们事务处理过程中,任何一个错误都会导致main函数退出,因此在main函数退出执行defer的rollback操作,回滚事务和释放连接。

如果不添加defer,只在最后Commit后check错误err后再rollback,那么当doSomething发生异常的时候,函数就退出了,此时还没有执行到tx.Commit。这样就导致事务的连接没有关闭,事务也没有回滚。

全部回滚方法2:使用Try...Catch

 

总结

database/sql提供了事务处理的功能。通过Tx对象实现。db.Begin会创建tx对象,后者的Exec和Query执行事务的数据库操作,最后在tx的Commit和Rollback中完成数据库事务的提交和回滚,同时释放连接。

tx事务环境中,只有一个数据库连接,事务内的Eexc都是依次执行的,事务中也可以使用db进行查询,但是db查询的过程会新建连接,这个连接的操作不属于该事务。

关于database/sql和mysql的驱动,我们已经分三部分内容介绍了。下一节,将会对之前的内容进行梳理总结,包括错误处理和注意事项的补充。

Use TestDB
Begin Try
    Begin TransAction
        Insert Into Person(PersonId,PersonName)
                    Values('1','Name1')
        Insert Into Person(PersonId,PersonName)
                    Values('1','Name1')
        Insert Into Person(PersonId,PersonName)
                    Values('3','Name3')
    Commit TransAction
End Try
Begin Catch
    Rollback TransAction
End Catch
/*
    使用TryCatch来捕获异常。
    如果 TRY 块内生成的错误导致当前事务的状态失效,
    则将该事务归类为不可提交的事务。
    如果通常在 TRY 块外中止事务的错误在 TRY 内发生时,
    就会导致事务进入不可提交状态。
    不可提交的事务只能执行读操作或 ROLLBACK TRANSACTION。
    该事务不能执行任何可能生成写操作或 COMMIT TRANSACTION 的 Transact-SQL 语句。
    如果事务被分类为不可提交的事务,则 XACT_STATE 函数会返回值 -1。
*/

 

全部回滚方法3:自定义错误变量

默认情况下如果执行一个事务中出现错误,则只回滚错误操作语句(就是说这句不执行了,算不上回滚),错误处之前或之后的正确操作语句还是会被提交。如:

Use TestDB
Declare @tranError int -- 定义变量
Set @tranError=0
    Begin TransAction
        Insert Into Person(PersonId,PersonName)
                    Values('1','Name1')
            Set @tranError = @tranError + @@Error
        Insert Into Person(PersonId,PersonName)
                    Values('1','Name1')
            Set @tranError = @tranError + @@Error
        Insert Into Person(PersonId,PersonName)
                    Values('3','Name3')
            Set @tranError = @tranError + @@Error
    If @tranError = 0
        Commit TransAction
    Else
        Rollback TransAction
/*
    自定义一个变量来判断最后是否发生过错误。
*/

9159金沙游戏场 3;)

最后要注意的是:如果一个事务写了 Begin TransAction 而没写 Commit TransAction 或 Rollback TransAction 则相关操作的数据(也许是表,也许是列,这我还没测试。。。)会被锁住。。。而对于锁住的解决办法就是单独执行一下Commit TransAction 或 Rollback TransAction

Use TestDB

本文由澳门游艺场9159发布于澳门游艺场,转载请注明出处:golang的mysql驱动也封装好了事务相关的操作,则只

关键词:

上一篇:没有了
下一篇:没有了