Page 1 of 2

SQLite In-Memory Database (VERY FAST )

Posted: Sun May 20, 2007 3:01 pm
by pdwyer
:shock:

The code below is basically the same as I posted before with a few very minor differences, first it uses an in-memory database instead of a file database, and second it writes a few thousand rows without a transaction to compare speed performance. The difference is impressive

It's fast enough that you could use this just because you wanted to use SQL statements on data that you have in an array or whatever, you could use SQLite as your array of structures and have access to the rich sql language to query it and get very good performance. It won't be as quick as arrays if your not doing much with them or just looping through them but if you have complex searches and queries to do it might be worth trying this to dump to an in-memory database and do some SQL queries.

Definately worth playing around with if you needs get a bit complex

Code: Select all


#SQLITE3_ISO8859         =   1   ;
#SQLITE3_OK              =   0   ;'*/ Successful result */
#SQLITE3_ERROR           =   1   ;'*/ SQL error or missing database */
#SQLITE3_INTERNAL        =   2   ;'*/ An internal logic error in SQLite */
#SQLITE3_PERM            =   3   ;'*/ Access permission denied */
#SQLITE3_ABORT           =   4   ;'*/ Callback routine requested an abort */
#SQLITE3_BUSY            =   5   ;'*/ The database file is locked */
#SQLITE3_LOCKED          =   6   ;'*/ A table in the database is locked */
#SQLITE3_NOMEM           =   7   ;'*/ A malloc() failed */
#SQLITE3_READONLY        =   8   ;'*/ Attempt to write a readonly database */
#SQLITE3_INTERRUPT       =   9   ;'*/ Operation terminated by sqlite3_interrupt() */
#SQLITE3_IOERR           =   10   ;'*/ Some kind of disk I/O error occurred */
#SQLITE3_CORRUPT         =   11   ;'*/ The database disk image is malformed */
#SQLITE3_NOTFOUND        =   12   ;'*/ (Internal Only) Table or record not found */
#SQLITE3_FULL            =   13   ;'*/ Insertion failed because database is full */
#SQLITE3_CANTOPEN        =   14   ;'*/ Unable to open the database file */
#SQLITE3_PROTOCOL        =   15   ;'*/ Database lock protocol error */
#SQLITE3_EMPTY           =   16   ;'*/ (Internal Only) Database table is empty */
#SQLITE3_SCHEMA          =   17   ;'*/ The database schema changed */
#SQLITE3_TOOBIG          =   18   ;'*/ Too much data for one row of a table */
#SQLITE3_CONSTRAINT      =   19   ;'*/ Abort due to contraint violation */
#SQLITE3_MISMATCH        =   20   ;'*/ Data type mismatch */
#SQLITE3_MISUSE          =   21   ;'*/ Library used incorrectly */
#SQLITE3_NOLFS           =   22   ;'*/ Uses OS features not supported on host */
#SQLITE3_AUTH            =   23   ;'*/ Authorization denied */
#SQLITE3_ROW             =   100  ;'*/ sqlite3_step() has another row ready */
#SQLITE3_DONE            =   101  ;'*/ sqlite3_step() has finished executing */

Global LastMessage.s
Global LibIsInitializedWithIncludedDLL.l
Global sqlite3_close.l
Global sqlite3_exec.l
Global sqlite3_open.l
Global sqlite3_errmsg.l
Global sqlite3DLL.l

Declare ProcMain()
Declare CallBack_ReturnRows(NullPtr.l,ArgCount.l,*Values.l, *ColNames.l)

; ==============================================================================

; Program Entry Point
ProcMain()

; ==============================================================================

