.NET中的数据库中的事物与并发

并发的概念: 两个用户同时对同一个数据进行操作。

如:两个用户在同时编辑一行,进行了成功提交,然而,查看结果时候,发现只有一名用户的修改在数据库中生效,另外一名用户的修改丢失了。或者两名用户删除同一行数据时,后提交删除者失败了,因为先提交者已经删除,当他提交时,实际操作的是一行已经不存在的数据。为了防止这种情况的发生,必须包含管理并发事件的代码。

 

并发控制的处理方式有三种:

1."后来者赢":仅当行正在更新时,才不能访问它,这意味着,当两个用户同时编辑一行时,后提交编辑者生效,前面提交的修改讲丢失,这时ADO.NET中的默认模式。

2."开放式并发控制":当行正在更新时,不能访问。并且在行更新完后试图更新它也将导致错误。这种方法也可以被称为"先来者赢"。这种方法在ADO.NET中比较容易实现。

3."悲观并发控制":行在检索时,就被锁定。直到更新完毕时才解除锁定,这可能影响到性能,但确实可以有效保护数据。这在ADO.NET中是不可能实现的。要实现它必须需要一些高级技术(SQL中的事物并发加锁控制)。

 

这里讲解前两种方式。第三种由于是DBMS系统的功能,不同DBMS系统有不同的实现方式,所以不讲解。

 

1."后来者赢":用操作所影响的行数来判断。这种方式其实很常见,我们也时刻在用,只不过平时用的时候不知道这是处理并发操作的一种方式而已。

SqlCommand cmd = new SqlCommand ( "DELETE FROM table WHERE ID = ' " + string + " ' ",conn);

int rowAffected = -1;

conn.open();

try

{

rowAffected = cmd.ExecuteNonQuery();

}

finally

{

conn.Close();

}

switch (rowAffected)

{

case -1: "Command failed to execute"; break;

case 0: "Row not delete as it doesn't exist"; break;

case 1: "Row delete"; break;

}        //代码只表达实现思想,无语法操作。

 

解释:通常我们会用 ExecuteNonQuery()>0 来判断是否进行了成功操作,原理是一样的,只不过多了一个当影响的行数(rowAffected)为0的时候,需要提交给用户一个额外的信息,因为这时候的操作失败并不是由于SQL语句错误,无法编译,而进入了catch语块,对用户抛出操作失败,也不是由于rowAffected=-1,对用户提示,让用户认为是操作不当(输入不对)。这里rowAffected=0的判断,就是为了给用户一个提示,正确的执行了操作,但是并没有影响到数据库中的数据,原因就是,他删除的这行可以刚刚已经被另外一个人删除过了!

 

2."开放式并发控制":使用时间戳和版本号进行操作。

额外的在表中新建一列,用来记录最后操作的时间,或最后操作的版本号。每当进行操作,在提交时,只需检查当该列的值是否是该行的正确版本即可。

如:

table

ID iContent Time //Time的类型为Datetime,Default(getdate())

1 123 2010/12/12 00:00:01

 

当我正在修改ID=1的内容时,另外一个人也在修改,并且先于我提交,那么,我提交时,检查一下Time列属性是否还是我取得这个属性的值,(我在读取这个Time列时,是2010/12/12 00:00:01,但后来有人先于我修改这个文本,那么SQL中该列的属性值一定不是2010/12/12 00:00:01),从而进行对应的操作。

 

3."悲观并发控制":高级的DBMS系统加锁技巧,对于严格的不可同时操作的数据十分有用。得参考专业资料。

 

 

 

 

这里主要谈一下事物这个概念。

事物:事物是将一系列的操作组合在一起的方式,使操作要么全部成功,要么全部失败。在使用事务时,即使一些操作成功,但有一个操作失败,之前的操作也将进行“回滚”,结果就像这些操作没有进行过一样。

 

典型的例子就是:当用户从银行一个账户上转账到另外一个账户上的时。其中有两部操作。

1.从自己账户取钱。

2.向别人账户存钱。

 

这时,无论哪一步操作失败,而另外一步成功响应,都会造成不同的后果,如账户莫名其妙的多了钱,少了钱。

所以我们要做的就是,当两步操作都成功时,数据库才写入记录。否则,一旦其中一步操作失败,整件事发生回滚。

 

事物分为两种: SQL事物和.NET事物。

 

1.SQL事物:

在使用SQL Server时,实际上一直在进行事物操作,以为SQL命令被解释为事物。实际上,每条命令都导致一个事物被执行,如果命令没有错误,结果将被提交数据库执行。如果命令导致错误,包含该命令的事物将被回滚。这是SQL Server中的默认操作模式,称为自动提交的事物。(隐式的事物操作)

 

SQL命令中,出现错误有语法错误和语义错误(逻辑错误)

语义错误:

DELETE FROM table WHERE ID = 1

DELETE FROM table WHERE ID = 2

上面操作中,系统将为每一个DELETE语句自动建立一个事物。即使第一句执行失败(可能由于ID=1不存在),第二条语句也会执行。

 

语法错误:

DELET FROM table WHERE ID = 1

DELETE FROM table WHERE ID = 2

上面操作中,存在语法错误,DELET 不是SQL关键字。 这时的结果是两条语句都不会执行,因为SQL语句根本无法进行正确编译。

 

以上是废话,关于SQL事物的介绍。重点在这里:

我们想让多个SQL命令进行显式的事务操作,从而控制事务的回滚操作。

必须使用关键字:

BEGIN TRANSACTION 定义事务开始,

COMMIT TRANSACTION 定义事务结束.

 

