Как уменьшить потребление памяти SQLite?
Я ищу способы уменьшить потребление памяти SQLite3 в моем приложении.
При каждом выполнении он создает таблицу со следующей схемой:
(main TEXT NOT NULL PRIMARY KEY UNIQUE,count INTEGER DEFAULT 0)
После этого база данных заполняется 50 тыс. Операций в секунду. Пиши только.
Когда элемент уже существует, он обновляет "count" с помощью запроса на обновление (я думаю, что это называется UPSERT). Это мои запросы:
INSERT OR IGNORE INTO table (main) VALUES (@SEQ);
UPDATE tables SET count=count+1 WHERE main = @SEQ;
Таким образом, с 5 миллионами операций на транзакцию я могу очень быстро записывать в БД.
Я действительно не забочусь о дисковом пространстве для этой проблемы, но у меня есть очень ограниченное пространство ОЗУ. Таким образом, я не могу тратить слишком много памяти.
С sqlite3_user_memory() сообщает, что его потребление памяти возрастает почти до 3 ГБ во время выполнения. Если я ограничу его до 2 ГБ с помощью sqlite3_soft_heap_limit64(), производительность операций с базой данных упадет почти до нуля, когда достигнет 2 ГБ.
Мне пришлось увеличить размер кэша до 1 МБ (размер страницы по умолчанию), чтобы достичь желаемой производительности.
Что я могу сделать, чтобы уменьшить потребление памяти?
4 ответа
Я мог бы:
- подготовьте заявления (если вы еще этого не делаете)
- уменьшите количество INSERT на транзакцию (10 сек = 500000 звуков)
- использование
PRAGMA locking_mode = EXCLUSIVE;
если ты можешь
Кроме того, (я не уверен, если вы знаете) PRAGMA cache_size
в страницах, а не в мегабайтах. Убедитесь, что вы определили свою целевую память как PRAGMA cache_size * PRAGMA page_size
или в SQLite >= 3.7.10 вы также можете сделать PRAGMA cache_size = -kibibytes;
, Установка его на 1 М (иллюзия) приведет к 1 или 2 ГБ.
Мне любопытно как cache_size
помогает во ВСТАВКАХ, хотя...
Вы также можете попробовать и сравнительный тест, если PRAGMA temp_store = FILE;
имеет значение.
И, конечно же, всякий раз, когда ваша база данных не записывается в:
PRAGMA shrink_memory;
VACUUM;
В зависимости от того, что вы делаете с базой данных, это также может помочь:
PRAGMA auto_vacuum = 1|2;
PRAGMA secure_delete = ON;
Я провел несколько тестов со следующими прагмами:
busy_timeout=0;
cache_size=8192;
encoding="UTF-8";
foreign_keys=ON;
journal_mode=WAL;
legacy_file_format=OFF;
synchronous=NORMAL;
temp_store=MEMORY;
Тест № 1:
INSERT OR IGNORE INTO test (time) VALUES (?);
UPDATE test SET count = count + 1 WHERE time = ?;
Пик ~109к обновлений в секунду.
Тест № 2:
REPLACE INTO test (time, count) VALUES
(?, coalesce((SELECT count FROM test WHERE time = ? LIMIT 1) + 1, 1));
Пик на ~120 тыс. Обновлений в секунду.
Я тоже пробовал PRAGMA temp_store = FILE;
и обновления сбрасываются на ~1-2к в секунду.
Для 7M обновлений в транзакции, journal_mode=WAL
медленнее, чем все остальные.
Я заполнил базу данных 35 839 987 записями, и теперь моя установка занимает почти 4 секунды на каждый пакет из 65521 обновлений, однако он даже не достигает 16 МБ потребления памяти.
Хорошо, вот еще один:
Индексы на столбцах INTEGER PRIMARY KEY (не делайте этого)
Когда вы создаете столбец с INTEGER PRIMARY KEY, SQLite использует этот столбец в качестве ключа для (индексации) структуры таблицы. Это скрытый индекс (так как он не отображается в таблице SQLite_Master) для этого столбца. Добавление другого индекса для столбца не требуется и никогда не будет использоваться. Кроме того, это замедлит операции INSERT, DELETE и UPDATE.
Вы, кажется, определяете свой ПК как НЕ NULL + UNIQUE. ПК уникален неявно.
Кажется, что высокое потребление памяти может быть вызвано тем фактом, что слишком много операций сосредоточено в одной большой транзакции. Может помочь попытка совершить транзакцию меньшего размера, например, за 1М операций. 5 миллионов операций на транзакцию будут занимать много памяти.
Однако мы бы сбалансировали скорость работы и использование памяти.
Так как меньшая транзакция не помогает, PRAGMA shrink_memory
может быть выбор.
С помощью sqlite3_status()
с SQLITE_STATUS_MEMORY_USED
отследить динамическое распределение памяти и найти точку.
Предполагая, что все операции в одной транзакции распределены по всей таблице, так что все страницы таблицы должны быть доступны, размер рабочего набора составляет:
- около 1 ГБ для данных таблицы, плюс
- около 1 ГБ для индекса на
main
колонка, плюс - около 1 ГБ для исходных данных всех страниц таблицы, измененных в транзакции (вероятно, все они).
Вы можете попытаться уменьшить объем данных, который изменяется для каждой операции, перемещая count
столбец в отдельную таблицу:
CREATE TABLE main_lookup(main TEXT NOT NULL UNIQUE, rowid INTEGER PRIMARY KEY);
CREATE TABLE counters(rowid INTEGER PRIMARY KEY, count INTEGER DEFAULT 0);
Затем для каждой операции:
SELECT rowid FROM main_lookup WHERE main = @SEQ;
if not exists:
INSERT INTO main_lookup(main) VALUES(@SEQ);
--read the inserted rowid
INSERT INTO counters VALUES(@rowid, 0);
UPDATE counters SET count=count+1 WHERE rowid = @rowid;
В C вставлен rowid
читается с http://www.sqlite.org/c3ref/last_insert_rowid.html.
Делать отдельно SELECT
а также INSERT
не медленнее, чем INSERT OR IGNORE
; SQLite делает ту же работу в любом случае.
Эта оптимизация полезна, только если большинство операций обновляют уже существующий счетчик.
В духе мозгового штурма я рискну ответить. Я не делал никаких испытаний, как этот парень:
Улучшить производительность SQLite INSERT-в-секунду?
Моя гипотеза заключается в том, что индекс в текстовом первичном ключе может быть более интенсивным в ОЗУ, чем пара индексов в двух целочисленных столбцах (что необходимо для имитации хэш-таблицы).
РЕДАКТИРОВАТЬ: На самом деле, вам даже не нужен первичный ключ для этого:
create table foo( slot integer, myval text, occurrences int);
create index ix_foo on foo(slot); // not a unique index
Целочисленный первичный ключ (или неуникальный индекс на слоте) не даст вам быстрого способа определить, было ли ваше текстовое значение уже в файле. Таким образом, чтобы удовлетворить это требование, вы можете попытаться реализовать то, что я предложил, для другого автора, имитируя хешированный ключ:
Оптимизация SQLite для миллионов записей?
Функция хэш-ключа позволит вам определить, где будет храниться текстовое значение, если оно существует.
http://www.cs.princeton.edu/courses/archive/fall08/cos521/hash.pdf http://www.fearme.com/misc/alg/node28.html http://cs.mwsu.edu/~griffin/courses/2133/downloads/Spring11/p677-pearson.pdf