繁体   English   中英

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

[英]Delphi XE Database component that can cache query result

我正在使用带数据库的基于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是否存在这样的组件或配置? 我只需要向正确的方向轻推即可。 谢谢。

(这并不是对q的确切回答,但它可能提供一些常规信息,并且我提供了一些代码,可以帮助您了解实施情况。)

您没有提到您正在使用哪个服务器后端,也没有提到您正在使用哪种Delphi数据集类型从服务器获取数据。 其他人在注释中提到了TClientDataSet,如果您对客户端缓存感兴趣,那么这当然是一个不错的起点,特别是考虑到您可以使用TDataSetFields将Detail table数据嵌套在处理Master的CDS接收的数据中。

需要记住的一点是,在早期(大约在Delphi 5左右),TClientDataSet就因其访问性能而闻名,一旦进入数千行,它就会掉下悬崖,尽管确切的位置似乎取决于数字字段和记录大小等。 多年来,它所依赖的CDS代码和Midas.Lib已得到改进,这减少了但并未完全消除该问题。

出于兴趣,我写了一个小测试台来演示这个可能的性能问题,并使用TAdoQuery比较它的性能,TAdoQuery从我的服务器获取CDS数据,以将自身的数据保存到本地磁盘文件或从本地磁盘文件加载数据。 我使用的代码如下所示,可以肯定的加以改进和更加严格。

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

这是结果。 请给他们大“盐”,因为我敢肯定其他人会得到不同的结果。 我的观点是,您应该在自己的环境中并使用自己选择的数据和数据集组件自己进行调查。

结果

          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 =打开AdoQuery(毫秒)

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

D =从文件加载AdoQuery(毫秒)

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

F = E / A,即每条记录的传输时间

G =通过DataSetProvider将AdoQuery数据传输到CDS,而CDS上没有PK索引(ms)

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

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

顺便说一句,此数据的RecordSize为241和45个字段

评论/意见

  • 在本地磁盘上保存和加载数据时,ClientDataSet比AdoQuery快很多。 两组时间都与记录数大致呈线性变化。

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

  • Adoquery-> ClientDataSet的传输时间与记录数量的线性变化相比要差很多,这就是我所说的“断崖式”。 但是只要您将自己限制在几千条记录中,ClientDataSet就可以正常工作。 您需要找出“很少”对您的应用程序意味着什么。

  • 将记录数从5000增加到10000,增加2倍,使传输花费的时间略多于两倍;而将记录增加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;

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

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

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

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

但请注意:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM