Как уменьшить потребление памяти SQLite?
Я ищу способы уменьшить потребление памяти SQLite3 в моем приложении.
При каждом выполнении он создает таблицу со следующей схемой:
(main TEXT NOT NULL PRIMARY KEY UNIQUE,count INTEGER DEFAULT 0)
После этого база данных заполняется 50 тыс. операций в секунду. Напишите только.
Когда элемент уже существует, он обновляет "счет" с помощью запроса на обновление (я думаю, что это называется UPSERT). Это мои запросы:
INSERT OR IGNORE INTO table (main) VALUES (@SEQ);
UPDATE tables SET count=count+1 WHERE main = @SEQ;
Таким образом, с 5 миллионами операций на транзакцию, я могу писать очень быстро в БД.
Мне действительно не нравится дисковое пространство для этой проблемы, но у меня очень ограниченное пространство RAM. Таким образом, я не могу тратить слишком много памяти.
С sqlite3_user_memory() сообщает, что во время выполнения его потребление памяти увеличивается почти до 3 ГБ. Если я ограничу его до 2 ГБ через sqlite3_soft_heap_limit64(), производительность баз данных снизится почти до нуля, когда достигнет 2 ГБ.
Мне нужно было увеличить размер кеша до 1 М (размер страницы по умолчанию), чтобы достичь желаемой производительности.
Что я могу сделать для уменьшения потребления памяти?
Ответы
Ответ 1
Я бы:
- подготовьте заявления (если вы этого уже не делаете)
- уменьшить количество ВСТАВКИ на транзакцию (10 секунд = 500 000 звуков)
- используйте
PRAGMA locking_mode = EXCLUSIVE;
, если вы можете
Кроме того, (я не уверен, что вы знаете) PRAGMA cache_size
находится на страницах, а не в МБ. Убедитесь, что вы определили целевую память как PRAGMA cache_size * PRAGMA page_size
или в SQLite >= 3.7.10, вы также можете сделать PRAGMA cache_size = -kibibytes;
. Установка его на 1 М (illion) приведет к 1 или 2 ГБ.
Мне любопытно, как cache_size
помогает в INSERT, хотя...
Вы также можете попробовать и сравнить, если значение 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-2k в секунду.
Для обновлений 7M в транзакции journal_mode=WAL
работает медленнее всех остальных.
Я заполнил базу данных с 35 839 987 записями, и теперь моя установка занимает почти 4 секунды на каждую партию обновлений 65521, однако она даже не достигает 16 МБ потребляемой памяти.
Хорошо, здесь еще один:
Индексы в столбцах INTEGER PRIMARY KEY (не делайте этого)
Когда вы создаете столбец с INTIMER PRIMARY KEY, SQLite использует это столбца в качестве ключа для (индекса) структуры таблицы. Это скрытая index (поскольку он не отображается в таблице SQLite_Master) в этом столбце. Добавление другого индекса в столбец не требуется и никогда не будет используемый. Кроме того, это замедлит операции INSERT, DELETE и UPDATE вниз.
Кажется, вы определяете свой ПК как NOT NULL + UNIQUE. PK UNIQUE неявно.
Ответ 2
Похоже, что потребление высокой памяти может быть вызвано тем, что слишком много операций сосредоточено в одной большой транзакции. Попытка совершить меньшую транзакцию, например, за 1M операций, может помочь. Операции 5M за транзакцию будут потреблять много памяти.
Однако мы бы сбалансировали скорость работы и использование памяти.
Поскольку меньшая транзакция не помогает, PRAGMA shrink_memory
может быть выбором.
Используя sqlite3_status()
с SQLITE_STATUS_MEMORY_USED
для трассировки распределения динамической памяти и найдите точку.
Ответ 3
Предполагая, что все операции в одной транзакции распределены по всей таблице, так что все страницы таблицы должны быть доступны, размер рабочего набора:
- около 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
считывается с sqlite3_last_insert_rowid.
Выполнение отдельных SELECT
и INSERT
не медленнее, чем INSERT OR IGNORE
; SQLite выполняет ту же работу в любом случае.
Эта оптимизация полезна только в том случае, если большинство операций обновляют счетчик, который уже существует.
Ответ 4
В духе мозгового штурма я отважусь ответить. Я не тестировал такого человека:
Улучшить производительность SQLite в секунду в секунду?
Моя гипотеза заключается в том, что индекс первичного ключа текста может быть более интенсивным, чем несколько индексов на двух целых столбцах (что вам нужно для моделирования хэшированной таблицы).
EDIT: На самом деле вам даже не нужен первичный ключ для этого:
create table foo( slot integer, myval text, occurrences int);
create index ix_foo on foo(slot); // not a unique index
Integer первичный ключ (или не уникальный индекс в слоте) не даст вам быстрого способа определить, было ли ваше текстовое значение уже в файле. Поэтому для решения этого требования вы можете попробовать реализовать что-то, что я предложил другому плакату, имитируя хешированный ключ:
Оптимизация 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