日期:2014-05-17  浏览次数:20618 次

一个关于事务和锁的问题
有两个表: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.没有必要加事务
因为一个语句就是一个隐式事务,你的语句,从 if 开始,到 insert 结束,只是一条语句,因此没必要加事务.
2.你的这个实验没意义
你用了begin trean,可是事后不提交,然后再用同样的语句,那它当然会死锁,这是你自己制造出来的死锁,而实际上,由于隐式事务执行后立即提交了,因此不会遇到这么严重的死锁.
------解决方案--------------------
前一个插入语句,执行完毕,事务未释放,tab1被插入操作锁住了,后一句来一个比较,先执行了(select count(*) from tab2 where tab1_id=1),锁住了tab2,接着再要查(select maxRow from tab1 where id=1),但因为tab1锁住了,只好等待,那不就是死锁了嘛.而且两个表分别被两次执行锁住了.
在客户端程序中先用select 查询,取回结果到客户端后,再判断是否能插入,再插入.这样的操作绝对不能加事务的.因为它们是两个不同的连接.后面的插入语句,本身就被前一句锁住了,根本没法操作.
这样的需求可以用存储过程来处理,在存储过程中传入要插入的东西,判断是否能插入,是插入便执行,否则返回一个不能执行的参数,让客户端去决定如何处理.


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

------解决方案--------------------
lz,tab1 和tab2 有外键关系吧
------解决方案--------------------
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
加上这个再试吧
------解决方案--------------------
楼主写个约束就得了,插入不进去就报错了