Procedure.l ProcMain()

    SQLiteDLLPath.s = "sqlite3.dll" 
    DataBaseFile.s = ":memory:" ;"test0001.dat" ;

    ;Load SQLite DLL
    sqlite3DLL = LoadLibrary_(@SQLiteDLLPath)
        sqlite3_close       = GetProcAddress_(sqlite3DLL, "sqlite3_close")
        sqlite3_exec        = GetProcAddress_(sqlite3DLL, "sqlite3_exec")
        sqlite3_open        = GetProcAddress_(sqlite3DLL, "sqlite3_open")
        sqlite3_errmsg      = GetProcAddress_(sqlite3DLL, "sqlite3_errmsg")
    
    ;Prep SQL statements
    CreateDB.s =            "CREATE TABLE 'MyTable' (" + #CRLF$ 
    CreateDB = CreateDB +   "'ID' INTEGER  Not NULL PRIMARY KEY AUTOINCREMENT," + #CRLF$
    CreateDB = CreateDB +   "'StringCol1' TEXT  NULL," + #CRLF$
    CreateDB = CreateDB +   "'StringCol2' TEXT  NULL," + #CRLF$
    CreateDB = CreateDB +   "'IntCol1' INTEGER  NULL," + #CRLF$
    CreateDB = CreateDB +   "'IntCol2' INTEGER  NULL" + #CRLF$
    CreateDB = CreateDB +   ")"    
    CreateDB = ReplaceString(CreateDB, "'", #DOUBLEQUOTE$)

    SQLAddRow.s = "Insert into MyTable(StringCol1, StringCol2, IntCol1, IntCol2) values('Hello', 'NextColumn', 100, 12345678) "

    CurrentTime.f = ElapsedMilliseconds()

    ;Open Database
    If CallCFunctionFast(sqlite3_open, DataBaseFile, @dbHandle)
        CallCFunctionFast(sqlite3_errmsg, @RetVal)
        Debug = PeekS(RetVal)
    Else
        ; If successful send queries

        ; Send Created DB SQL, table may exist
        QueryRet.l = CallCFunctionFast(sqlite3_exec, DBHandle, CreateDB, @CallBack_ReturnRows() , #Null, @ReturnValue)      
        If QueryRet
            CallCFunctionFast(sqlite3_errmsg, @ReturnValue)
            Debug = PeekS(ReturnValue)
        EndIf
        
        ; Send Insert Row SQL, table may exist
        For i.l = 1 To 5000
        QueryRet.l = CallCFunctionFast(sqlite3_exec, DBHandle, SQLAddRow, @CallBack_ReturnRows() , #Null, @ReturnValue) 
            If QueryRet
                CallCFunctionFast(sqlite3_errmsg, @ReturnValue)
                Debug = PeekS(ReturnValue)
            EndIf       
        Next
        ;Send arbitrary Query
        QueryRet.l = CallCFunctionFast(sqlite3_exec, DBHandle, "Select * from MyTable", @CallBack_ReturnRows() , #Null, @ReturnValue) 
        If QueryRet
            CallCFunctionFast(sqlite3_errmsg, @ReturnValue)
            Debug = PeekS(ReturnValue)
        EndIf 

        ;Close Database
        CallCFunctionFast(sqlite3_close, DBHandle)
    
    EndIf

    Debug  ElapsedMilliseconds() - CurrentTime 

    ;Unload DLL on program end
    FreeLibrary_(sqlite3DLL)

EndProcedure

;==============================================================================

ProcedureC.l CallBack_ReturnRows(NullPtr.l,ArgCount.l,*Values.l, *ColNames.l) ;argcount = column count
  
    Dim ValuePts.l(ArgCount)
    Row.s = ""

    For ValLoop.l = 0 To ArgCount -1
        ValuePts(ValLoop) = PeekL(*Values + (ValLoop * 4)) 
        Row = Row + PeekS(ValuePts(ValLoop) ) + "," 
    Next
        
    Debug Row
        
    ProcedureReturn = 0  ;Proceed with next row

EndProcedure                               

;======================================================================================



Posted: Sun May 20, 2007 3:06 pm
by pdwyer
feel free to comment out "Debug Row " in that last callback proc to just get the times, on my PC it was 187ms vs 21sec (115x speedup) over a disk file. That debug statement is there just to show you that the data did get there and is queriable, it didn't just fly off into space :)


:!: :arrow: Another point to note, since the PBOSL version of SQLite does a file check before creating or opening a database...

Code: Select all

If FileSize(sDataBase) = -1 
or
If FileSize(sDataBase) <> -1
I don't think in-memory databases will be usable. Something to consider for the next version perhaps

Posted: Sun May 20, 2007 3:57 pm
by rsts
I'm still working on the first example and you post a second?

Seriously, good stuff here that I'm attempting to learn from.

We do use sqlite from memory, using either SQLite3_InitLibFromMemory(?SQLite3_DLL) or sqlite3_lib = LoadLibraryM(?SQLite3_DLL) depending on the lib you choose.

Since you're fast becoming another one of my 'heros' :) I have a practical sqlte issue I'm currently attempting to handle. I need to create and load a table from a 1GB database of text and images (the images account for most of the size. There are only about 1500 records in the database). The extracted table is between 400 and 500 columns which I then parse by row, extracting relevant data to a linked list via -

SQLite3_GetTable("select * from table where name = "dwyer"
then First_row, Next_row to read the rows.

Then get the row data by index, which uses
*Recordset\sValue = PeekS(PeekL(*Recordset\Handle + (lindex * 4) + (*Recordset\CurrentPos * *Recordset\Cols) * 4))

There are eight rows extracted - all are text.
This seems similar to what you're doing.

My question/concern is that it can take upward of 15 seconds for the above mentioned table to be created and the relevant data extracted. This being my only real experience with sqlite, I don't know whether that is 'normal' for an extraction of that nature or whether I could improve it. Might you have an opinion/suggestions?

Also, would you know if sqlite frees it's own memory following a SQLite3_Column_Blob(Statement, n)? I've been able to find 'speculation' that it does via a google search, but was wondering if you might know (since I have a memory leak and want to eliminate sqlite3 blob from consideration, or free the memory, if I need to).

Many thanks for your contributions to the forums. The more sqlite knowledge the better :)

cheers

whoops - hope it's ok to ask a coding question in 'tricks and tips' :oops:

Posted: Sun May 20, 2007 4:54 pm
by Dare
rsts wrote:I'm still working on the first example and you post a second?
:D
rsts wrote:Many thanks for your contributions to the forums.
Agreed. Almost makes up for introducing that ruddy game .....

Keep the code coming. Like rsts I am not getting through them as fast as you're producing them but it all helps.

Now all we need is a workaround for the missing sqlite "DROP COLUMN" .. :D

Posted: Sun May 20, 2007 6:36 pm
by netmaestro
rsts wrote:We do use sqlite from memory, using either SQLite3_InitLibFromMemory(?SQLite3_DLL) or sqlite3_lib = LoadLibraryM(?SQLite3_DLL) depending on the lib you choose.
You might have missed the point, he's got the table in memory, not the library:

Code: Select all

DataBaseFile.s = ":memory:"

Posted: Sun May 20, 2007 8:38 pm
by srod
Huh, I didn't know that SQLite could work with memory based databases like this! You learn something new every day. :)

Thanks pdwyer.

Posted: Sun May 20, 2007 8:47 pm
by rsts
netmaestro wrote:
rsts wrote:We do use sqlite from memory, using either SQLite3_InitLibFromMemory(?SQLite3_DLL) or sqlite3_lib = LoadLibraryM(?SQLite3_DLL) depending on the lib you choose.
You might have missed the point, he's got the table in memory, not the library:

Code: Select all

DataBaseFile.s = ":memory:"
Duh - you caught me there :oops:

Posted: Mon May 21, 2007 12:19 am
by pdwyer
rsts, in regards to your question, I'm not quite sure. One thing I want to play with if I get any time this week is using blobs and returning the binary data in there. I know it's doable but I've never needed to do it so I've never looked into what the return data looks like.

15secs sounds like a long time to return for 8 rows, but the comment about 400-500 columns sounds like it might be an issue.

Still, 1gb database with only 1500 rows says that these rows can be very large (from the image size I guess). How big does the process get in memory when you run this? Maybe it's paging back to disk

Posted: Mon May 21, 2007 9:05 am
by Dare
At first I thought this (the in-memory db) was nice and novel, but I couldn't really see how it would be all that useful versus arrays and lists with a little b-tree or binary-lookup tweaking, etc.

On consideration though, this can very handy indeed! I am very glad you brought this to our attention.

Thanks mate!

Posted: Tue May 22, 2007 8:44 am
by dige
@pdwyer: thank you very much, that opens up completely new possibilities!!

Posted: Sat Sep 27, 2008 10:46 am
by thanos
Is it possible to save the virtually created database (":memory:") to disk as a real SQLite database?
I mean with a copy or save memory procedure to be as fast as possible.
Regards.

Thanos

Posted: Sat Sep 27, 2008 12:02 pm
by Kiffi
thanos wrote:Is it possible to save the virtually created database (":memory:") to disk as a real SQLite database?
I mean with a copy or save memory procedure to be as fast as possible.
http://www.purebasic.fr/english/viewtop ... ory#248007

Greetings ... Kiffi

Posted: Sat Sep 27, 2008 1:58 pm
by thanos
Kiffi wrote:
thanos wrote:Is it possible to save the virtually created database (":memory:") to disk as a real SQLite database?
I mean with a copy or save memory procedure to be as fast as possible.
http://www.purebasic.fr/english/viewtop ... ory#248007

Greetings ... Kiffi
Thanks Kiffi!

Regards.

Thanos

Posted: Sat Sep 27, 2008 4:52 pm
by thanos
Short question:
Is SQlite useful in multi user environments?
I mean with record locking etc.
Regards.

Thanos

Posted: Sun Sep 28, 2008 5:30 am
by pdwyer
not really, no.

You'd need a middle tier to serialise the requests. selects can run together but updates and inserts etc can't