【问题标题】:Performance of a sequence of SQL queries using SqlDataReader使用 SqlDataReader 执行一系列 SQL 查询
【发布时间】:2013-09-18 22:30:59
【问题描述】:

我需要在 C# 中执行一系列 SQL 查询。查询类似于以下内容:

...
select top 1 [time],[value] from [table] where [time]<='T0' and [param]='1' order by [time] desc
select top 1 [time],[value] from [table] where [time]>='T0' and [param]='1' order by [time] asc
select top 1 [time],[value] from [table] where [time]<='T1' and [param]='1' order by [time] desc
select top 1 [time],[value] from [table] where [time]>='T1' and [param]='1' order by [time] asc
select top 1 [time],[value] from [table] where [time]<='T2' and [param]='1' order by [time] desc
select top 1 [time],[value] from [table] where [time]>='T2' and [param]='1' order by [time] asc
select top 1 [time],[value] from [table] where [time]<='T3' and [param]='1' order by [time] desc
select top 1 [time],[value] from [table] where [time]>='T3' and [param]='1' order by [time] asc
...

如您所见,我为 (T0, T1, T2, ...) 中的每个时间 T 请求成对的 (time, value)-tuples,其中每对的查询是“就在之前”的元组和其他“就在”某个时间点 T 之后。每个单个请求花费的时间不到 1 毫秒(根据 SMSS 2012 Express 中的 SQL Profiler)。

在我的程序 (C#.NET) 中,我使用 SqlDataReader 执行一系列查询。但是,每个查询大约需要 12-20 毫秒,这远远超出了我的预期,这对于我的目的来说是不可接受的。在我看来,SqlDataReader 的开销是问题所在,不是吗?

到代码看起来类似于这样:

foreach (int x = 0; x < screen.width; ++x)
{
    time T = pixel2time(x);

    string cmd = "select top 1 [time],[value] from [table] where [time]<='" + T.ToString() + "' and [param]='1' order by [time] desc";
    SqlCommand scmd = new SqlCommand(cmd, con);

    // The time from here ...
    SqlDataReader reader = scmd.ExecuterReader();
    // ... to here takes about 12-20 milliseconds
    // the same query in SQL Profiler takes 
    // "0 milliseconds"

    if (reader.Read())
    {
        ...
    }
}

(我正在绘制一个时间值序列,并在 x 轴上为代表某个时间 T 的每个像素请求时间值元组“恰好在”T 之前和“之后”T 以确定 y 值这个像素。因此,根据屏幕/窗口宽度,我可能有大约 1000 个像素,因此有 2 x 1000 个查询,每个查询需要约 12 毫秒 = 24 秒。这对于绘制图表来说太多了。此外,我有一个数据库中具有索引等的10,000,000百万条目的序列应该为每个查询提供O(log n)的访问时间,因此查询时间小于一毫秒的数据库很好。问题只是.NET框架,(或者可能是网络?)以及我找不到更有效的解决方案的问题。)

如何解决这个性能问题?


我尝试/考虑了以下方法:

(1) 使用“union”语句将序列组合到由单个 SqlDataReader 执行的单个查询不起作用。我想这是因为“union”与“order by”语句的某种不兼容。您对此了解更多吗?

编辑:(更新)

select top 1 [time],[value] from [table] where [time]<='T0' and [param]='1' order by [time] desc
union all
select top 1 [time],[value] from [table] where [time]>='T0' and [param]='1' order by [time] asc

给出错误“消息 156,级别 15,状态 1,第 2 行关键字“联合”附近的语法不正确。”

每个单独的查询都可以正常工作。我有语法错误吗?谢谢。

(2) 我确信存储过程不会带来任何好处,因为在 SMSS 中执行单个查询的时间不到一毫秒。

【问题讨论】:

  • 使用Union All 而不是union。还有,表格总共有多少行?
  • 谢谢,我添加了更新。我的表中正好有 10,000,000 行。

标签: c# mysql sql database ado.net


【解决方案1】:

我可以在这里建议的是尝试在某个时间创建 n 个线程并并行进行所有查询: - 在网络数据包上 - 编组和解组结果等...... 希望对你有帮助

【讨论】:

  • 我会试试这个,但老实说,我更喜欢一种可以减少开销而不是并行化查询 + 开销的解决方案。如果没有更好的提高性能的方法,我会将您的答案标记为“正确答案”。谢谢。
【解决方案2】:

使用联合是正确的方法,但是您需要使用子查询,因为整个联合只有一个order by,并且您应该使用union all 而不是union,这样它就不会删除重复项:

select [time],[value] from (select top 1 [time],[value] from [table] where [time]<='T0' and [param]='1' order by [time] desc) x
union all
select [time],[value] from (select top 1 [time],[value] from [table] where [time]>='T0' and [param]='1' order by [time] asc) x
union all
select [time],[value] from (select top 1 [time],[value] from [table] where [time]<='T1' and [param]='1' order by [time] desc) x
union all
select [time],[value] from (select top 1 [time],[value] from [table] where [time]>='T1' and [param]='1' order by [time] asc) x
union all
select [time],[value] from (select top 1 [time],[value] from [table] where [time]<='T2' and [param]='1' order by [time] desc) x
union all
select [time],[value] from (select top 1 [time],[value] from [table] where [time]>='T2' and [param]='1' order by [time] asc) x
union all
select [time],[value] from (select top 1 [time],[value] from [table] where [time]<='1453511000' and [param]='1' order by [time] desc) x
union all
select [time],[value] from (select top 1 [time],[value] from [table] where [time]>='1453511000' and [param]='1' order by [time] asc) x

【讨论】:

  • 查询末尾的“x”有什么作用?无论如何,使用“x”它可以工作,没有它则不能。谢谢。
  • @sema:这是子查询的名称,因为每个子查询都需要一个名称。它没有专门用于查询,但您可以在外部查询中将其用作select x.[time],x.[value]
【解决方案3】:

主要问题是在循环中运行查询。

  • 找出您需要的最大和最小时间值。
  • 一次性查询所有积分 - SELECT time,value FROM [table] WHERE time BETWEEN [smallest]AND[largest]...
  • 遍历结果并计算出要为结果绘制的像素 (x) 值。

您真的需要为非常像素或时间值绘制一个点吗?如果您想坚持逐个像素的方法,那么如上所述获得所有结果会更快,并通过使用 linq 查询来获取您正在绘制的时间的值。

【讨论】:

  • 此解决方案无法扩展,因为我将从该查询中收到 10,000,000 行。
  • 仅当您绘制表格中的每一行时。您的时间值是否均匀分布?你有身份列吗?如果有的话,有更快的方法来绘制一系列点。