On 12/15/2014 11:59 PM, Dan Kennedy wrote:
On 12/15/2014 11:11 PM, Paul wrote:
Hello, dear developers
Recently I've stumbled upon a very rare and strange bug.
The result of this is abnormal memory usage, that does not allow us
to remove
fair number of rows from a table due to the limit of memory,
available for 32bit
process. This is strange, because database size is somewhat small: 79M.
Digging around I finally managed to pinpoint when exactly does this
issue occur.
Another stange thing, though is that memory is successfully deallocated,
bacause no matter what, valgrind does not report definitely lost memory.
I want to present you my test case. You have two options to
manipulate it.
Two defines:
- NO_NESTED_TRANSACTION desables nested transaction.
- NO_CHILD_RECORDS disables population of 'child' table with data.
To compile:
# clang -o test -L/usr/local/lib -lsqlite3 test.c
My results
Without defines:
# clang -o test -L/usr/local/lib -lsqlite3 test.c
# ./test
Current mem: 0
Hi mem: 1294136920
Not okay, 1.2GiB peak memory usage.
With NO_CHILD_RECORDS
# clang -o test -DNO_CHILD_RECORDS -L/usr/local/lib -lsqlite3 test.c
# ./test
Current mem: 0
Hi mem: 421141176
A bit better, but still not ok.
With NO_NESTED_TRANSACTION:
# clang -o test -DNO_NESTED_TRANSACTION -L/usr/local/lib -lsqlite3
test.c
# ./test
Current mem: 0
Hi mem: 15100760
Seems ok.
With NO_NESTED_TRANSACTION and NO_CHILD_RECORDS:
# clang -o test -DNO_NESTED_TRANSACTION -DNO_CHILD_RECORDS
-L/usr/local/lib -lsqlite3 test.c
# ./test
Current mem: 0
Hi mem: 2554168
No doubt it's even better.
The memory is being used by the statement journal, which you have in
memory. If the app did not set "journal_mode=memory" and
"temp_store=memory", SQLite would create a really large temp file
instead of using memory. Which would still be sub-optimal, but might
not run into the 32-bit limit.
The reason the statement journal is growing so large is that SQLite
only truncates the statement journal when the outermost
sub-transaction is closed. Otherwise it just keeps on appending. i.e.
BEGIN;
SAVEPOINT xxx;
...
SAVEPOINT yyy;
...
RELEASE yyy; -- does not truncate statement journal
COMMIT xxx; -- truncates statement journal
COMMIT;
Your example has one explicit sub-transaction (equivalent to xxx) and
each DELETE statement is opening a second, implicit, sub-transaction
(equivalent to yyy).
With the child records included, each DELETE statement is modifying 3
db pages - one from table "bar", one from table "foo" and one from the
PRIMARY KEY index on "foo". 100,000 deletes, 4Kib page size, 3 pages
per delete == 1.2GiB. Or without the child records, just 1 page
modified per delete, so closer to 400MiB of memory. Without the
sub-transaction, the implicit sub-transaction created by each DELETE
becomes the outermost and so the statement journal doesn't grow much
at all. So not much memory used in that case.
Another idea would be to use a deferred foreign key constraint. That way
the DELETE operations will not need the statement journal at all.
_______________________________________________
sqlite-users mailing list
sqlite-users@sqlite.org
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users