一、事务的概念和特点
事务(transaction)是恢复和并发控制的基本单位。
事务的特点
原子性:事务是一个工作单元,要都成功,要么的失败
例子:A付款给B,A余额-100,B余额+100,只能都成功或者都失败,不能把A的钱扣了,而B的没加上
一致性:必须让数据库从一个一致状态到另一个一致状态
例子:还是上边的例子,A和B的总余额没有改变(保持了一致性)
隔离性:事务之间互不影响,并发执行的事务之间互不干扰(通过锁来实现的)
例子:A给B付款 和 C给B付款是两个事务,A付A的,C付C的,互不干扰。
持久性:事务对数据的改变时永久性的。
例子:A付款后,A的余额确实少了100,B的余额多了100(数据库中的记录确实改变了)
二、事务的使用
还是用付款的例子,先添加以下测试数据
--创建一个账户表,添加约束,余额(money)不小于零 create table Tb_bankAcount( Id int identity(1,1) primary key, Name nvarchar(20) not null, Money int not null ) alter table Tb_bankAcount add constraint CK_money CHECK(money>=0) --添加数据 insert into Tb_bankAcount values('A',200) insert into Tb_bankAcount values('B',200)
2.1 sql中使用事务
begin transaction --开启事务 declare @errorCount int=0;--记录错误的变量 update Tb_bankAcount set Money-=500 where Name='A' set @errorCount+=@@ERROR update Tb_bankAcount set Money+=500 where Name='B' set @errorCount+=@@ERROR if @errorCount>0 --有错误就回滚 rollback transaction else --没有错误提交 commit transaction
上边的代码执行时A用户的余额为200-500=-300,由于不满足约束条件(Money>0),执行回滚
2.2 Ado.Net中使用事务(SqlTransaction形式)
1 using (SqlConnection conn = new SqlConnection(connStr)) 2 { 3 //要执行的sql脚本 4 string sqlText = @"update Tb_bankAcount set Money-=100 where Name='A' 5 update Tb_bankAcount set Money+=100 where Name='B'"; 6 conn.Open(); 7 //创建事务 8 SqlTransaction tran = conn.BeginTransaction(); 9 using (SqlCommand com = new SqlCommand(sqlText, conn)) 10 { 11 try 12 { 13 //开启事务 14 com.Transaction = tran; 15 com.ExecuteNonQuery(); 16 //提交事务 17 tran.Commit(); 18 Console.WriteLine("事务执行成功"); 19 } 20 catch (Exception ex) 21 { 22 //回滚事务 23 tran.Rollback(); 24 Console.WriteLine(ex.Message); 25 } 26 } 27 }
上边的代码执行时,由于满足约束条件(Money>0),执行事务提交。
使用SqlTransaction执行事务时,每个事务都是基于SqlConnection的,如果我们的事务要跨越多个程序集或者使用多个数据库时,使用SqlTransaction来实现事务就比较麻烦了,针对这个问题.net 2.0出现了TransactionScope
2.3 Ado.Net中使用分布式事务(TransactionScope形式)
static void Main(string[] args) { //连接字符串 string connstr1 = @"your connctionString1"; string connstr2 = @"your connctionString2"; using (TransactionScope ts = new TransactionScope()) { #region 执行任务1 using (SqlConnection conn1 = new SqlConnection(connstr1)) { using (SqlCommand com = conn1.CreateCommand()) { conn1.Open(); com.CommandText = "delete from t_stu where id=10"; com.ExecuteNonQuery(); } } #endregion #region 执行任务2 using (SqlConnection conn2 = new SqlConnection(connstr2)) { using (SqlCommand com = conn2.CreateCommand()) { conn2.Open(); com.CommandText = "insert into t_stu(stuname,age) values ('zs',22')"; com.ExecuteNonQuery(); } } #endregion //通过ts.Complete()方法进行提交 ts.Complete(); } }
上边的代码十分简单,我们可以看到使用TransactionScope可以轻松的构建分布式的事务模型,conn1和conn2两个连接可以连接不同的数据库。TransactionScope实现了IDispose()接口,我们可以使用using语法糖来自动释放资源。执行TransactionScope时会依此执行TranactionScope的所有代码,当执行到ts.Complete()时表示事务中的任务都执行完成了,进行提交。如果不显示地执行ts.Complete()方法,TransactionScope中代码执行完毕后执行回滚操作。
事务在数据库和业务编码中十分重要,这里只是简单地介绍了.net平台下事务的基本用法,如果有不合理的地方,欢迎指出。