Подтвердить что ты не робот

Может ли SQLite обрабатывать 90 миллионов записей?

Или я должен использовать другой молот, чтобы исправить эту проблему.

У меня очень простой пример использования для хранения данных, а также редкая матрица, которую я попытался сохранить в базе данных SQLite. Я создал таблицу:

create TABLE data ( id1 INTEGER KEY, timet INTEGER KEY, value REAL )

в который я вставляю много данных (800 элементов каждые 10 минут, 45 раз в день), большинство дней в году. Кортеж (id1, timet) всегда будет уникальным.

Значение времени составляет секунды с эпохи и всегда будет увеличиваться. Id1 для всех практических целей представляет собой случайное целое число. Однако, вероятно, существует только 20000 уникальных идентификаторов.

Мне бы хотелось получить доступ ко всем значениям, где id1 == someid или доступ ко всем элементам, где timet == sometime. В моих тестах, используя последний SQLite через интерфейс C в Linux, поиск одного из них (или любого варианта этого поиска) занимает приблизительно 30 секунд, что недостаточно быстро для моего использования.

Я попытался определить индекс для базы данных, но это замедлило вставку на полностью невыгодные скорости (возможно, я сделал это неправильно, хотя...)

В приведенной выше таблице очень медленный доступ к любым данным. Мой вопрос:

  • Является ли SQLite полностью неправильным инструментом для этого?
  • Могу ли я определить индексы, чтобы значительно ускорить процесс?
  • Должен ли я использовать что-то вроде HDF5 вместо SQL для этого?

Пожалуйста, извините мое самое основное понимание SQL!

Спасибо

Я включаю образец кода, который показывает, как скорость вставки замедляется при сканировании при использовании индексов. С инструкциями 'create index', код занимает 19 минут. Без этого он работает через 18 секунд.


#include <iostream>
#include <sqlite3.h>

void checkdbres( int res, int expected, const std::string msg ) 
{
  if (res != expected) { std::cerr << msg << std::endl; exit(1); } 
}

int main(int argc, char **argv)
{
  const size_t nRecords = 800*45*30;

  sqlite3      *dbhandle = NULL;
  sqlite3_stmt *pStmt = NULL;
  char statement[512];

  checkdbres( sqlite3_open("/tmp/junk.db", &dbhandle ), SQLITE_OK, "Failed to open db");

  checkdbres( sqlite3_prepare_v2( dbhandle, "create table if not exists data ( issueid INTEGER KEY, time INTEGER KEY, value REAL);", -1, & pStmt, NULL ), SQLITE_OK, "Failed to build create statement");
  checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
  checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");
  checkdbres( sqlite3_prepare_v2( dbhandle, "create index issueidindex on data (issueid );", -1, & pStmt, NULL ), SQLITE_OK, "Failed to build create statement");
  checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
  checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");
  checkdbres( sqlite3_prepare_v2( dbhandle, "create index timeindex on data (time);", -1, & pStmt, NULL ), SQLITE_OK, "Failed to build create statement");
  checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
  checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");

  for ( size_t idx=0; idx < nRecords; ++idx)
  {
    if (idx%800==0)
    {
      checkdbres( sqlite3_prepare_v2( dbhandle, "BEGIN TRANSACTION", -1, & pStmt, NULL ), SQLITE_OK, "Failed to begin transaction");
      checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute begin transaction" );
      checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize begin transaction");
      std::cout << "idx " << idx << " of " << nRecords << std::endl;
    }

    const size_t time = idx/800;
    const size_t issueid = idx % 800;
    const float value = static_cast<float>(rand()) / RAND_MAX;
    sprintf( statement, "insert into data values (%d,%d,%f);", issueid, (int)time, value );
    checkdbres( sqlite3_prepare_v2( dbhandle, statement, -1, &pStmt, NULL ), SQLITE_OK, "Failed to build statement");
    checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
    checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");

    if (idx%800==799)
    {
      checkdbres( sqlite3_prepare_v2( dbhandle, "END TRANSACTION", -1, & pStmt, NULL ), SQLITE_OK, "Failed to end transaction");
      checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute end transaction" );
      checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize end transaction");
    }
  }

  checkdbres( sqlite3_close( dbhandle ), SQLITE_OK, "Failed to close db" ); 
}

4b9b3361

Ответ 1

