是否可以在SQL Server(2012)中以恒定时间进行数据分页?

时间:2013-06-20 20:02:33

标签: sql-server database data-paging

我一直在研究一个我负责的系统的性能问题解决方案,我认为至少部分问题是由于数据库查询性能。我们使用存储过程以非常标准的方式查询数据的“页面”。但是,当数据集变大时,这种分页看起来会更加昂贵。

鉴于这个简单的表填充了样本数据:

create table Data (
Value uniqueidentifier not null,
constraint PK_Data primary key clustered (Value)
)

insert into Data 
-- SeedTable has ~2M rows
select newid() from SeedTable 

这个存储过程返回分页数据:(这显然需要Sql2012,尽管使用ROW_NUMBER()的Sql2008样式表现相同):

create proc
GetDataPage @Offset int, @Count int
as

select Value
from Data
order by Value
offset @Offset rows
fetch next @Count rows only

然后我用这个C#代码测试了这个sproc的性能:

const int PageSize = 50;
const int MaxCount = 50000;

using (var conn = new SqlConnection("Data Source=.;Initial Catalog=TestDB;Integrated Security=true;")) {

  conn.Open();
  int a = 0;
  for (int i = 0; ; i += PageSize) {
    using (var cmd = conn.CreateCommand()) {
      cmd.CommandType = System.Data.CommandType.StoredProcedure;
      cmd.CommandText = "GetDataPage";
      var oid = cmd.CreateParameter();

      var offset = cmd.CreateParameter();
      offset.Value = i;
      offset.ParameterName = "Offset";
      cmd.Parameters.Add(offset);

      var count = cmd.CreateParameter();
      count.Value = PageSize;
      count.ParameterName = "Count";
      cmd.Parameters.Add(count);

      var sw = Stopwatch.StartNew();
      int c = 0;
      using(var reader = cmd.ExecuteReader()) {
        while (reader.Read()) {
          c++;
        }              
      }
      a += c;

      sw.Stop();
      Console.WriteLine(sw.ElapsedTicks + "\t" + a);

      if (c < PageSize || a >= MaxCount)
        break;
    }
  }
}

当我绘制此代码的输出时,我得到以下内容: Linear

我原本期望在SQL中这样的分页会有恒定的时间性能,或者最坏的情况下可能是对数,但从图表中可以清楚地看出性能是线性的。

是否有任何特殊技巧(提示)可以使这项工作更好?

还有其他方法可能更快吗?

其他数据库的行为方式是否相同?


更改实验代码以使用Kevin Suchlicki建议的“page from”技术,会产生以下结果:

Page From ID

非常令人印象深刻。这种表现看起来更像我期望/想要的。现在我只需要弄清楚我是否可以将其应用于我的真正问题。潜在的问题是它不允许对数据进行“随机访问”,而是仅允许前向光标访问。我知道它看起来像我正在做的事情违反了良好的数据库设计的每一个概念。

1 个答案:

答案 0 :(得分:1)

最明显的可能性是应用程序设计本身。为您的用户提供过滤条件。用户通常会知道他们正在寻找什么,而不愿意通过1000页的返回结果进行翻页。您多久在Google搜索上传递第10页?

话虽如此,您可以尝试存储上一页返回的最后一行的id(聚簇索引值),并在SQL where子句中使用它。如果您需要允许对不同的键(例如姓氏)进行排序,则存储聚集的索引ID值和上一页的最终姓氏。然后像这样编写你的SQL(你总是需要对你的关键字段和聚集的id值进行排序,以便在重复键值的情况下确定性地对记录进行排序):

select top (@count) Id, LastName, FirstName
from Data
where LastName >= @previousLastName and Id > @previousId
order by LastName, Id

您还希望索引可能是排序键的所有字段。不确定上面会如何执行但我希望对索引字段的搜索将执行O(log n)。

另一种选择可能是按行顺序持久保存完整列表,每次源数据发生变化,在幕后,并从持久表中拉出应用程序。

好问题......请告诉我们原来的结果!