Delphi XE数据库组件,可以缓存查询结果

时间:2014-11-14 02:54:06

标签: sql delphi caching delphi-xe

我正在使用带有数据库的基于Delphi XE的桌面程序,我注意到,对于程序来说,加载整个表,然后每次要将它显示给用户每页时,它太大了浏览产品,但是当用户在浏览器中浏览时,程序加载和重新加载每个页面也是低效的。我想问一下,Delphi是否有任何方法可以缓存查询调用结果,因此对于同一个查询,数据库组件不会再次查询数据库,而是返回缓存而不是?

这适用于以下情况:

程序使用查询从数据库加载第1页的前20行。

SELECT * FROM tbl_product_master LIMIT 0,20;

然后用户点击下一页。该程序使用查询从数据库加载第2页的下20行。

SELECT * FROM tbl_product_master LIMIT 21,40;

然后用户点击上一页。该程序试图再次加载前20行。

SELECT * FROM tbl_product_master LIMIT 0,20;

但是因为数据库组件知道我之前已经调用过这个查询,并且它已经将结果保存在缓存中,它会立即从缓存中返回结果,而不是再次向数据库发送查询,从而导致程序更快

Delphi XE是否存在这样的组件或配置?我只需要朝着正确的方向轻推。谢谢。

2 个答案:

答案 0 :(得分:1)

(这不是完全你q的答案,但它可能会提供一些常用信息,而且我已经包含了一些可能有助于实施的代码。)

您没有提到您正在使用哪个服务器后端,也没有提到Delphi数据集 键入您用于从服务器获取数据。其他人提到了TClientDataSet 在评论中,如果您对客户端感兴趣,这肯定是一个好的开始 缓存,特别是考虑到您可以使用TDataSetFields来嵌套Detail表数据 在处理船长的CDS收到的数据中。

值得注意的是,早期(在Delphi 5周围),TClientDataSet获得了一点声誉,因为一旦你进入成千上万行,访问性能会从悬崖上掉下来,尽管看起来确实在哪里依赖关于字段数量和记录大小等等。它依赖的CDS代码和Midas.Lib多年来得到了改进,这已经减少但并未完全消除这个问题。

出于兴趣,我写了一个小测试平台来演示这个可能的性能问题 并将其性能与使用TAdoQuery进行比较,TAdoQuery从我的服务器获取CDS数据 从本地磁盘文件保存和加载自己的数据。我使用的代码如下所示 当然可以改进并变得更加严谨。

