HI ALL,
   大家好,现在遇到了一点小疑惑。之前的SQLServer开发知识中讲到单条Update语句在SQLServer中按照隐式事务来处理的。我现在遇到这样一个问题,C#开发的多线程客户端联入SQLServer2005数据库,执行一个表的update 语句,Update的是单条记录。就是
update table set field=1 where 唯一ID=1这个样子。
在测试程序中,启动30个线程实例做系统压力测试,每个线程循环更新1000次表中的某一条记录,更新内容为一数值列+1操作。这时sp_lock观察数据库中的在线程执行过程中会产生大约20到30个锁的信息,锁的类型有Key、PAG、TAB。在全部测试线程执行结束后,锁会被自动释放掉。如果在update语句中显式的写入Begin tran commit rollback tran后,通过sp_lock观察不到锁的信息。
请问,前一种情况是否可以判断为锁表,后一种则没有锁表。另外,隐式与显式事务的区别有这么大吗?期望专家指教!
问这个问题是由于在测试过程中发现了有锁导致系统异常的状况产生。谢谢各位。

解决方案 »

  1.   

    显式事务
    显式事务可以显式地在其中定义事务的启动和结束。在 Microsoft® SQL Server™ 的早期版本中,显式事务也称为用户定义或用户指定的事务。DB-Library 应用程序和 Transact-SQL 脚本使用 BEGIN TRANSACTION、COMMIT TRANSACTION、COMMIT WORK、ROLLBACK TRANSACTION 或 ROLLBACK WORK Transact-SQL 语句定义显式事务。BEGIN TRANSACTION为连接标记显式事务的起始点。COMMIT TRANSACTION 或 COMMIT WORK如果没有遇到错误,可使用该语句成功地结束事务。该事务中的所有数据修改在数据库中都将永久有效。事务占用的资源将被释放。ROLLBACK TRANSACTION 或 ROLLBACK WORK用来清除遇到错误的事务。该事务修改的所有数据都返回到事务开始时的状态。事务占用的资源将被释放。还可以在 OLE DB 中使用显式事务。调用 ITransactionLocal::StartTransaction 方法可启动事务。将 fRetaining 设置为 FALSE,则调用 ITransaction::Commit 或 ITransaction::Abort 方法结束事务时,不会自动启动另一事务。在 ADO 中,对 Connection 对象使用 BeginTrans 方法可启动隐性事务。若要结束该事务,可调用该 Connection 对象的 CommitTrans 或 RollbackTrans 方法。ODBC API 不支持显式事务,只支持自动提交和隐性事务。显式事务模式持续的时间只限于该事务的持续期。当事务结束时,连接将返回到启动显式事务前所处的事务模式,或者是隐性模式,或者是自动提交模式。
      

  2.   

    隐式事务又称自动提交事务,就这么点区别,说多点就是下面这一大段 
    事务是SQL Server防止你的数据出现不一致状态的基础结构.我们已经学习过事务是如 
    何工作的.在这一节中,我们将学习在SQL Server开发环境下如何让事务为你工作.S Q L 
    S e r v e r中事务有两种,它们是隐式事务和显式事物. 
    21.3.1 隐式事务 
    隐式事务是SQL Server为你而做的事务.隐式事务又称自动提交事务.如果运行一条 
    I N S E RT语句,SQL Server将把它包装到事务中,如果此I N S E RT语句失败,SQL Server将回滚 
    或取消这个事务.每条S Q L语句均被视为一个自身的事务.例如在程序清单2 1 - 2中,有四条 
    I N S E RT语句.第一,二,四条是有效的,第三条语句是无效的.因为它违反了该表中有关作 
    者标识必须唯一的约束.当程序运行时,第一,二,四条语句执行成功并插入表中.第三条 
    第2 1学时SQL Serv e r编程2 0 5 
    下载 
    语句失败并回滚. 
    程序清单21-2 隐式事务 
    在日常操作中,你可能依赖于隐式事务.在第三方应用程序中,这些应用程序的开发人 
    员则可能使用显式事务. 
    21.3.2 显式事务 
    显示事务是一种由你自己指定的事务.这种事务允许你自己决定哪批工作必须成功完成, 
    否则所有部分都不完成.为了给自己的事务定界,可以使用关键字BEGIN TRANSACTION和 
    ROLLBACK TRANSACTION或COMMIT TRANSACTION. 
    BEGIN TRANSACTION—这个关键词用来通知SQL Server一个事务就要开始了. 
    BEGIN TRANSACTION后面发生的每一条S Q L语句都是同一个事务中的一部分. 
    ROLLBACK TRANSACTION—这个关键词用来通知SQL Server自BEGIN TRANSACTION 
    后的所有工作都应取消,对数据库中任何数据的改变都被还原,任何已经创建或删除的对 
    象被清除或恢复. 
    COMMIT TRANSACTION—这个关键词用来通知SQL Server自BEGIN TRANSACTION 
    后的全部工作都要完成并成为数据库的一个永久性部分.在同一个事务中,你不能同时 
    使用ROLLBACK TRANSACTION和COMMIT TRANSACTION. 
    你必须意识到,即使你的脚本中有错误,而你又让SQL Server提交事务,该事务也将执 
    行.如果你打算依赖于现实事务保证数据完整性,必须在脚本中建立错误检查机制.程序清 
    单2 1 - 3中的代码显示了运用显式事务来回滚对e m p l o y e e s表的改动. 
      

  3.   

    事务的隔离等级
    语法:
    SET  TRANSACTION  ISOLATION  LEVEL
    {READ UNCOMMITTED
    | READ COMMITTED
    | REPEATABLE READ
    | SERIALIZABLE
    }
    1. READ UNCOMMITTED:完全没有隔离效果,读取到的数据随时都可能被别人更改事删除。
    2. READ COMMITTED:不允许读取尚未COMMIT的数据。因为该数据被改动的几率很大。不过,在读取完数据后就和READ UNCOMMITTED一样,不会在乎该数据是否还会被别人更改。因此每次读取到的数据可能会不相同。
    3. REPEATABLE READ:事务中所读取到的数据,将不允许别人更改或删除,以保证在事务中每次都可以读取到相同的内容。但别人仍然可以在该数据表中新增记录。
    4. SERIALIZABLE:数据表全部锁定,不允许别人来修改、删除或新增数据。由于必须等到事务完成后,其他事务才能使用这些数据表,因此Serializable的并发性最低,要使用相同数据的事务必须一个个顺序地进行。
      

  4.   

    [Quote=引用 7 楼 hdhai9451 的回复:]
    事务的隔离等级 
    语法: 
    SET  TRANSACTION  ISOLATION  LEVEL 
    {READ UNCOMMITTED 
    | READ COMMITTED 
    | REPEATABLE READ 
    | SERIALIZABLE 

    1. READ UNCOMMITTED:完全没有隔离效果,读取到的数据随时都可能被别人更改事删除。 
    2. READ COMMITTED:不允许读取尚未COMMIT的数据。因为该数据被改动的几率很大。不过,在读取完数据后就和READ UNCOMMITTED一样,不会在乎该数据是否还会被别人更改。因此每次读取到的数…
    [/Quote学习
      

  5.   

    请问,前一种情况是否可以判断为锁表,后一种则没有锁表。另外,隐式与显式事务的区别有这么大吗?期望专家指教! -->>应该说在进行更新表的时候,都会自动加锁的。使用显示事务也是这样的,所以后一种也是加了锁的。
    个人认为:使用隐式事务产生锁是因为:当有大量的DDL 和DML命令执行时会自动开始,并一直保持到用户明确提交为止。这样的话在没有提交之前会申请同一个资源,那么自然会产生锁冲突,使用sp_lock可以查看到。而使用显式事务,每个事务都会在执行后进行提交,而其他的事务处于等待状态(也是申请相同资源),因为每次只有一个事务执行,没有产生锁冲突。
      

  6.   

    是的,这样要显式事务,并指定事务的隔离等级
    有关这方面的资料可以参考Roy_88写的一篇关于死锁的文章;
    http://topic.csdn.net/u/20080721/20/15a60db6-34b5-4ea1-b392-11c42270aaab.html
      

  7.   

    第一种情况应该是C#多线程竞争时产生的自动锁吧。
    对于插入和更新操作,原则上来说,如果存在多用户同时访问的情况,那么应当在SQL端用事务+事务隔离级来控制,防止出现同一操作出现不同结果的现象。清楼主多多注意这一点。另外,隐式的事务其实没有那么强的功能,只是更新和删除即时生效而已。
      

  8.   

    是否声明显式事务均会加锁 如果显式事务都不产生锁/锁冲突的话 那会是一场革命啊默认的事务隔离等级在语句完成后即释放相关锁 如果你的语句瞬间执行完毕 sp_lock监测是难度的 可以设置更严格的事务隔离等级 如SERIALIZABLE 并嵌入waitfor语句进行测试你的第二个测试还是在压力下进行的吗?
      

  9.   

    我也有这方面的疑问,在程序里写单条 sql执行的时候,是否有必要加事务。当然不考虑在执行语句的时候还有别的东西同步运行的情况。仅仅就单条语句而言。另外 还有个事儿:
    怎样防止出现统一操作不同结果?能具体点吗?如果是在数据库端写事务,那在程序里能引用吗?
      

  10.   

    只要update,肯定有锁的,强制写进事务观察不到锁?是不是观测粒度不够哦,加wait for试试
      

  11.   

    你使用隐式事务,执行一万次,SQL就给你启动、提交一万次事务,你如果显式启动事务,然后执行一万次,只有一个事务。
    所以,即使是单条更新,如果需要连续操作多条记录的话,还是得显式启动事务,速度更快。这点你自己可以测试一下。
      

  12.   

    下面的链接对楼主也许有用
    http://technet.microsoft.com/zh-cn/library/ms190230.aspx
      

  13.   


    隐式事务又称自动提交事务,就这么点区别,说多点就是下面这一大段 隐式事务根本就不同于自动提交事务!!!!使用 API 函数和 Transact-SQL 语句,可以在 SQL Server Database Engine 实例中将事务作为显式、自动提交或隐式事务来启动。显式事务:通过 API 函数或通过发出 Transact-SQL BEGIN TRANSACTION 语句来显式启动事务。自动提交事务:数据库引擎 的默认模式。每个单独的 Transact-SQL 语句都在其完成后提交。不必指定任何语句来控制事务。隐式事务:通过 API 函数或 Transact-SQL SET IMPLICIT_TRANSACTIONS ON 语句,将隐性事务模式设置为打开。下一个语句自动启动一个新事务。当该事务完成时,下一个 Transact-SQL 语句又将启动一个新事务。
    对于lz的问题,默认的锁模式都是数据库引擎决定的,除非你手动锁表,所以不会是你说的前一种情况锁表了,后一种没锁表
    两张情况的默认锁行为都是一样的,就是根据你的update语句来决定使用行锁、键范围锁、页锁、表所。与事务模式无关好了,现在我来解释lz出现的情况,应该是lz的数据库或链接被直接或间接设置为隐式事务模式,处于这种模式下,
    UPDATE会开启一个事务,直到事务显示或隐式终止,或链接关闭。lz说有30个锁,那么应该是LZ创建了30个线程,每一个线程单独使用一个链接,一个线程里面公用一个打开的链接,直到该线程结束。
    没错吧?那么基于上述描述,要想快速释放锁,你可以在连接打开之后使用SET IMPLICIT_TRANSACTIONS ON立即更改你的选项来关闭隐式事务模式,
    或者在update语句后面加上 COMMIT TRANSACTION、COMMIT WORK、ROLLBACK TRANSACTION 或 ROLLBACK WORK 语句来结束事务,
    或者直接关闭链接然后再次打开
      

  14.   

    hi,all.感谢各位的分析与热情回复。因为前段时间出差没有及时的响应。
    这个问题令人迷惑的地方是。多线程中执行了一句update语句,未额外填加任何的事务代码。
    在夜间通过sp_lock观察数据库时,发现存在不少量的锁。这也很可能是代码中有争资源的现象造成了锁表。
    在编写测试代码时,同时开启30到100个线程来update数据表,后台观测在线程执行过程中,sp_lock是可以观察到锁记录,在所有线程执行完成后,锁全部释放。
    同样的测试方式,增加了事务部分的代码,在线程执行过程中,后台sp_lock观察数据库,未发现锁记录。
    根据手头的资料与信息,认为在多线程的情况下还是设置事务级别,显示编写事务代码更好一些。
    在此发帖,供各位有兴趣的朋友们参考了解。
    Thanks all.
      

  15.   


    唉,技术实力决定思维真是一点都没错,
    你已经完全认定一个有锁发生,一个没锁发生,
    判定的只是你自己有没在后台查看到,哈哈,其实两个都有,如果语句和数据都一样,那么锁类型都是一样
    只不过一个释放的快,一个释放的慢而已,如果你的代码没有设置其他信息,我敢保证,你把代码贴出来,别人的运行结果和你的绝对不一样,
    就是都在后台看不到锁信息,因为都会很快就释放了或者你也可以重新新建一个数据库,新建一个表,在测试,保证你看不到了。哈哈53楼已经说的很清楚了,不知道你看的懂还是看不懂就你现在不改代码,创建100个线程,那就会有100个锁,创建1000个线程就有1000个锁
    但是如果你所有线程都公用一个数据库连接的话,保证你只能看到1个,连接关闭后你一个都看不到还有,真正监视一条语句会引发多少锁,什么类型的锁,也不是你这么测试的,
    这样测试的结果:不准确呵呵,光知道sp_lock有什么作用是远远不够的,应该了解相关实现
      

  16.   

    应该这样来看待这个问题。我猜你的C#语句大概是这样的吧for (int i=0;i<1000;i++)
    {
        command.CommandText="Update xxx set xxx Where ID=...";
        command.ExecuteNoQuery();
    }在这种模式下,这1000条UPDATE语句是作为同一个transaction内提交的,因为事务执行时间长,所以你可以
    用sp_lock监测的到。
    而显示加入begin transaction,则每一次执行ExecuteNoQuery()都会提交一次事务,因为事务执行时间短,所以你可以sp_lock就看不到锁了。for (int i=0;i<1000;i++)
    {
        command.CommandText="begin tran Update xxx set xxx Where ID=... commit tran";
        command.ExecuteNoQuery();
    }
      

  17.   

    我的理解是不论加不加BEGIN TRANS都会加锁的,从概念上理解,在更新表时当然别人不能更新喽,update时系统应该会添加共享拍他锁就是读可以INSERT和update是不能同时做的。这当然是必要的不然会有很多脏数据的