分布式死锁的一个例子

有时候你会发现你的程序没有响应了,而此刻你在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