数据库查询不遵守事务

时间:2018-06-21 14:41:15

标签: .net-core dapper

我有一些代码可用于一次从数据库中提取项目。每次获取时,它都会更新该行的状态以指示该行已被检索,从而不会多次检索同一行。

以下代码是从提供Web API的后端服务运行的。有时,当有多个请求进入时,它将返回同一行(具有相同ID的任务)。

我的印象是,围绕它进行事务将意味着一次运行的更新将阻止该行被第二次查询返回。

任何帮助将不胜感激。

public async Task<TaskDetail> GetTask()
{
    using (var db = new SqlConnection(""))
    {
        using (var tran = db.BeginTransaction())
        {
            try
            {

                var sql = $@"
                    SELECT TOP 1 * FROM
                        (SELECT TOP 150 t.*
                        FROM Task t
                        INNER JOIN TaskStatus ts ON t.Id = ts.TaskId AND ts.Status = @taskStatus) t
                    ORDER BY NEWID();";

                    var chosen = await db
                        .QuerySingleOrDefaultAsync<TaskDetail>(
                        sql,
                        param: new
                        {
                            taskStatus = TaskStatusEnum.Ready
                        },
                        transaction: tran
                    );
                    if (chosen == null)
                    {
                        throw new InvalidOperationException();
                    }
                    var expiry = await db.ExecuteAsync("UPDATE TaskStatus SET Status = @status WHERE TaskId = @taskId", new {status = TaskStatusEnum.Done, taskId = chosen.TaskId}, tran);

                    tran.Commit();

                    return chosen;
                }
                catch
                {
                    tran.Rollback();
                    throw;
                }
            }
        }
}

2 个答案:

答案 0 :(得分:1)

一些错误的配置可能是原因;

1-检查SQL Server隔离级别,注意脏读。 2-确保正确处理Web API中的错误,因为尤其是交易错误未正确显示。 3-并且,请从代码中删除t-sql:)

答案 1 :(得分:1)

选择查询不会阻止来自其他事务的行数据,不同事务中的两个选择将同时执行。您可以尝试在该行上设置会话ID,然后选择它。

编辑:希望此帮助在执行更新时应为其他交易屏蔽该行

public async Task<TaskDetail> GetTask()
{
    using (var db = new SqlConnection(""))
    {
        using (var tran = db.BeginTransaction())
        {
            try
            {

            var mySessionId = Guid.NewGuid();

            var sql = $@"
                UPDATE TaskStatus SET Status = @status, SessionId = @mySessionId WHERE TaskId in 
                    (SELECT TOP 1
                    t.Id
                    FROM Task t
                    INNER JOIN TaskStatus ts ON t.Id = ts.TaskId AND ts.Status = @taskStatus ORDER BY NEWID());";

                await db
                .QuerySingleOrDefaultAsync<TaskDetail>(
                    sql,
                    param: new
                    {
                        taskStatus = TaskStatusEnum.Ready
                        status = TaskStatusEnum.InProgress,
                        mySessionId = mySessionId
                    },
                    transaction: tran
                );


                var sql = $@"
                    SELECT TOP 150 t.*
                    FROM Task t
                    INNER JOIN TaskStatus ts ON t.Id = ts.TaskId AND ts.Status = @taskStatus
                    WHERE ts.SessionId = @mySessionId;";

                    var chosen = await db
                        .QuerySingleOrDefaultAsync<TaskDetail>(
                        sql,
                        param: new
                        {
                            taskStatus = TaskStatusEnum.InProgress,
                            mySessionId = mySessionId
                        },
                        transaction: tran
                    );
                if (chosen == null)
                {
                    throw new InvalidOperationException();
                }
                var expiry = await db.ExecuteAsync("UPDATE TaskStatus SET Status = @status WHERE TaskId = @taskId", new {status = TaskStatusEnum.Done, taskId = chosen.TaskId}, tran);

                tran.Commit();

                return chosen;
            }
            catch
            {
                tran.Rollback();
                throw;
            }
        }
    }
}