2

Recently i have completed the migration of my company's project, from Delphi XE7 to Alexandria 11.1. After a couple of releases, some customers with bigger databases complained for delays at opening my most used form. I have started to research the problem and i found out, that the delay occurs at the insertion of a row to the master TClientDataset of the form. The TClientDataset has around 50 TField with 5 of them, been lookup an the another ADOQuery with around 100000 rows.

In order to isolate the problem, i have created a project with an ADOQuery with 100000 rows as with 5 fields, and another ADOQuery with lookup fields to the first. I insert a row and copy a value to key Field and post the row. I notice that it need around 40ms to complete for Alexandria and 2ms for XE7.

Those times scale up if i create more lookup fields, or assign values to the rest of the fields. In my project in some scenarios, load time of 3-4 seconds in XE7 rise up to 12-15 in Alexandria. This is more clear in release

I have tried to debug VCL code and compare the files of Ado and dsnap folders of each version, but i haven't figure out what has change. I have google about it, and i haven't find any similar report. I wonder if this is a bug in the newest Delphi, or am i missing something else, maybe a new option. Does anyone have a similar experience? I will appreciate any info about the subject.

I will leave the sql script of the Database and the Delphi Code if anyone want to reproduce it.

The SQL SCRIPT:

CREATE TABLE [dbo].[Persons](
    [ID] [int] NOT NULL,
    [ModifiedDate] [datetime] NULL,
    [FirstName] [varchar](50) NULL,
    [LastName] [varchar](50) NULL,
    [EMail] [varchar](30) NULL,
    [PhoneNumber] [varchar](15) NULL,
PRIMARY KEY CLUSTERED 
(
    [ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[Books](
    [BookID] [int] NOT NULL,
    [Title] [nchar](50) NOT NULL,
    [PersonID] [int] NOT NULL,
 CONSTRAINT [PK_Books] PRIMARY KEY CLUSTERED 
(
    [BookID] ASC
) ON [PRIMARY]
) ON [PRIMARY]

DECLARE @RowCount int = 100000,
        @Index int = 1

WHILE (@Index <= @RowCount)
BEGIN
    INSERT INTO Persons (ID, ModifiedDate, FirstName, LastName, EMail, PhoneNumber)
           VALUES (@Index, getdate(), 'FirstName' + CAST(@Index AS varchar(10)), 'LastName' + CAST(@Index AS varchar(10)), 'EMail' + CAST(@Index AS varchar(10)), CAST(@Index AS varchar(10)))
    SET @Index += 1
END

The DFM:

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 177
  ClientWidth = 179
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -12
  Font.Name = 'Segoe UI'
  Font.Style = []
  OnCreate = FormCreate
  TextHeight = 15
  object BitBtn1: TBitBtn
    Left = 56
    Top = 144
    Width = 75
    Height = 25
    Caption = 'Load'
    TabOrder = 0
    OnClick = BitBtn1Click
  end
  object luPersons: TADOQuery
    Connection = ADOConnection1
    CursorType = ctStatic
    Parameters = <>
    SQL.Strings = (
      'SELECT *'
      'FROM PERSONS')
    Left = 32
    Top = 72
    object luPersonsID: TIntegerField
      FieldName = 'ID'
      ProviderFlags = [pfInUpdate, pfInWhere, pfInKey]
    end
    object luPersonsModifiedDate: TDateTimeField
      FieldName = 'ModifiedDate'
    end
    object luPersonsFirstName: TStringField
      FieldName = 'FirstName'
      Size = 50
    end
    object luPersonsLastName: TStringField
      FieldName = 'LastName'
      Size = 50
    end
    object luPersonsEMail: TStringField
      FieldName = 'EMail'
      Size = 30
    end
    object luPersonsPhoneNumber: TStringField
      FieldName = 'PhoneNumber'
      Size = 15
    end
  end
  object ADOConnection1: TADOConnection
    ConnectionString = 
      'Provider=SQLNCLI11.1;Persist Security Info=False;User ID=sa;Pass' +
      'word=password;Initial Catalog=testDB;Data Source=DATABASE;' +
      'Use Procedure for Prepare=1;Auto Translate=True;Pack' +
      'et Size=4096;Workstation ID=Workstation;Initial File Name=""' +
      ';Use Encryption for Data=False;Tag with column collation when po' +
      'ssible=False;MARS Connection=False;DataTypeCompatibility=0;Trust' +
      ' Server Certificate=False;Server SPN="";Application Intent=READW' +
      'RITE'
    Provider = 'SQLNCLI11.1'
    Left = 80
    Top = 16
  end
  object qBooks: TADOQuery
    Connection = ADOConnection1
    CursorType = ctStatic
    Parameters = <>
    SQL.Strings = (
      'SELECT *'
      'FROM BOOKS')
    Left = 128
    Top = 72
    object qBooksBookID: TIntegerField
      FieldName = 'BookID'
      ProviderFlags = [pfInUpdate, pfInWhere, pfInKey]
    end
    object qBooksTitle: TWideStringField
      FieldName = 'Title'
      FixedChar = True
      Size = 50
    end
    object qBooksPersonID: TIntegerField
      FieldName = 'PersonID'
    end
    object qBooksModifiedDate: TDateTimeField
      FieldKind = fkLookup
      FieldName = 'ModifiedDate'
      LookupDataSet = luPersons
      LookupKeyFields = 'ID'
      LookupResultField = 'ModifiedDate'
      KeyFields = 'PersonID'
      Lookup = True
    end
    object qBooksFirstName: TStringField
      FieldKind = fkLookup
      FieldName = 'FirstName'
      LookupDataSet = luPersons
      LookupKeyFields = 'ID'
      LookupResultField = 'FirstName'
      KeyFields = 'PersonID'
      Size = 50
      Lookup = True
    end
    object qBooksLastName: TStringField
      FieldKind = fkLookup
      FieldName = 'LastName'
      LookupDataSet = luPersons
      LookupKeyFields = 'ID'
      LookupResultField = 'LastName'
      KeyFields = 'PersonID'
      Size = 50
      Lookup = True
    end
    object qBooksEMail: TStringField
      FieldKind = fkLookup
      FieldName = 'EMail'
      LookupDataSet = luPersons
      LookupKeyFields = 'ID'
      LookupResultField = 'EMail'
      KeyFields = 'PersonID'
      Size = 30
      Lookup = True
    end
    object qBooksPhoneNumber: TStringField
      FieldKind = fkLookup
      FieldName = 'PhoneNumber'
      LookupDataSet = luPersons
      LookupKeyFields = 'ID'
      LookupResultField = 'PhoneNumber'
      KeyFields = 'PersonID'
      Size = 15
      Lookup = True
    end
  end
end

And the PAS source code:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Data.Win.ADODB, Vcl.StdCtrls, Vcl.Buttons;

type
  TForm1 = class(TForm)
    luPersons: TADOQuery;
    ADOConnection1: TADOConnection;
    luPersonsID: TIntegerField;
    luPersonsModifiedDate: TDateTimeField;
    luPersonsFirstName: TStringField;
    luPersonsLastName: TStringField;
    luPersonsEMail: TStringField;
    luPersonsPhoneNumber: TStringField;
    qBooks: TADOQuery;
    qBooksBookID: TIntegerField;
    qBooksTitle: TWideStringField;
    qBooksPersonID: TIntegerField;
    qBooksModifiedDate: TDateTimeField;
    qBooksFirstName: TStringField;
    qBooksLastName: TStringField;
    qBooksEMail: TStringField;
    qBooksPhoneNumber: TStringField;
    BitBtn1: TBitBtn;
    procedure FormCreate(Sender: TObject);
    procedure BitBtn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses System.DateUtils;

procedure TForm1.BitBtn1Click(Sender: TObject);
var
    starttime, endtime: TTime;
begin
    starttime := Time;
    qBooks.Open;

    qBooks.Insert;
    qBooksBookID.AsInteger := 1;
    qBooksPersonID.AsInteger := 1;

    endtime := Time;

    ShowMessage(MilliSecondsBetween(endtime, starttime).ToString + ' ' + FormatDateTime('hh:mm:ss.zzz', starttime) +
                                                                   ' ' + FormatDateTime('hh:mm:ss.zzz', endtime));
    qBooks.Close;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
    luPersons.Open;
end;

end.
stmpakir
  • 319
  • 2
  • 10
  • I tested your code. First with 10,000 records, then 100,000, then 1,000,000. You are correct, it does not scale. I ran out of memory at 1,000,000. I only use lookups for small tables, so I've never encountered this. I think you should rework the design. I would left outer join to the table you are looking up and provide the data that way. – John Easley Jan 21 '23 at 01:22
  • Thank you for trying to reproduce it and that you confirm the problem. Your suggestion is something that i have in mind, but as a last solution. Mainly i want to find out, if this is a bug. Something have change between XE7 and Alexandria D11.1, that change the behavior and causing the delay. Maybe i should report at QC. – stmpakir Jan 23 '23 at 10:55
  • I've replicated it here with the Fire DAC components on top of Advantage Database, Delphi 10.4.1 and Delphi Seattle. No slow down in XE3 (but I couldn't use FireDAC here. Had to use Advantage native TDataSet). I absolutely think this is a worthy submission to QC. This effectively prevents us from moving forward with newer Delphi versions. – FLDelphi Feb 01 '23 at 16:39
  • I hoped that FireDac had better performance. Good to know. I have report it at https://quality.embarcadero.com/browse/RSP-40506 – stmpakir Feb 03 '23 at 15:14

0 Answers0