Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / lazarus / 2011-07

SQLite performance problem. Simple query takes too long.

11 messages · 3 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Howard Lee Harkness

I am doing a conversion of a small database application from PostgreSQL to SQLite.

I have gotten the program to work, but it it about 2 orders of magnitude slower than the PostgreSQL version. In particular, a query like

    select distinct clients.* from clients where  lastname like
'Hark%' order by lastname, firstname, middlename;

...take nearly 3 seconds, whereas in the PostgreSQL version, it was done instantly. Before I indexed the clients table on lastname, it was something like 6 seconds. This is for a table with about 8000 records, and the above query returns 3 rows. I tried reducing the "order by" clause to just lastname, but that made no difference. I even removed the "order by" clause completely, and that made no difference. I tried without "distinct" and that made no difference. What did make a difference was running the above query using sqlite3.exe from the command line -- which was instantaneous. This implies that the LCL components are taking 3 seconds to do some sort of setup for the query.

This makes no sense to me. The LCL looks fairly straightforward. Here is the call:

 qClients.Close;
 qClients.SQL.Text:=sql; // where sql contains the above select statement
 qClients.Open;

The Open step is the one taking the time. The database setup is below, since I suspect that may be where the problem is. I hope this is enough info.

Any clue about what is taking so long?

Environment:
Windows 7
SQLite3.dll
Lazarus 9.30 using TSQLite3Connection, TSQLTransaction, TSQLQuery, TDataSource, TDBGrid

object FormDatabase: TFormDatabase
Left = 473
Height = 240
Top = 169
Width = 184
Caption = 'FormDatabase'
OnDestroy = FormDestroy
LCLVersion = '0.9.30'
object SQLite3Connection: TSQLite3Connection
Connected = True
LoginPrompt = False
DatabaseName = 'DB.s3db'
KeepConnection = True
Password = 'test'
Transaction = SQLTransaction
UserName = 'user'
Options = []
left = 53
top = 123

  end
  object SQLTransaction: TSQLTransaction
    Active = True
    Action = caNone
    Database = SQLite3Connection
    left = 53
    top = 67
  end

end

excerpts from main form:

  object dsClients: TDatasource
    DataSet = qClients
    OnStateChange = Sync
    OnDataChange = dsClientsDataChange
    OnUpdateData = Sync
    left = 552
    top = 64
  end

object qClients: TSQLQuery
IndexName = 'DEFAULT_ORDER' // what is this, and why can't I change it? I tried IndexFieldNames = 'lastname' but that was EVEN SLOWER
AutoCalcFields = False
Database = FormDatabase.SQLite3Connection
Transaction = FormDatabase.SQLTransaction
ReadOnly = False
SQL.Strings = (
'select * from clients'
'order by lastname,firstname,middlename'
)
Params =
left = 504
top = 64

  end
Howard Lee Harkness

Reply by email

Martin Schreiber
to Howard Lee Harkness
Howard Lee Harkness wrote 10 lines
I am doing a conversion of a small database application from PostgreSQL to SQLite. I have gotten the program to work, but it it about 2 orders of magnitude slower than the PostgreSQL version. In particular, a query like select distinct clients.* from clients where lastname like 'Hark%' order by lastname, firstname, middlename;

Can you post the database file at a place where I can download it or send it to my Email address if it is not too big?

Martin

Reply by email

Howard Lee Harkness
to Martin Schreiber

Unfortunately, it has a lot of information that is sensitive (belongs to a client). I will look at building a database with dummy data. I can, however, provide the schema, if that would help.

Martin Schreiber wrote 22 lines
On Thursday 28 July 2011 17:54:03 Howard Lee Harkness wrote: > I am doing a conversion of a small database application from > PostgreSQL to SQLite. > > I have gotten the program to work, but it it about 2 orders of > magnitude slower than the PostgreSQL version. In particular, a query > like > > ? ? select distinct clients.* from clients where ?lastname like > 'Hark%' order by lastname, firstname, middlename; > Can you post the database file at a place where I can download it or send it to my Email address if it is not too big? Martin -- _______________________________________________ Lazarus mailing list http://lists.lazarus.freepascal.org/mailman/listinfo/lazarus
Howard Lee Harkness (214) 390-4896

Reply by email

Martin Schreiber
to Howard Lee Harkness
Howard Lee Harkness wrote
Unfortunately, it has a lot of information that is sensitive (belongs to a client). I will look at building a database with dummy data. I can, however, provide the schema, if that would help.

I need a reproducible testcase, please send the dummy data.

Martin

Reply by email

