Может ли SQLite обрабатывать 90 миллионов записей? - PullRequest
41 голосов
/ 01 июля 2010

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

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

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

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

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

Затем я хотел бы получить доступ ко всем значениям, где id1 == someid, или получить доступ ко всем элементам, где timet == когда-нибудь. В моих тестах с использованием последней версии 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" ); 
}

Ответы [ 8 ]

29 голосов
/ 01 июля 2010

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

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

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

9 голосов
/ 02 июля 2010

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

Цитирование с сайта SQLite:

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

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

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

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

7 голосов
/ 05 июля 2010

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

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

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

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

5 голосов
/ 02 июля 2010

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

  1. Захват 800 значений во временную таблицу безindex.

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

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

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

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

3 голосов
/ 02 июля 2010

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

  1. Вставлять новые значения из new_table в master_table
  2. Очистить new_table для следующего дня обработки

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

2 голосов
/ 02 июля 2010

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

0 голосов
/ 12 октября 2017

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

0 голосов
/ 22 июля 2013

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...