Discussion of open issues, suggestions and bugs regarding IBDAC (InterBase Data Access Components) for Delphi, C++Builder, Lazarus (and FPC)
-
ralle1
- Posts: 43
- Joined: Thu 27 May 2010 15:01
Post
by ralle1 » Thu 06 Dec 2012 09:41
We’ve got the problem that the Firebird Server becomes very slow performance in the course of a day. We’ve found out that the transactions cause this. In the database statistic, I can see that the different between oldest active Transaction and next transaction permanently increases. After reading in several forums, we thought that it’s correct to use the feature “UpdateTransaction”. But the increase is just the same. Please look at my example below. The database sweep interval is set to 10 but the values for “Oldest transaction” / “Oldest active” / “Oldest snapshot” are standing still and the “Next transaction” is running. Nothing else is working on this database.
Here the example:
Code: Select all
procedure TForm7.Button1Click(Sender: TObject);
var
id : integer;
IBCQuery : TIBCQuery;
DefaultTransaction,
UpdateTransaction : TIBCTransaction;
IBCCon : TIBCConnection;
begin
Cancel := false;
IBCCon := TIBCConnection.Create(nil);
IBCCon.LoginPrompt := false;
IBCCon.Database := '10.1.0.18/3050:c:\databases\data1.fdb';
IBCCon.Username := 'SYSDBA';
IBCCon.Password := 'masterkey';
IBCCon.Connected := true;
DefaultTransaction := TIBCTransaction.Create(Self);
DefaultTransaction.DefaultConnection := IBCCon;
UpdateTransaction := TIBCTransaction.Create(Self);
UpdateTransaction.DefaultConnection := IBCCon;
IBCQuery := TIBCQuery.Create(Self);
TRY
(* Simple Table for Inserts:
CREATE TABLE TESTTABLE (
ID INTEGER NOT NULL,
VAL INTEGER,
PRIMARY KEY (ID))
*)
id := 0;
IBCQuery.Connection := IBCCon;
IBCQuery.Transaction := DefaultTransaction;
IBCQuery.UpdateTransaction := UpdateTransaction;
IBCQuery.SQL.Text := 'SELECT MAX(ID) FROM TESTTABLE';
IBCQuery.Open;
id := IBCQuery.Fields[0].AsInteger;
IBCQuery.Close;
IBCQuery.SQL.Text := 'SELECT * FROM TESTTABLE';
IBCQuery.ReadOnly := false;
IBCQuery.Open;
repeat
inc(id);
IBCQuery.Insert;
IBCQuery.FieldByName('ID').AsInteger := id;
IBCQuery.Post;
Label1.Caption := IntToStr(id);
Application.ProcessMessages;
until (Cancel);
FINALLY
IBCQuery.Close;
IBCQuery.Free;
DefaultTransaction.Free;
UpdateTransaction.Free;
IBCCon.Close;
IBCCon.Free;
END;
end;
Can you please help me??
Thanks!
-
AndreyZ
Post
by AndreyZ » Thu 06 Dec 2012 10:10
The point is that if the TIBCQuery.Options.PrepareUpdateSQL property is set to True, IBDAC prepares statements that are executed in the context of the writing transaction. This is done to provide the further work with the prepared statement. To avoid the problem, you should set TIBCQuery.Options.PrepareUpdateSQL to False. In this case, IBDAC does not prepare statements of the writing transaction, and executes the Commit statement instead of CommitRetaining.
Please note that we have changed the default value of the TIBCQuery.Options.PrepareUpdateSQL property from True to False in IBDAC 4.2.7 for performance improving.
-
ralle1
- Posts: 43
- Joined: Thu 27 May 2010 15:01
Post
by ralle1 » Thu 06 Dec 2012 10:52
Unfortunately this is not quite correct. I am using Delphi XE2 with IBDAC 4.5.9
Sorry for not communicate this information.
And I use the default value. I just checked it.
TIBCQuery.Options.PrepareUpdateSQL is set to False.
-
AndreyZ
Post
by AndreyZ » Thu 06 Dec 2012 17:13
I have checked your code using IBDAC 4.5.9, and IBDAC correctly calls the Commit method instead of CommitRetaining. You are right, when using your code, "Oldest transaction", "Oldest active", and "Oldest snapshot" do not change and "Next transaction" changes. It is a correct behaviour, because you have two transactions, one for reading data and one for writing. When you open IBCQuery, IBDAC starts DefaultTransaction to read data from the TESTTABLE table. "Oldest transaction", "Oldest active", and "Oldest snapshot" do not change because DefaultTransaction is active. When you add new records, IBDAC starts UpdateTransaction and commits it for each record, that is why the value of "Next transaction" grows. If you do not want this to happen, you can start UpdateTransaction manually before adding new records and commit all changes afterwards. Here is a code example:
Code: Select all
IBCQuery.SQL.Text := 'SELECT * FROM TESTTABLE';
IBCQuery.ReadOnly := false;
IBCQuery.Open;
IBCQuery.AutoCommit := False;
IBCQuery.UpdateTransaction.StartTransaction;
repeat
inc(id);
IBCQuery.Insert;
IBCQuery.FieldByName('ID').AsInteger := id;
IBCQuery.Post;
Label1.Caption := IntToStr(id);
Application.ProcessMessages;
until (Cancel);
IBCQuery.UpdateTransaction.Commit;
-
ralle1
- Posts: 43
- Joined: Thu 27 May 2010 15:01
Post
by ralle1 » Fri 07 Dec 2012 07:30
Thank your for investing your time in my problem!
Your explanation seems to be logical. But if I always close the IBCQuery after each Insert, the DefaultTransaction will no longer be active and because of "WHERE ID IS NULL" none of the new records is selected. "Oldest transaction" etc. just the same.
Can you explain this behaviour please?
Here is a code example:
Code: Select all
IBCQuery.Close;
IBCQuery.SQL.Text := 'SELECT * FROM TESTTABLE WHERE ID IS NULL';
IBCQuery.ReadOnly := false;
repeat
inc(id);
IBCQuery.Open;
IBCQuery.Insert;
IBCQuery.FieldByName('ID').AsInteger := id;
IBCQuery.Post;
IBCQuery.Close;
Label1.Caption := IntToStr(id);
Application.ProcessMessages;
until (Cancel);
-
AndreyZ
Post
by AndreyZ » Fri 07 Dec 2012 09:53
This is a correct Firebird behaviour. Firebird returns old values of "Oldest transaction", "Oldest active", and "Oldest snapshot" until a new transaction is started. You can check this using the following code:
Code: Select all
IBCQuery.Close;
IBCQuery.SQL.Text := 'SELECT * FROM TESTTABLE WHERE ID IS NULL';
IBCQuery.ReadOnly := false;
repeat
inc(id);
IBCQuery.Open;
IBCQuery.Insert;
IBCQuery.FieldByName('ID').AsInteger := id;
IBCQuery.Post;
IBCQuery.Close;
Label1.Caption := IntToStr(id);
Application.ProcessMessages;
until (Cancel);
DefaultTransaction.Rollback;
// shows old values
Memo1.Lines.Add(IntToStr(IBCCon.DatabaseInfo.InfoOldestTransaction));
Memo1.Lines.Add(IntToStr(IBCCon.DatabaseInfo.InfoOldestActive));
Memo1.Lines.Add(IntToStr(IBCCon.DatabaseInfo.InfoOldestSnapshot));
Memo1.Lines.Add(IntToStr(IBCCon.DatabaseInfo.InfoNextTransaction));
Memo1.Lines.Add('');
DefaultTransaction.StartTransaction;
// shows new values
Memo1.Lines.Add(IntToStr(IBCCon.DatabaseInfo.InfoOldestTransaction));
Memo1.Lines.Add(IntToStr(IBCCon.DatabaseInfo.InfoOldestActive));
Memo1.Lines.Add(IntToStr(IBCCon.DatabaseInfo.InfoOldestSnapshot));
Memo1.Lines.Add(IntToStr(IBCCon.DatabaseInfo.InfoNextTransaction));
Memo1.Lines.Add('');
We already had similar requests from our users and we discussed it with Firebird developers. They confirmed that it is a correct behaviour.
-
ralle1
- Posts: 43
- Joined: Thu 27 May 2010 15:01
Post
by ralle1 » Fri 07 Dec 2012 11:09
OK, thank you for these informations! Can you tell me one more thing: Which transaction will be used by ExecSQL respectivly Execute in this example:
Code: Select all
IBCQuery.ReadOnly := true;
IBCQuery.Connection := IBCCon;
IBCQuery.Transaction := DefaultTransaction;
IBCQuery.UpdateTransaction := UpdateTransaction;
IBCQuery.SQL.Text := 'UPDATE TESTTABLE SET VAL=123 WHERE ID=2';
IBCQuery.ExecSQL;
-
AndreyZ
Post
by AndreyZ » Fri 07 Dec 2012 12:53
In this case, DefaultTransaction is used.
-
ralle1
- Posts: 43
- Joined: Thu 27 May 2010 15:01
Post
by ralle1 » Fri 07 Dec 2012 13:18
OK. What's your opinion, is it possible to do something like this or is there any reason to use the DefaultTransaction:
Code: Select all
IBCQuery.Transaction := UpdateTransaction;
UpdateTransaction.StartTransaction;
IBCQuery.ExecSQL;
UpdateTransaction.Commit;
IBCQuery.Transaction := DefaultTransaction;
-
AndreyZ
Post
by AndreyZ » Fri 07 Dec 2012 16:48
Yes, it is possible to use such code. It is up to you to decide what transaction to use in each particular situation.
-
ralle1
- Posts: 43
- Joined: Thu 27 May 2010 15:01
Post
by ralle1 » Sun 09 Dec 2012 19:07
OK, thank you very much. While testing I noticed the following behavior:
I have exended the loop in the above example:
I use in each continuous a Stored Procedure, which uses a generator, something like this: „ID = GEN_ID(GENERATORNAME,1)“. Now, the values "Oldest transaction" etc. is increasing too. I don't understand
Can you explain this?
-
AndreyZ
Post
by AndreyZ » Mon 10 Dec 2012 11:26
InterBase and Firebird require an active transaction for any operation under data. So, when you execute your stored procedure, a new transaction is started and committed, which leads to increasing of the "Oldest transaction", etc. values.
Please note that IBDAC does not control the "Oldest transaction", "Oldest active", "Oldest snapshot", and "Next transaction" values, they are controlled by Firebird. They can change when any transaction-related operations are performed. If you want to know more about these values and situations when they change their values, please consult with Firebird developers.
-
ralle1
- Posts: 43
- Joined: Thu 27 May 2010 15:01
Post
by ralle1 » Mon 10 Dec 2012 15:53
I understand that it’s a firebird issue. But with all of your information you have helped me a lot. Thank you for the very good support. I will try to rebuild everything and to use "Update Transaction" respectively explicitly transactions, so that the DefaultTransaction can be set to IsolationLevel ReadOnlyReadCommitted. So I hope the performance will be consistently good.
Thank you!
-
AndreyZ
Post
by AndreyZ » Mon 10 Dec 2012 17:41
I am glad I could help. Feel free to contact us if you have any further questions about IBDAC.