Martin Schreiber
to Howard Lee Harkness: Unfortunately, it has a lot of information that is sensitive (belongs to a client). I…
Howard Lee Harkness wrote
Unfortunately, it has a lot of information that is sensitive (belongs to a client). I will look at building a database with dummy data. I can, however, provide the schema, if that would help.

I tested with MSEide+MSEgui, it is instant too, see attachment. Suggestion: test with a similar simple Lazarus application.

Martin
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sqliteperf.png
Type: image/png
Size: 7698 bytes
Desc: not available
URL: <http://lists.lazarus.freepascal.org/pipermail/lazarus/attachments/20110729/cd175264/attachment-0001.png> -------------- next part --------------
A non-text attachment was scrubbed...
Name: sqliteperf.zip
Type: application/zip
Size: 8779 bytes
Desc: not available
URL: <http://lists.lazarus.freepascal.org/pipermail/lazarus/attachments/20110729/cd175264/attachment-0001.zip>

Reply by email

Martin Schreiber
to Howard Lee Harkness: I have established that the problem is not in SQLite, but has to be in my code somewhere.…
Howard Lee Harkness wrote 5 lines
I have established that the problem is not in SQLite, but has to be in my code somewhere. I suspect it is related to the transaction/commit problem I posted a few days ago, and I plan to track that down today if I can. I need to finish this application by tomorrow.

Sqlite manages transactions by file locks AFAIK. A not finished resultset fetch also locks the file. So tsqlquery.packetrecords must be set to -1. MSEgui sqldb version works with implicit transactions by default which makes life with Sqlite3 easier.
On the other hand writing many records to the database should be enclosed in a transaction otherwise there will be a slow filesync after every write statement.

Martin

Reply by email

Howard Lee Harkness
to Martin Schreiber: I tested with MSEide+MSEgui, it is instant too, see attachment. Suggestion: test with a…

I have established that the problem is not in SQLite, but has to be in my code somewhere. I suspect it is related to the transaction/commit problem I posted a few days ago, and I plan to track that down today if I can. I need to finish this application by tomorrow.

2011/7/29 Martin Schreiber :

Show quoted text 17 lines
Am Donnerstag 28 Juli 2011, 22:01:32 schrieb Howard Lee Harkness: > Unfortunately, it has a lot of information that is sensitive (belongs > to a client). I will look at building a database with dummy data. I > can, however, provide the schema, if that would help. > I tested with MSEide+MSEgui, it is instant too, see attachment. Suggestion: test with a similar simple Lazarus application. Martin -- _______________________________________________ Lazarus mailing list http://lists.lazarus.freepascal.org/mailman/listinfo/lazarus
Howard Lee Harkness (214) 390-4896

Reply by email

Ludo Brands
to Howard Lee Harkness
Show quoted text 6 lines
I have established that the problem is not in SQLite, but has to be in my code somewhere. I suspect it is related to the transaction/commit problem I posted a few days ago, and I plan to track that down today if I can. I need to finish this application by tomorrow.

I read your previous messages again. By the looks of it you're using a very hybrid way of using SQLite3Connection trying to do things yourself that the components are supposed to do (starting transactions for example). Here is a basic example of inserting data with direct sql queries:

-drop a TSQLite3Connection, a TSQLTransaction and a TSQLQuery on a form, a data module or create them in code.
-set TSQLite3Connection properties to connect to your database -assign TSQLTransaction.Database to the TSQLite3Connection instance -assign TSQLQuery.Database to the TSQLite3Connection instance and TSQLQuery.Transaction to the SQLTransaction instance. Active:= False, SQL.Text:=''.

Now to execute an insert query (assuming
TSQLite3Connection.Connected:=true):
    SQLQuery.SQL.Text:=sql;
    SQLQuery.ExecSQL;
    SQLTransaction.CommitRetaining;

That's it. SQLTransaction.CommitRetaining; does the COMMIT and a BEGIN to start a new transaction.

Just to be complete, I'm using lazarus 0.9.31 svn with fpc 2.5.1 svn.

Ludo

Reply by email

Howard Lee Harkness
to Ludo Brands

Thank you for your reply. I found it very interesting. I switched the code that I posted to the following (I refactored this so that I could call from several different places, for inserts, updates, and deletes):

procedure TfrmMain.ExecSQL(sql:string);
begin
     FormDatabase.SQLite3Connection.ExecuteDirect(sql);
     FormDatabase.SQLite3Connection.ExecuteDirect('commit');
     FormDatabase.SQLite3Connection.ExecuteDirect('begin transaction');
end;