Вы вставляете все 800 элементов одновременно? Если вы это сделаете, вставки в транзакции значительно ускорят процесс.

См. http://www.sqlite.org/faq.html#q19

SQLite может обрабатывать очень большие базы данных. См. http://www.sqlite.org/limits.html

Ответ 2

Я просмотрел ваш код, и я думаю, что вы можете переусердствовать с инструкциями prepare и finalize. Я ни в коем случае не специалист по SQLite, но при подготовке оператора каждый раз через цикл выполнялись значительные накладные расходы.

Цитата из веб-сайта SQLite:

После подготовленного заявления оценивается одним или несколькими вызовами sqlite3_step(), он может быть reset в чтобы снова оценить по вызову до sqlite3_reset(). С помощью sqlite3_reset() в существующем подготовленного заявления, а новое подготовленное выражение избегает ненужные обращения sqlite3_prepare(). Во многих SQL заявления, время, необходимое для запуска sqlite3_prepare() равно или превышает время, необходимое для sqlite3_step(). Поэтому избегайте sqlite3_prepare() может привести к значительное улучшение производительности.

http://www.sqlite.org/cintro.html

В вашем случае, а не при подготовке нового заявления каждый раз, вы можете попробовать привязать новые значения к существующей инструкции.

Все это сказало, я думаю, что индексы могут быть фактическим виновником, так как время увеличивается, когда вы добавляете больше данных. Мне достаточно любопытно об этом, где я планирую провести какое-то тестирование в выходные.

Ответ 3

Так как мы знаем, что захват ваших данных происходит быстро, когда нет индекса в таблице, что может действительно работать:

  • Захват 800 значений во временной таблице без индекса.

  • Скопировать записи в основную таблицу (содержащие индексы), используя форму INSERT INTO, которая принимает инструкцию SELECT.

  • Удалите записи из временной таблицы.

Этот метод основан на теории, что INSERT INTO, который принимает инструкцию SELECT, быстрее, чем выполнение отдельных INSERT.

Шаг 2 можно выполнить в фоновом режиме, используя Асинхронный модуль, если он все же немного медленный. Это использует бит времени простоя между захватами.

Ответ 4

Отвечая на мой собственный вопрос, как место, где можно добавить некоторые детали:

Получается (как правильно было сказано выше), что создание индекса является медленным шагом, и каждый раз, когда я делаю другую транзакцию вставки, индекс обновляется, что занимает некоторое время. Мое решение:  (A) создать таблицу данных  (B) вставить все мои исторические данные (за несколько лет)  (C) создать индексы

Теперь все поисковые запросы и т.д. очень быстрые, и SQLite делает отличную работу. Последующие ежедневные обновления теперь занимают несколько секунд, чтобы вставить только 800 записей, но это не проблема, поскольку он работает только каждые 10 минут или около того.

Спасибо Роберту Харви и maxwellb за помощь/предложения/ответы выше.

Ответ 5

Рассмотрите возможность использования таблицы для новых вставок данного дня без индекса. Затем, в конце каждого дня, запустите script, который будет:

  • Вставьте новые значения из new_table в master_table
  • Очистить новую_таблицу на следующий день обработки

Если вы можете выполнять поиск по историческим данным в O (log n) и искать на сегодняшний день данные в O (n), это должно обеспечить хороший компромисс.

Ответ 6

Я не могу сказать по вашим спецификациям, но если поле идентификатора всегда увеличивается, а поле времени включает YYYYMMDD для уникальности и также всегда увеличивается, и вы выполняете поиск по идентификатору или поиск по времени, тогда самый простой решение без базы данных состояло бы в том, чтобы просто добавить все записи в текст или бинарный файл с фиксированным полем (поскольку они генерируются в отсортированном порядке) и использовать код для выполнения двоичного поиска для требуемых записей (например, найти первая запись с идентификатором или временем интереса, затем последовательно перейдите через нужный диапазон).

Ответ 7

При создании больших баз данных SQLite всегда добавляйте как можно больше данных, прежде чем создавать индексы. Это будет работать во много раз быстрее, чем при создании индексов перед вставкой данных.

Ответ 8

Теоретическое максимальное количество строк в таблице 2 ^ 64 (18446744073709551616 или около 1,8e + 19). Этот предел недостижим, так как максимальный размер базы данных составляет 140 терабайт. База данных размером 140 терабайт может содержать не более 1e + 13 строк, а затем только если нет индексов, и если каждая строка содержит очень мало данных.