有两个表:tab1有字段id和maxRow  tab2有字段tab1_id和foo
要执行代码
if (select count(*) from tab2 where tab1_id=1)<(select maxRow from tab1 where id=1)
insert tab2 values(1,'a')   
                               
作用是在insert tab2的时候,查一下tab1 里规定的数量,如果没达到就可以插入。因为怕insert之前、select之后,正好有别的连接也在插入记录。所以可能应该设隔离级别为串行。不过在简单实验的后就发现了问题。
实验如下:
首先在连接1上执行:
set transaction isolation level serializable                                        --语句1
begin tran
if (select count(*) from tab2 where tab1_id=1)<(select maxRow from tab1 where id=1)
insert tab2 values(1,'a') 
        --语句2
执行结果:(1行受影响)
然后在连接2和连接3上执行 语句1 到 语句2,同样没有提交。

下面是用活动监视器查看的锁的情况:
图中:2073058421是tab1的id,2089058478是tab2的id

在连接1上commit tran,显示“命令已成功完成。”
连接3 显示“(1行受影响)”
连接2 显示
"消息1205,级别13,状态56,第5 行
事务(进程ID 54)与另一个进程被死锁在锁资源上,并且已被选作死锁牺牲品。请重新运行该事务。"
这时候尚未提交的连接3锁的情况如下图。我的问题:
1、死锁是否因为连接2和连接3请求锁的顺序不同造成的吗?
2、S和IS锁应该是互相兼容的,是否因为接下来在tab2上的锁要升级为X锁,所以造成死锁?
3、要达到限制tab2中记录条数的目的应该怎么办?目前想到的办法是用在tab1里增加一个计数字段和check约束,在服务器这边写存储过程,然后把 insert tab2和update tab1放在一个事务里。这样可以吗?还有别的方法吗?

解决方案 »

  1.   

    1.没有必要加事务
    因为一个语句就是一个隐式事务,你的语句,从 if 开始,到 insert 结束,只是一条语句,因此没必要加事务.
    2.你的这个实验没意义
    你用了begin trean,可是事后不提交,然后再用同样的语句,那它当然会死锁,这是你自己制造出来的死锁,而实际上,由于隐式事务执行后立即提交了,因此不会遇到这么严重的死锁.
      

  2.   

    做这个实验是因为有人在开发web程序时,在java写的db客户端上,先做了select查询,取回结果后,判断能不能插入,然后用insert插入,也就是说sql语句不是我这么写的。在网络延迟稍大一点的地方,模拟多个客户端并行执行的话,就会有问题,就是说记录数超过了限制。他加了隔离级别为串行的事务以后,就会报死锁。我对这些理解得不怎么样,所以想深入了解一下,才有了这个实验。至于sql语句,我认为这么写应该更合理一点,虽然我知道隐式事务,倒没往上想,还是理解得不深刻。希望大家能帮我入门:)
      

  3.   

    还是不太理解为什么会死锁,怎么提交同样的语句,对资源的请求次序会不同呢?似乎连接2先请求的是tab2上的锁,而连接3先请求的是表tab1上的锁哇。
      

  4.   

    前一个插入语句,执行完毕,事务未释放,tab1被插入操作锁住了,后一句来一个比较,先执行了(select count(*) from tab2 where tab1_id=1),锁住了tab2,接着再要查(select maxRow from tab1 where id=1),但因为tab1锁住了,只好等待,那不就是死锁了嘛.而且两个表分别被两次执行锁住了.
    在客户端程序中先用select 查询,取回结果到客户端后,再判断是否能插入,再插入.这样的操作绝对不能加事务的.因为它们是两个不同的连接.后面的插入语句,本身就被前一句锁住了,根本没法操作.
    这样的需求可以用存储过程来处理,在存储过程中传入要插入的东西,判断是否能插入,是插入便执行,否则返回一个不能执行的参数,让客户端去决定如何处理.
      

  5.   

    前一个插入语句,执行完毕,事务未释放,tab1被插入操作锁住了,后一句来一个比较,先执行了(select count(*) from tab2 where tab1_id=1),锁住了tab2,接着再要查(select maxRow from tab1 where id=1),但因为tab1锁住了,只好等待,那不就是死锁了嘛.而且两个表分别被两次执行锁住了.
    在客户端程序中先用select 查询,取回结果到客户端后,再判断是否能插入,再插入.这样的操作绝对不能加事务的.因为它们是两个不同的连接.后面的插入语句,本身就被前一句锁住了,根本没法操作.
    这样的需求可以用存储过程来处理,在存储过程中传入要插入的东西,判断是否能插入,是插入便执行,否则返回一个不能执行的参数,让客户端去决定如何处理.
      

  6.   

    楼上-_- !
    写了个java客户端,把程序分成3句: select tab1... (延时) select tab2...(延时) insert tab2...
    开了两个线程执行,每个都开事务,隔离级别为串行,低级别的隔离级别不会出现死锁。
    下面的图片是用 sql profiler 捕获的。deadlock chain 和 deadlock 条目显示的都是tab2上的IX锁(两个线程都是如此), 似乎和select没关系?
      

  7.   

    lz,tab1 和tab2 有外键关系吧
      

  8.   

    set transaction isolation level serializable --语句1
    begin tran
    if (select count(*) from tab2 where tab1_id=1)<(select maxRow from tab1 where id=1)
    insert tab2 values(1,'a')
    commit
    加上这个再试吧
      

  9.   

    我知道要怎么实现啦。这个问题主要是不太清楚sqlserver的事务隔离级别和insert、select等等对dbms用什么锁有什么影响,还有就是死锁的问题什么的。现在正在研究哈,有结果发上来,让大家指正
      

  10.   

    上面提到的那个java程序是正常提交的,不过如果要用分析器捕获 锁的请求、释放什么的产生的记录很多,要慢慢看呀