On 27.01.2011 09:25, Tobias Rapp wrote: > I recently had the case that the MS Access database file of one program > became "fragmented": It contained less than 5 rows but still was over 10 > Megabytes in file size and insert/delete of rows took more than 4 seconds. > Finally it was necessary to replace the database file with a new, unused > instance to get rid of the performance problems. > > I plan to change the database engine from MS Access to SQLite now. Is this > something that can happen to (DI)SQLite database files, too? Do I need to > regulary perform some clean-up maintenance on it? My application is running > 24h/7days a week and using the database file for persistence of running jobs > (that is amount of rows is low but amount of insert/update/deletes is high). In principal, all disk files may suffer from fragmentation, more precisely file system fragmentation (see http://en.wikipedia.org/wiki/File_system_fragmentation). Database files are no exception. To work against this, most database engines do not shrink their data files after they DELETE records. Instead they reuse empty space when required by an INSERT or UPDATE. Generally, this counters file fragmentation, but it can also increase the size of the data file in worst case scenarios. In my experience, DISQLite3 does not tend to grow its database file size excessively for a balanced mixture of INSERTs, UPDATEs and INSERTs. I have attached a simple project which demonstrates this scenario and shows that the database file does not grow beyond a reasonable size after a certain number of operations. For explicit control of the database file size, DISQLite3 offers two SQL commands: * VACUUM rebuilds the entire database, reclaims free database pages and reduces the size of the database file. * PRAGMA auto_vacuum = full at every transaction commit moves free b-tree pages to the end of the database file and truncates the file. For your scenario, PRAGMA auto_vacuum = full will probably be the best option. Decide freely after reading the detailed documentation and discussion in the DISQLite3 help. Ralf
{ DISQLite3 demo project to simulate continuous INSERT, UPDATE and DELETE of random data. This demo shows that deleted database pages are in fact reused and the database file size does not grow excessively, even if VACUUM or PRAGMA auto_vacuum is not used. } program DISQLite3_Continuous_Insert_Update_Delete; {$APPTYPE CONSOLE} {$I DI.inc} uses {$IFDEF FastMM}FastMM4, {$ENDIF}Windows, SysUtils, DISQLite3Api; const Changes_Count = $10000; Changes_per_Transaction = $200; FileName = 'test.db3'; //---------------------------------------------------------------------------- function GetSizeOfFile(const FileName: string): Int64; var lpFileInformation: WIN32_FILE_ATTRIBUTE_DATA; Size: record case Integer of 0: (LowPart: LongWord; HighPart: LongWord); 1: (QuadPart: Int64); end; begin Result := 0; if GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, @lpFileInformation) then begin Size.LowPart := lpFileInformation.nFileSizeLow; Size.HighPart := lpFileInformation.nFileSizeHigh; Result := Size.QuadPart; end else RaiseLastOSError; end; var c: Cardinal; Data: array[0..$FFF] of AnsiChar; DB: sqlite3_ptr; insert_stmt, update_stmt, delete_stmt, begin_stmt, commit_stmt: sqlite3_stmt_ptr; inserts, updates, deletes: Cardinal; r, t: Cardinal; begin try try Randomize; sqlite3_check(sqlite3_initialize); try sqlite3_check(sqlite3_open(FileName, @DB)); try { Uncomment next line to enable full auto-vacuum. } // sqlite3_check(sqlite3_exec(DB, 'PRAGMA auto_vacuum = full', nil, nil, nil), DB); sqlite3_check(sqlite3_exec(DB, 'CREATE TABLE IF NOT EXISTS t (a TEXT)', nil, nil, nil), DB); sqlite3_check(sqlite3_prepare_v2(DB, 'BEGIN', -1, @begin_stmt, nil), DB); sqlite3_check(sqlite3_prepare_v2(DB, 'COMMIT', -1, @commit_stmt, nil), DB); sqlite3_check(sqlite3_prepare_v2(DB, 'INSERT INTO t VALUES (?)', -1, @insert_stmt, nil), DB); sqlite3_check(sqlite3_prepare_v2(DB, 'UPDATE t SET a = ? WHERE' + ' RowID > abs(random()) % (SELECT max(RowID) FROM t) LIMIT 1', -1, @update_stmt, nil), DB); sqlite3_check(sqlite3_prepare_v2(DB, 'DELETE FROM t WHERE' + ' RowID > abs(random()) % (SELECT max(RowID) FROM t) LIMIT 1', -1, @delete_stmt, nil), DB); WriteLn('Each . represents ', Changes_per_Transaction, ' changes wrapped inside a transaction.'); inserts := 0; updates := 0; deletes := 0; c := Changes_Count; t := 0; repeat if t >= Changes_per_Transaction then begin sqlite3_check(sqlite3_step(commit_stmt), DB); sqlite3_check(sqlite3_reset(commit_stmt), DB); t := 0; Write('.'); end; if t = 0 then begin sqlite3_check(sqlite3_step(begin_stmt), DB); sqlite3_check(sqlite3_reset(begin_stmt), DB); end; r := Random(3); case r of 0: begin sqlite3_check(sqlite3_bind_text(insert_stmt, 1, Data, Random(SizeOf(Data)), SQLITE_STATIC), DB); sqlite3_check(sqlite3_step(insert_stmt), DB); sqlite3_check(sqlite3_reset(insert_stmt), DB); Inc(inserts); end; 1: begin sqlite3_check(sqlite3_bind_text(update_stmt, 1, Data, Random(SizeOf(Data)), SQLITE_STATIC), DB); sqlite3_check(sqlite3_step(update_stmt), DB); sqlite3_check(sqlite3_reset(update_stmt), DB); Inc(updates); end; 2: begin sqlite3_check(sqlite3_step(delete_stmt), DB); sqlite3_check(sqlite3_reset(delete_stmt), DB); Inc(deletes); end; end; Inc(t); Dec(c); until c = 0; if t > 0 then begin sqlite3_check(sqlite3_step(commit_stmt), DB); sqlite3_check(sqlite3_reset(commit_stmt), DB); end; sqlite3_check(sqlite3_finalize(begin_stmt), DB); sqlite3_check(sqlite3_finalize(commit_stmt), DB); sqlite3_check(sqlite3_finalize(insert_stmt), DB); sqlite3_check(sqlite3_finalize(delete_stmt), DB); sqlite3_check(sqlite3_finalize(update_stmt), DB); { Uncomment next line to run VACUUM after database operations. } // sqlite3_check(sqlite3_exec(DB, 'VACUUM', nil, nil, nil), DB); WriteLn; WriteLn; WriteLn('Inserts: ', inserts); WriteLn('Updates: ', updates); WriteLn('Deletes: ', deletes); WriteLn('Total DB Changes: ', sqlite3_total_changes(DB)); WriteLn('File Size (bytes): ', GetSizeOfFile(FileName)); finally sqlite3_check(sqlite3_close(DB)); end; finally sqlite3_check(sqlite3_shutdown); end; except on e: Exception do WriteLn(e.Message); end; finally WriteLn; WriteLn('Done - Press ENTER to exit'); ReadLn; end; end.