I suppose that I could use a query component in there, but I don't know what difference that would make (this code is not presenting any performance issues; see below), and I'm a bit confused about the proper usage. There are some properties of TSQLQuery for which I have been unable to locate usage examples.

DeleteSQL
InsertSQL
UpdateSQL

I also don't know how to use the IndexDefs, IndexFieldNames, or IndexName properties. Every permutation of guesses I've tried either get deleted by the IDE, or don't actually change anything that I can detect. I have found some documentation on
http://free-pascal-general.1045716.n5.nabble.com/New-local-indexes-support-for-sqldb-td2817867.html which might shed some light on those parameters.

A search on TSQLQuery.DeleteSQL brought up a reference to http://community.freepascal.org:10000/bboards/message?message_id=275621&forum_id=24081 which partially answers a question *I* posed 4 years ago on that topic (that was for a prior iteration of this same program, which I managed to kluge together, then not look at again until last week). I see that the wiki was updated to include a mention of the fact that DeleteSQL, InsertSQL, and UpdateSQL can take parameters -- which is helpful information (parameters can help performance), but the performance hit that I am seeing is happening in a simple lookup (for which the transaction stuff is not needed). Here's the code:

 qClients.Close;
 qClients.SQL.Text:=sql;
 qClients.Open;

That part is pretty much in the form that you suggested. The Close is taking 1 to 2 seconds, and the Open is taking a similar amount of time. That does not seem reasonable, especially since the same code using PostgreSQL executed those three statements on the same database before a fast typist could hit the next key (I am incrementally building the lookup sql). That makes me wonder if I need to change how qClients is initialized. Here is the current setup of qClients:

  object qClients: TSQLQuery
    IndexName = 'DEFAULT_ORDER'
    AutoCalcFields = False
    Database = FormDatabase.SQLite3Connection
    Transaction = FormDatabase.SQLTransaction
    ReadOnly = False
    Params =
    left = 504
    top = 64
  end

I'm not seeing anything in there that I would recognize as a performance issue.

Here is the database form (user and password omitted):

object FormDatabase: TFormDatabase
Left = 473
Height = 240
Top = 169
Width = 184
Caption = 'FormDatabase'
LCLVersion = '0.9.30'
object SQLite3Connection: TSQLite3Connection
Connected = False
LoginPrompt = False
DatabaseName = 'C:\pruett\PascalDB\PruettClients.s3db' KeepConnection = True
Transaction = SQLTransaction
Options = []
left = 53
top = 123

  end
  object SQLTransaction: TSQLTransaction
    Active = False
    Action = caNone
    Database = SQLite3Connection
    left = 53
    top = 67
  end

end

I found it interesting that the program could open and use the database without the full path included, but the design-time part of the IDE requires a full pathname to do the "active at design time" trick. Not sure exactly how I guessed that.

I think I have a (ugly) workaround for the performance issue, but I would like to do better. Further suggestions are welcome, and don't worry about hurting my feelings; I realize that I'm not yet very proficient in Lazarus, since I have used it only sporadically.

Ludo Brands wrote 32 lines
> I have established that the problem is not in SQLite, but has > to be in my code somewhere. I suspect it is related to the > transaction/commit problem I posted a few days ago, and I > plan to track that down today if I can. I need to finish this > application by tomorrow. > I read your previous messages again. By the looks of it you're using a very hybrid way of using SQLite3Connection trying to do things yourself that the components are supposed to do (starting transactions for example). Here is a basic example of inserting data with direct sql queries: -drop a TSQLite3Connection, a TSQLTransaction and a TSQLQuery on a form, a data module or create them in code. -set TSQLite3Connection properties to connect to your database -assign TSQLTransaction.Database to the TSQLite3Connection instance -assign TSQLQuery.Database to the TSQLite3Connection instance and TSQLQuery.Transaction to the SQLTransaction instance. Active:= False, SQL.Text:=''. Now to execute an insert query (assuming TSQLite3Connection.Connected:=true): ? ?SQLQuery.SQL.Text:=sql; ? ?SQLQuery.ExecSQL; ? ?SQLTransaction.CommitRetaining; That's it. SQLTransaction.CommitRetaining; does the COMMIT and a BEGIN to start a new transaction. Just to be complete, I'm using lazarus 0.9.31 svn with fpc 2.5.1 svn. Ludo
Howard Lee Harkness (214) 390-4896

Reply by email

