分布式死锁的一个例子
有时候你会发现你的程序没有响应了,而此刻你在SQL server里面发现对应的线程在等待网络IO如下所示:
select session_id,blocking_session_id,wait_type,wait_time,wait_resource,* from sys.dm_exec_requests where session_id>50
上面图示 spid 57被spid55阻塞了。Spid55等待网络IO (ASYNC_NETWORK_IO). 等待网络IO的意思是等待客户端程序来拿数据, 也就是客户端拿数据不够快。在很多情况下,这个假设是对的。比如客户端的程序每次只fetch一条数据逐条处理,就会导致网络IO等待。
但是如果有别的spid等待这个等待网络IO的spid,如上面的spid57等待spid55,那么要小心你是不是碰到了分布式死锁。 分布式死锁是比较难检测的。 光SQL server 自己不能形成分布式死锁。SQL server 和客户端程序一起才能形成分布式死锁。下面就是分布式死锁的一个例子:
上面的图说明,客户端程序和SQL server的资源等待形成了一个环,这个就叫分布式死锁。 造成上面分布式死锁的代码如下:
SqlDataReader rdr = null;
SqlConnection con = null;
SqlCommand cmd = null;
try
{
// Open connection to the database
string connStr="Data Source=TCP:myserver\\sql2008;Integrated Security=SSPI;database=AdventureWorks;pooling=true; ";
con = new SqlConnection(connStr);
con.Open();
string CommandText = "select id, name,age from test";
cmd = new SqlCommand(CommandText);
cmd.Connection = con;
// Execute the query
rdr = cmd.ExecuteReader();
using (SqlConnection connection2 = new SqlConnection(connStr))
{
connection2.Open();
while (rdr.Read())
{
Console.WriteLine(rdr[0].ToString());
SqlCommand cmdUpdate = new SqlCommand("update test set age=age+10 where age>" + rdr[2].ToString());
cmdUpdate.Connection = connection2;
cmdUpdate.CommandTimeout = 0;
cmdUpdate.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
// Print error message
Console.WriteLine(ex.Message);
}
finally
{
// Close data reader object and database connection
if (rdr != null)
rdr.Close();
if (con.State == ConnectionState.Open)
con.Close();
}
}
形成分布式死锁的关键点是rdr.read()没有保证一次就从SQL服务器把数据全部拿完,而是需要的时候才拿。这样就容易导致网络IO等待。上面的例子中,第一个连接的select语句因为等待网络IO,可能有些锁在page上面,而第二个连接有可能和那个锁有冲突,而第一个连接的rdr.read()又有对第二个连接有依赖(即在while(rdr
.read() 语句里面执行第二个连接的update语句),这样就导致循环等待即分布式死锁的产生。
如何解决这个分布式死锁呢?办法由很多种。其中比较容易的一种就是使用dataset 代替data reader,这样一次性把数据读下缓存在客户端即可。
附录:
要执行上面的程序,数据库端需要执行如下的script建立相应的测试表:
use AdventureWorks
go
create table test(id int primary key not null,name varchar(120),age int)
go
declare @i int
set @i=0
while (@i<10000)
begin
insert into test values(@i,'name',RAND()*100);
set @i=@i+1
end
Comments
- Anonymous
September 19, 2012
刚刚我的留言怎么看不到呢?我的邮箱是xingjunwu@vancl.cn - Anonymous
September 19, 2012
你好:我是凡客诚品的一个开发工程师,我们可能遇到类似的问题:1、SQL经常超时,根据DBA检测,CPU运行时间不超过200毫秒,Duration却在30秒以上2、我们用的是WCF技术3、即使是查字典表,真个表四个字段,22条数据也会有最大6秒的峰值4、取数据大多用的是reader,大概占比85%请帮忙分析一下,是否可以基本确定是分布式死锁造成,如果是,除了使用DataSet还有什么其他解决方案吗?