【问题标题】:Preventing orphaned sql queries from website防止来自网站的孤立 sql 查询
【发布时间】:2018-05-09 20:56:28
【问题描述】:

我们有一个网站存在性能问题,存储过程执行是瓶颈。造成这种情况的原因之一是其中一些存储过程在页面加载时启动。

假设数据库负载很高,用户对页面上的报告加载时间感到沮丧,并反复刷新页面,开始执行同一存储过程的新执行。

这是我们用来执行存储过程和管理连接的代码:

using (SqlCommand cmd = CreateCommand(qry, CommandType.StoredProcedure, args))
            {
                SqlDataAdapter adapt = new SqlDataAdapter(cmd);
                DataSet ds = new DataSet();
                adapt.Fill(ds);
                return ds;
            }

即使用户离开页面或刷新页面,旧的执行仍会访问数据库,从而产生新的执行。

两种可能的解决方案:

  • 当生成存储过程的用户离开本应显示的页面时,以某种方式终止存储过程的执行。
  • 更进一步,尝试保持相同的连接实例,这样如果用户点击相同的页面,它将等待上一个存储过程的结果,然后将这些结果显示在页面上。

这些事情的常见做法是什么,它们是如何实施的?当用户离开调用它的页面时,我如何至少杀死存储过程?

【问题讨论】:

  • 如何检测用户离开?您需要将某种取消令牌发送到 asp.net 来处理
  • 如果您无法修复报告的性能,请考虑使用队列或类似系统。如果您确实需要检测断开连接,可以考虑使用信号器类型的场景,在该场景中,在从队列中执行长时间运行的报告任务之前检查用户是否还活着。
  • @JoePhillips 我不知道。我是前端工作的新手,所以我考虑了这个问题的这一部分。

标签: c# sql asp.net sql-server


【解决方案1】:

我最终使用了带有取消令牌的异步操作。


MVC 动作方法本身:

[AsyncTimeout(600000)] // Necessary to avoid the too-short 45 second default timeout 
                       // (which also results in successful cancellation of the sql command 
                       // by the way in case that's something you want)
[Route("my-report")]
public async Task<string> MyReport(CancellationToken cancellationToken) 
{
    // These two lines are a workaround - necessary for page refreshes to trigger cancellation in MVC 5
    CancellationToken disconnectedToken = Response.ClientDisconnectedToken;
    var token = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, disconnectedToken).Token;

    // get the data
    var response = await dbService.ExecDataSetProcAsync("stored_proc_name", cancellation token, storedProcedureArguments);

    // returning the page with whatever data
    return response;
}

存储过程执行和取消逻辑: (我排除了在 CreateCommand() 中构建 SQLCommand 的逻辑,以保持示例简洁)

public async Task<DataSet> ExecDataSetProcAsync(string qry, CancellationToken cancellationToken, params object[] args)
        {
            using (SqlCommand cmd = CreateCommand(qry, CommandType.StoredProcedure, args))
            {
                DataSet ds = new DataSet();

                await Task.Run(() =>
                {
                    SqlDataAdapter adapt = new SqlDataAdapter(cmd);
                    cancellationToken.Register(() => cmd.Cancel());

                    try
                    {
                        adapt.Fill(ds);
                    }
                    catch (SqlException e)
                    {
                        // Canceling the sqlCommand causes an exception.  This is a workaround 
                        // to catch it, in case you don't want to be throwing the exception.
                        if (!cancellationToken.IsCancellationRequested)
                        {
                            throw e;
                        }
                    }
                });

                return ds;
            }
        }

【讨论】:

    猜你喜欢
    • 2015-07-19
    • 2022-11-24
    • 2013-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多