SELECT/UPDATE上的死锁

emp*_*mpi 7 deadlock transactions isolation sql-server-2008

我在SQL Server 2008上的SELECT/UPDATE上遇到了死锁问题.我从这个帖子中读到了答案:SQL Server在select/update或多个选择之间出现死锁,但我仍然不明白为什么会出现死锁.

我在以下测试用例中重新创建了这种情况.

我有一张桌子:

CREATE TABLE [dbo].[SessionTest](
    [SessionId] UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL,
    [ExpirationTime] DATETIME NOT NULL,
    CONSTRAINT [PK_SessionTest] PRIMARY KEY CLUSTERED (
        [SessionId] ASC
    ) WITH (
        PAD_INDEX  = OFF, 
        STATISTICS_NORECOMPUTE  = OFF, 
        IGNORE_DUP_KEY = OFF, 
        ALLOW_ROW_LOCKS  = ON, 
        ALLOW_PAGE_LOCKS  = ON
    ) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[SessionTest] 
    ADD CONSTRAINT [DF_SessionTest_SessionId] 
    DEFAULT (NEWID()) FOR [SessionId]
GO
Run Code Online (Sandbox Code Playgroud)

我首先尝试从此表中选择一条记录,如果记录存在,则将到期时间设置为当前时间加上一些间隔.它使用以下代码完成:

protected Guid? GetSessionById(Guid sessionId, SqlConnection connection, SqlTransaction transaction)
{
    Logger.LogInfo("Getting session by id");
    using (SqlCommand command = new SqlCommand())
    {
        command.CommandText = "SELECT * FROM SessionTest WHERE SessionId = @SessionId";
        command.Connection = connection;
        command.Transaction = transaction;
        command.Parameters.Add(new SqlParameter("@SessionId", sessionId));

        using (SqlDataReader reader = command.ExecuteReader())
        {
            if (reader.Read())
            {
                Logger.LogInfo("Got it");
                return (Guid)reader["SessionId"];
            }
            else
            {
                return null;
            }
        }
    }
}

protected int UpdateSession(Guid sessionId, SqlConnection connection, SqlTransaction transaction)
{
    Logger.LogInfo("Updating session");
    using (SqlCommand command = new SqlCommand())
    {
        command.CommandText = "UPDATE SessionTest SET ExpirationTime = @ExpirationTime WHERE SessionId = @SessionId";
        command.Connection = connection;
        command.Transaction = transaction;
        command.Parameters.Add(new SqlParameter("@ExpirationTime", DateTime.Now.AddMinutes(20)));
        command.Parameters.Add(new SqlParameter("@SessionId", sessionId));
        int result = command.ExecuteNonQuery();
        Logger.LogInfo("Updated");
        return result;
    }
}

public void UpdateSessionTest(Guid sessionId)
{
    using (SqlConnection connection = GetConnection())
    {
        using (SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable))
        {
            if (GetSessionById(sessionId, connection, transaction) != null)
            {
                Thread.Sleep(1000);
                UpdateSession(sessionId, connection, transaction);
            }
            transaction.Commit();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,如果我尝试从两个线程执行测试方法,他们尝试更新相同的记录,我得到以下输出:

[4] : Creating/updating session
[3] : Creating/updating session
[3] : Getting session by id
[3] : Got it
[4] : Getting session by id
[4] : Got it
[3] : Updating session
[4] : Updating session
[3] : Updated
[4] : Exception: Transaction (Process ID 59) was deadlocked 
on lock resources with another process and has been 
chosen as the deadlock victim. Rerun the transaction.
Run Code Online (Sandbox Code Playgroud)

我无法理解使用Serializable Isolation Level会发生什么.我认为首先选择应该锁定行/表,并且不会让另一个选择获取任何锁.该示例使用命令对象编写,但它仅用于测试目的.最初,我正在使用linq,但我想展示简化的例子.Sql Server Profiler显示死锁是密钥锁.我将在几分钟内更新问题,并从sql server profiler发布图表.任何帮助,将不胜感激.我理解这个问题的解决方案可能是在代码中创建关键部分,但我试图理解为什么Serializable Isolation Level不能解决问题.

这里是死锁图: 死锁http://img7.imageshack.us/img7/9970/deadlock.gif

提前致谢.

Sam*_*ron 4

拥有可序列化的事务是不够的,您需要暗示锁定才能使其工作。

可序列化隔离级别通常仍会获取它所能获得的“最弱”类型的锁,以确保满足可序列化条件(可重复读取、无幻像行等)

因此,您正在获取表上的共享锁,稍后您(在可序列化事务中)尝试升级到更新锁。如果另一个线程持有共享锁,则升级将失败(如果没有其他线程持有共享锁,则升级将起作用)。

您可能想将其更改为以下内容:

SELECT * FROM SessionTest with (updlock) WHERE SessionId = @SessionId
Run Code Online (Sandbox Code Playgroud)

这将确保执行 SELECT 时获取更新锁(因此您不需要升级锁)。