可在事务中嵌套多个事务。顺序执行,当最外层事务执行完毕,没有错误时,数据库更改才会成功。

 

BEGIN TRANSACTION

 

... code which might call ROLLBACK TRANSACTION or other TRANSACTION

 

COMMIT TRANSACTION

 

 

我来举一个例子:

CREATE PROC causeRollback

{

@FirstId uniqueidentifier,

@FirstName varchar(50),

@SecondId uniqueidentifier,

@SecondName varchar(50)

}

AS

SET XACT_ABORT ON       //这句尤为重要,它代表任何一个SQL命令错误,都会引发事务终止,所修改的数据回滚。

 

BEGIN TRANSACTION

INSERT INTO table1 (Id, Name) VALUES (@FirstId, @FirstName)

INSERT INTO table1 (Id, Name) VALUES (@SecondId, @SecondName)

COMMIT TRANSACTION

SET XACT_ABORT OFF //OFF它

 

 

现在我在程序中进行两次操作,进行一个对比。(具体实现代码略)

try

{

先调用存储过程causeRollback。     传入参数(@FirstID=1,@FirstName=a),(@SecondId=2,@SecondName=b);

再调用一次存储过程causeRollback。        传入参数(@FirstID=3,@FirstName=c),(@SecondId=1,@SecondName=d);

执行!

}

 

这时,我们再查看数据库中table1表的记录,

Id     Name

1      a

2      b

 

说明,只有第一次操作是成功的。第二次传入的@SecondID=1,导致导致参数必须唯一(uniqueidentifier)事件错误。所以第二次的操作全部回滚,即插入的(@FirstID=3,@FirstName=c)数据也无效。

以上就是SQL事务的操作,为一个抛砖引玉的作用,可以解决如刚才的银行转账问题。

而更复杂的则用.NET事务。

 

 

2. .NET事物.

虽然说SQL事物可以满足基本需要,如银行转帐问题我们可以在SQL存储过程中建立一个包含三个参数(两个ID,一个金额)的存储过程,并使用两个UPDATE命令,在一个事物中更新两行,要么两行都修改,要么都不修改,从而保持总金额不变。

但当我们需要进行复杂事物操作的时候,我们构建一个存储过程往往很困难.这时,我们选择.NET事物处理机制。

.NET中的事物处理机制对象是SqlTransaction,他包含在System.Data.SqlClient命名空间中,可以调用SqlTransaction对象中的BeginTransaction()方法。且仅在连接打开时才可这样做。

 

SqlConnection conn = new SqlConnection(ConnectionString);

conn.Open();

SqlTransaction sTransaction = conn.BeginTransaction();

 

这样我们就获得了一个SqlTransaction对象。但是我们获得的这个对象只能用于一个连接,因此不适合用于分布式事务。(一般小型应用程序不会用到分布式)。

SqlTransaction对象有两个方法可以用于提交或回滚修改:Commit()和Rollback()。通常。用try...catch语句来控制这两个方法的调用:

 

SqlConnection conn = new SqlConnection(ConnectionString);

conn.Open();

SqlTransaction sTransaction = conn.BeginTransaction();

try

{

    //... some code

    sTransaction.Commit() ;

}

catch (Exception ex)

{

    transaction.Rollback();

    //... process exception.

}

finnaly

{

    conn.Close();

}

这样的操作,也有可能会导致Rollback()方法失败。这取决于事务处理过程中发生了什么,如失去到数据库的连接。因此,应将该方法的调用封装在另一个try...catch语句中来处理。

接着我们需要做更多的工作,用属性的方法来进行事务操作的调用。当然这么做的好处是你可以一步步控制事务的进行。但是十分的繁琐。.NET中实现事务中还有更简单的方式,这种方式不需要额外代码。

 

我知道说了这么多,大多数人还是不懂该如何进行操作。上面的例子我只是想让大家理解.NET事务的原理。接着我举一个简单的.NET事物处理方法的例子。你可以不必理解为什么,只需要知道如何用就好。后面我会进行详细的解释。

在这种方法中,我们需要用到两个对象: TransactionScope和Transaction,他是通用的事务操作方法,包含于System.Transaction 命名空间中。所以我们必须在代码页中先进行引用。

继续用上方SQL事务处理中的例子来进行举例,编辑一个按钮的click事件处理程序:

 

protect void button_OnClick(object sender, EventArgs e)

{

    using(TransactionScope transactionScope = new TransactionScope())

    {

        try

        {

            Sql语句赋值:

             FirstID=1,FirstName=a,SecondId=2,SecondName=b;

             FirstID=3,FirstName=c,SecondId=1,SecondName=d;

 

             transactionScope.Complete();

         }

         catch ()

         {

         }

      }

}

 

如果执行程序后,出现错误通知(是程序运行错误),意味着MSDTC事务协调器没有启动,或者访问的时候发生了安全错误。解决这个问题,执行下列步骤:

a.控制面板---->管理工具。打开配置工具"组建服务"。

b.展开"配置服务",展开"计算机"。

c.右键"我的电脑",然后选择"属性"。

d.单击"MSDTC"标签,如果服务状态是"已停止",把它启动。

e.测试程序,如果还是失败,单击"MSDTC"页中的安全性配置,启动"网络DTC访问",并选择"不需要认证"。

(各操作系统)中可能实现具体步骤不同。

 

 

以上就是我对数据库事物与并发的一点认识。 -----gmark

 

本文转载自:http://blog.csdn.net/mark4ever/article/details/6061002

posted @ 2015-03-17 22:53  James-ping  阅读(233)  评论(0编辑  收藏  举报