序言
为什么需要事务?
当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性, 所以, 对于不同的事务,采用不同的隔离级别会有不同的结果。
如果没有事务隔离,会出现什么样的情况?
http://blog.itpub.net/26736162/viewspace-2638951/
事务
事务就是作为一个逻辑工作单元的SQL语句,如果任何一个语句操作失败那么整个操作就被失败,以后操作就会回滚到操作前状态,或者是上个节点。为了确保要么执行,要么不执行,就可以使用事务。
Sql中使用事务
测试数据
--创建一个账户表,添加约束,余额(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)
测试转账事务
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
Ado.Net中使用事务(SqlTransaction形式)
using (SqlConnection conn = new SqlConnection(connStr)) { //要执行的sql脚本 string sqlText = @"update Tb_bankAcount set Money-=100 where Name='A' update Tb_bankAcount set Money+=100 where Name='B'"; conn.Open(); SqlTransaction tran = conn.BeginTransaction(); using (SqlCommand com = new SqlCommand(sqlText, conn)) { try { //开启事务 com.Transaction = tran; com.ExecuteNonQuery(); //提交事务 tran.Commit(); Console.WriteLine("事务执行成功"); } catch (Exception ex) { //回滚事务 tran.Rollback(); Console.WriteLine(ex.Message); } } }
上边的代码执行时,由于满足约束条件(Money>0),执行事务提交。
使用SqlTransaction执行事务时,每个事务都是基于SqlConnection的,如果我们的事务要跨越多个程序集或者使用多个数据库时,使用SqlTransaction来实现事务就比较麻烦了,针对这个问题.net 2.0出现了TransactionScope
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中代码执行完毕后执行回滚操作。
其他测试代码
提交事务
BEGIN TRAN Tran_Money; INSERT INTO [dbo].[Money]([Name],[Money])VALUES('沐风',100) COMMIT TRAN;
回滚事务
BEGIN TRAN Tran_Money; INSERT INTO [dbo].[Money]([Name],[Money])VALUES('沐风',100) ROLLBACK TRAN;
转账事务
BEGIN TRAN Tran_Money; --开始事务 DECLARE @tran_error INT; SET @tran_error = 0; BEGIN TRY UPDATE dbo.Money SET Money = Money - 30 WHERE Name = '张三'; SET @tran_error = @tran_error + @@ERROR; --测试出错代码,看看张三的钱减少,李四的钱是否会增加 --SET @tran_error = 1; UPDATE dbo.Money SET Money = Money + 30 WHERE Name = '李四'; SET @tran_error = @tran_error + @@ERROR; END TRY BEGIN CATCH PRINT '出现异常,错误编号:' + CONVERT(VARCHAR, ERROR_NUMBER()) + ',错误消息:' + ERROR_MESSAGE(); SET @tran_error = @tran_error + 1; END CATCH; IF ( @tran_error > 0 ) BEGIN --执行出错,回滚事务 ROLLBACK TRAN; PRINT '转账失败,取消交易!'; END; ELSE BEGIN --没有异常,提交事务 COMMIT TRAN; PRINT '转账成功!'; END
锁
锁是实现事务的关键,锁可以保证事务的完整性和并发性。数据库中的锁也是为了解决在并发访问时出现各种冲突的一种机制。
锁的目的是什么?
主要解决多个用户同时对数据库的并发操作时会带来以下数据不一致的问题:
- 丢失更新,同时修改一条数据
- 读脏,A修改了数据后,B读取后A又取消了修改,B读脏
- 不可重复读,A用户读取数据,随后B用户读取该数据并修改,此时A用户再读取数据时发现前后两次的值不一致
- 还有一种是幻读,这个情况好像不多。
并发控制的主要方法是封锁,锁就是在一段时间内禁止用户做某些操作以避免产生数据不一致
锁的粒度有哪些?
- 数据库锁:锁定整个数据库,这通常发生在整个数据库模式改变的时候。
- 表锁:锁定整个表,这包含了与该表相关联的所有数据相关的对象,包括实际的数据行(每一行)以及与该表相关联的所有索引中的键。
- 区段锁:锁定整个区段,因为一个区段由8页组成,所以区段锁定是指锁定控制了区段、控制了该区段内8个数据或索引页以及这8页中的所有数据行。
- 页锁:锁定该页中的所有数据或索引键。
- 行或行标识符:虽然从技术上将,锁是放在行标识符上的,但是本质上,它锁定了整个数据行。