从广义上讲,它使用TAdoQuery从服务器表中检索一定数量的行, 保存并加载本地磁盘文件中的数据,然后使用传输数据到CDS 一个TDataSetProvider,保存并加载CDS数据,首先是数据PK的索引 并且第二次没有(因为我想知道CDS是否可以使用PK索引 增加CDS的LoadFromFile性能 - 它没有。

以下是结果。请带上大量的盐和#34;因为我确定其他人 会得到不同的结果。我的观点是,你应该自己调查一下 您自己的环境并使用您自己选择的数据和数据集组件。

结果

          A      B   C   D     E         F        G      H   I
  Recs  5000    140  63  93   967   0.0001934     952    15  31
  Recs  10000   172 125 156  2574   0.0002574    2355    47  47
  Recs  15000   250 171 219  4508   0.0003005    4477    63  62
  Recs  20000   359 218 297  7082   0.0003541    7129    78  94
  Recs  25000   390 327 343  9985   0.0003994    9968    94 109
  Recs  30000   531 343 421 13401   0.0004467   13572   125 140

A =记录数

B = Open AdoQuery(毫秒)

C =将AdoQuery保存到文件(ms)

D =从文件加载AdoQuery(ms)

E =通过DataSetProvider将AdoQuery数据传输到CDS,CDS上的PK索引(ms)

F = E / A,即每条记录的转移时间

G =通过DataSetProvider将AdoQuery数据传输到CDS而不使用CDS上的PK索引(ms)

H = CDS保存到磁盘文件(ms)

I =从磁盘文件加载CDS(ms)

顺便说一句,这些数据的记录大小为241和45个字段

评论/观察

  • ClientDataSet比AdoQuery在保存和保存方面要快得多在本地磁盘中加载数据。两组时间与记录数大致呈线性关系。

  • AdoQuery的开放时间与检索到的记录数大致呈线性关系。

  • Adoquery-> ClientDataSet传输时间与记录数量的线性差异要大得多,这就是我所说的“悬崖”#34;。但只要您将自己限制在几千条记录中,ClientDataSet就可以正常工作。你需要找出什么"少数"适用于您的申请。

  • 将记录数量增加一个因子或2,从5000增加到10000,这使得传输时间略长于两倍,同时将其增加5倍,达到30000,将时间乘以系数超过14岁。

  • 如果我用cbSelectPKOnly.Checked重复测试,那么CDS只检索整数字段,AdoQuery-> ClientDataSet传输时间要快得多,所以你可以移动"悬崖"通过检索更少的数据列。

  • 代码是用D7编写的,并经过测试(没有Andreas Hausladen的令人钦佩的"速度修复"对于Midas)和XE4。结果适用于XE4。

  • 主机包含Sql Server,并且拥有所有SSD磁盘,Win7 64位。

代码

const
  scPKName = 'ApcsID';
  scSql = 'select top %d %s from btapcs'; // order by apcsid';

function TForm1.GetTestSql(Count : Integer) : String;
var
  S : String;
begin
  if cbSelectPKOnly.Checked then
    S := scPKName
  else
    S := '*';
  Result := Format(scSql, [Count, S]);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Count : Integer;
begin
   Count := 5000;
   while Count <= StrToInt(edMaxRecs.Text) do begin
     TestOpenAdoQuery(Count);
     Inc(Count, 5000);
     Application.ProcessMessages;
   end;
end;

procedure TForm1.Log(const Msg : String);
begin
  Memo1.Lines.Add(Msg);
end;

procedure TForm1.TestOpenAdoQuery(Count : Integer);
type
  TDataOperation = (doOpenQuery, doSaveQueryData, doLoadQueryData, doGetCDSData,
    doSaveCDSData, doLoadCDSData);
var
  Msg : String;
  AdoFN,
  CdsFN : String;
  Query : TAdoQuery;
  DSP : TDataSetProvider;
  CDS : TClientDataSet;

  procedure PerformOperation(Op : TDataOperation; var Msg : String);
  var
    T1,
    T2 : Integer;
  begin
    T1 := GetTickCount;
    case Op of
      doOpenQuery : begin
        Query.Sql.Text := GetTestSql(Count);
        Query.Open;
        Msg := Msg + Chr(9) + IntToStr(Query.RecordCount);
      end;
      doSaveQueryData : begin
        Query.SaveToFile(AdoFN);
      end;
      doLoadQueryData : begin
        Query.LoadFromFile(AdoFN);
      end;
      doGetCDSData : begin
        CDS.Open;
      end;
      doSaveCDSData : begin
        CDS.SaveToFile(CdsFN, dfBinary);
      end;
      doLoadCDSData : begin
        CDS.LoadFromFile(CdsFN);
      end;
    end; { case }
    T2 := GetTickCount;
    Msg := Msg + Chr(9) + IntToStr(T2 - T1);
  end;

begin
  //  This proc uses a TAdoConnection1 on the form, but uses a temporary AdoQuery, 
  //  DataSetProvider and TClientDataSet to avoid state being carried over
  //  from one call to the next.

  Screen.Cursor := crSqlWait;
  Update;
  try
    Query := TAdoQuery.Create(Nil);
    Query.CursorType := ctKeySet;
    Query.CursorLocation := clUseClient;
    Query.Connection := AdoConnection1;
    if cbDropConnectionBetweenRuns.Checked then begin
      AdoConnection1.Connected := False;
    end;

    AdoFN := IncludeTrailingPathDelimiter(GetEnvironmentVariable('TEMP'))+ 'ado.dat';
    CdsFN := IncludeTrailingPathDelimiter(GetEnvironmentVariable('TEMP'))+ 'cds.cds';

    Msg := 'Recs ';
    Query.Sql.Text := GetTestSql(Count);
    PerformOperation(doOpenQuery, Msg);

    Query.Connection := Nil;

    PerformOperation(doSaveQueryData, Msg);
    PerformOperation(doLoadQueryData, Msg);

    DSP := TDataSetProvider.Create(Self);
    DSP.Name := 'MyProvider';
    DSP.DataSet := Query;
    CDS := TClientDataSet.Create(Self);

    DSP.DataSet := Query;
    CDS.ProviderName := DSP.Name;

    CDS.IndexFieldNames := scPKName;
    PerformOperation(doGetCDSData, Msg);

    CDS.Close;
    CDS.IndexFieldNames := '';
    PerformOperation(doGetCDSData, Msg);
    PerformOperation(doSaveCDSData, Msg);

    CDS.Close;

    PerformOperation(doLoadCDSData, Msg);

    Log(Msg);

  finally
    Query.Free;
    DSP.Free;
    CDS.Free;
    Screen.Cursor := crDefault;
  end;
end;

答案 1 :(得分:1)

Delphi中有不同的方法来限制查询加载的记录数 - 它还取决于您使用的数据库和数据访问组件。

如果您使用的Delphi数据访问库也支持该功能并且可以设置,则某些数据库将只能返回给定数量的记录,直到您要求更多。有些还可以缓存查询结果,并避免在可能的情况下重新执行查询。已经取出的记录将被保留。

TClientDataset + Provider组合提供了与数据库无关的类似功能。它可以递增地加载数据,并且它将缓存已经获取的行。但是,它需要更多代码才能工作。

在本机Windows应用程序中,您通常不需要Web UI使用的“分页”隐喻,因为使用一些可以在本地内存或磁盘中缓存数据时可以向上和向下滚动的控件要容易得多。

但请注意:

  • 如果不限制SQL级别的结果集,则数据库可能仍需要生成整个结果集。查询可能运行得更慢,并且使用了更多的服务器资源。
  • 无论如何,某些控件可能会加载整个数据集。例如,Developer Express网格通常会加载整个数据集以启用本地排序,过滤和分组等功能,除非您将它们设置为不执行此操作。
  • 如果您尝试获取记录数,并且不在单独的SELECT COUNT查询中执行此操作,或者db库未以某种方式返回它,则TDataset组件可能只获取所有行以获取计数。