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.