Ludo Brands
to Howard Lee Harkness
Show quoted text 18 lines
Thank you for your reply. I found it very interesting. I switched the code that I posted to the following (I refactored this so that I could call from several different places, for inserts, updates, and deletes): procedure TfrmMain.ExecSQL(sql:string); begin FormDatabase.SQLite3Connection.ExecuteDirect(sql); FormDatabase.SQLite3Connection.ExecuteDirect('commit'); FormDatabase.SQLite3Connection.ExecuteDirect('begin transaction'); end; I suppose that I could use a query component in there, but I don't know what difference that would make (this code is not presenting any performance issues; see below), and I'm a bit confused about the proper usage. There are some properties of TSQLQuery for which I have been unable to locate usage examples.

Th difference is that TSQLTransaction and TSQLQuery do some housekeeping (such as transactions, preparing statements, managing cursors) which can intervene with your direct access or vice versa. The effect doesn't have to be immediate.

Show quoted text 5 lines
DeleteSQL InsertSQL UpdateSQL

FPC didn't implement the Delphi Ttable. These propereties allow a programmer to create a control that acts like a TTable with the advantage of having far much more control. Downside is the more complex setup.

Show quoted text 9 lines
I also don't know how to use the IndexDefs, IndexFieldNames, or IndexName properties. Every permutation of guesses I've tried either get deleted by the IDE, or don't actually change anything that I can detect. I have found some documentation on http://free-pascal-general.1045716.n5.nabble.com/New-local-ind exes-support-for-sqldb-td2817867.html which might shed some light on those parameters.

To make it simple, when a select query is executed, all data is retrieved, at some point, in memory. Using "local" indices to the data in memory allow for quick reordering of data in a DBGrid for example. That is how clicking on a column header to reorder the data is working without retrieving data from the db backend.

Show quoted text 20 lines
A search on TSQLQuery.DeleteSQL brought up a reference to http://community.freepascal.org:10000/bboards/message?message_ id=275621&forum_id=24081 which partially answers a question *I* posed 4 years ago on that topic (that was for a prior iteration of this same program, which I managed to kluge together, then not look at again until last week). I see that the wiki was updated to include a mention of the fact that DeleteSQL, InsertSQL, and UpdateSQL can take parameters -- which is helpful information (parameters can help performance), but the performance hit that I am seeing is happening in a simple lookup (for which the transaction stuff is not needed). Here's the code: qClients.Close; qClients.SQL.Text:=sql; qClients.Open; That part is pretty much in the form that you suggested. The Close is taking 1 to 2 seconds, and the Open is taking a similar amount of time.

I traced through TSqlQuery.Close and there isn't happening a lot, except for cleaning up prepared statements and cursors. And that is where your direct BEGIN/COMMIT could very well confuse sqlite.
sqlite3_finalize(fstatement) is used to unprepare a statement. But is fstatement still valid after you have restarted a transaction? I doubt it. Do replace the ExecuteDirect with a TSqlQuery component. It is only a small change and it removes a lot of causes of errors.

Ludo

Reply by email

Howard Lee Harkness
to Ludo Brands
Ludo Brands wrote 7 lines
I traced through TSqlQuery.Close and there isn't happening a lot, except for cleaning up prepared statements and cursors. And that is where your direct BEGIN/COMMIT could very well confuse sqlite. sqlite3_finalize(fstatement) is used to unprepare a statement. But is fstatement still valid after you have restarted a transaction? I doubt it. Do replace the ExecuteDirect with a TSqlQuery component. It is only a small change and it removes a lot of causes of errors.

Ok, it seems like a reasonable suggestion, so I tried that.

The TSQLQuery object:
object qDIU: TSQLQuery
IndexName = 'DEFAULT_ORDER'
AutoCalcFields = False
Database = FormDatabase.SQLite3Connection
Transaction = FormDatabase.SQLTransaction
ReadOnly = False
Params =
left = 308
top = 48

  end
The updated procedure:
procedure TfrmMain.ExecSQL(sql:string);
begin
//     FormDatabase.SQLite3Connection.ExecuteDirect(sql);
//     FormDatabase.SQLite3Connection.ExecuteDirect('commit');
//     FormDatabase.SQLite3Connection.ExecuteDirect('begin transaction');
       qDIU.SQL.Text:=sql;
       qDIU.ExecSQL;
       FormDatabase.SQLTransaction.CommitRetaining;
end;

I also tracked down one other usage of ExecuteDirect in another form (not used for this test, but just in case) and applied the same change.

The result: No improvement. If anything, things got slightly worse.

However, I think maybe I have some insight into what is causing the problem. It looks like the lookups for the dependent tables are happening multiple times. Not sure why that would be (I didn't think I did anything to that part of the code other than change database engines), but I hope to find out shortly.

Howard Lee Harkness (214) 390-4896

Reply by email