.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