Какой самый быстрый способ вставить 100 000 записей из одной базы данных в другую? - PullRequest
17 голосов
/ 24 января 2010

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

Я подключил вторую базу данных к основной и запустил insert into table select * from sync.table.

Это очень медленно, я думаю, что это займет около 10 минут. Я заметил, что размер файла журнала постепенно увеличивается.

Как я могу ускорить это?

ИЗД. 1

У меня отключены индексы, и у меня отключен журнал. Использование

insert into table select * from sync.table

это все еще занимает 10 минут.

ИЗД. 2

Если я выполню запрос типа

select id,invitem,invid,cost from inventory where itemtype = 1 
order by invitem limit 50 

это занимает 15-20 секунд.

Схема таблицы:

CREATE TABLE inventory  
('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
 'serverid' INTEGER NOT NULL DEFAULT 0,
 'itemtype' INTEGER NOT NULL DEFAULT 0,
 'invitem' VARCHAR,
 'instock' FLOAT  NOT NULL DEFAULT 0,
 'cost' FLOAT NOT NULL DEFAULT 0,
 'invid' VARCHAR,
 'categoryid' INTEGER  DEFAULT 0,
 'pdacategoryid' INTEGER DEFAULT 0,
 'notes' VARCHAR,
 'threshold' INTEGER  NOT NULL DEFAULT 0,
 'ordered' INTEGER  NOT NULL DEFAULT 0,
 'supplier' VARCHAR,
 'markup' FLOAT NOT NULL DEFAULT 0,
 'taxfree' INTEGER NOT NULL DEFAULT 0,
 'dirty' INTEGER NOT NULL DEFAULT 1,
 'username' VARCHAR,
 'version' INTEGER NOT NULL DEFAULT 15
)

Индексы создаются как

CREATE INDEX idx_inventory_categoryid ON inventory (pdacategoryid);
CREATE INDEX idx_inventory_invitem ON inventory (invitem);
CREATE INDEX idx_inventory_itemtype ON inventory (itemtype);

Мне интересно, вставка в ... select * from не самый быстрый встроенный способ для массивного копирования данных?

ИЗД. 3

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

Ответы [ 8 ]

10 голосов
/ 24 января 2010

Если целью является какая-либо версия MS SQL Server, SqlBulkCopy предлагает эффективную вставку для больших наборов данных, это похоже на команду bcp.

Вы также можете отключить / удалить некластеризованные индексы перед вставкой и заново создать их после.

В SQLite это обычно довольно быстро:

.dump ?TABLE? ...      Dump the database in an SQL text format
.import FILE TABLE     Import data from FILE into TABLE

Также попробуйте: PRAGMA journal_mode = OFF

К вашему сведению, вы должны иметь возможность запустить утилиту командной строки в Windows Mobile, если вы включите ее в свой пакет.

6 голосов
/ 05 февраля 2010

Я не думаю , что подключение двух баз данных и запуск INSERT INTO foo (SELECT * FROM bar) - самый быстрый способ сделать это. Если вы выполняете синхронизацию между портативным устройством и сервером (или другим устройством), может ли механизм транспорта стать узким местом? Или два файла базы данных уже находятся в одном файле? Если файловая система на устройстве медленнее флэш-памяти, может ли это быть узким местом?

Можете ли вы скомпилировать / запустить сырой код SQLite C на вашем устройстве? (Я думаю, что объединение RAW sqlite3 должно компилироваться для WinCE / Mobile) Если да, то вы готовы:

  • Чтобы написать код на C (используя SQLite C API)
  • Увеличьте риск потери данных, отключив ведение журнала на диске

Должна быть возможность написать небольшой автономный исполняемый файл для чрезвычайно быстрого копирования / синхронизации записей по 100 КБ между двумя базами данных.

Я опубликовал кое-что из того, что узнал об оптимизации вставок SQLite, здесь: Улучшение производительности SQLite по INSERT в секунду?


Редактировать: Пробовал это с реальным кодом ...

Я не знаю всех шагов, связанных с созданием исполняемого файла Windows Mobile, но объединение SQLite3 должно компилироваться из коробки с использованием Visual Studio. Вот пример main.c программы, которая открывает две базы данных SQLite (обе должны иметь одну и ту же схему - см. Оператор #define TABLE), выполняет инструкцию SELECT и затем связывает полученные строки с оператором INSERT:

/*************************************************************
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "sqlite3.h"

#define SOURCEDB "C:\\source.sqlite"
#define DESTDB "c:\\dest.sqlite"

#define TABLE "CREATE TABLE IF NOT EXISTS TTC (id INTEGER PRIMARY KEY, Route_ID TEXT, Branch_Code TEXT, Version INTEGER, Stop INTEGER, Vehicle_Index INTEGER, Day Integer, Time TEXT)"
#define BUFFER_SIZE 256

int main(int argc, char **argv) {

    sqlite3 * sourceDB;
    sqlite3 * destDB;

    sqlite3_stmt * insertStmt;
    sqlite3_stmt * selectStmt;

    char * insertTail = 0;
    char * selectTail = 0;

    int n = 0;
    int result = 0;
    char * sErrMsg = 0;
    clock_t cStartClock;

    char sInsertSQL [BUFFER_SIZE] = "\0";
    char sSelectSQL [BUFFER_SIZE] = "\0";

    /* Open the Source and Destination databases */
    sqlite3_open(SOURCEDB, &sourceDB);
    sqlite3_open(DESTDB, &destDB);

    /* Risky - but improves performance */
    sqlite3_exec(destDB, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);
    sqlite3_exec(destDB, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);

    cStartClock = clock(); /* Keep track of how long this took*/

    /* Prepared statements are much faster */
    /* Compile the Insert statement */
    sprintf(sInsertSQL, "INSERT INTO TTC VALUES (NULL, @RT, @BR, @VR, @ST, @VI, @DT, @TM)");
    sqlite3_prepare_v2(destDB, sInsertSQL, BUFFER_SIZE, &insertStmt, &insertTail);

    /* Compile the Select statement */
    sprintf(sSelectSQL, "SELECT * FROM TTC LIMIT 100000");
    sqlite3_prepare_v2(sourceDB, sSelectSQL, BUFFER_SIZE, &selectStmt, &selectTail);

    /* Transaction on the destination database */
    sqlite3_exec(destDB, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);

    /* Execute the Select Statement.  Step through the returned rows and bind
    each value to the prepared insert statement.  Obviously this is much simpler
    if the columns in the select statement are in the same order as the columns
    in the insert statement */
    result = sqlite3_step(selectStmt);
    while (result == SQLITE_ROW)
    {

        sqlite3_bind_text(insertStmt, 1, sqlite3_column_text(selectStmt, 1), -1, SQLITE_TRANSIENT); /* Get Route */
        sqlite3_bind_text(insertStmt, 2, sqlite3_column_text(selectStmt, 2), -1, SQLITE_TRANSIENT); /* Get Branch */
        sqlite3_bind_text(insertStmt, 3, sqlite3_column_text(selectStmt, 3), -1, SQLITE_TRANSIENT); /* Get Version */
        sqlite3_bind_text(insertStmt, 4, sqlite3_column_text(selectStmt, 4), -1, SQLITE_TRANSIENT); /* Get Stop Number */
        sqlite3_bind_text(insertStmt, 5, sqlite3_column_text(selectStmt, 5), -1, SQLITE_TRANSIENT); /* Get Vehicle */
        sqlite3_bind_text(insertStmt, 6, sqlite3_column_text(selectStmt, 6), -1, SQLITE_TRANSIENT); /* Get Date */
        sqlite3_bind_text(insertStmt, 7, sqlite3_column_text(selectStmt, 7), -1, SQLITE_TRANSIENT); /* Get Time */

        sqlite3_step(insertStmt);       /* Execute the SQL Insert Statement (Destination Database)*/
        sqlite3_clear_bindings(insertStmt); /* Clear bindings */
        sqlite3_reset(insertStmt);      /* Reset VDBE */

        n++;

        /* Fetch next from from source database */
        result = sqlite3_step(selectStmt);

    }

    sqlite3_exec(destDB, "END TRANSACTION", NULL, NULL, &sErrMsg);

    printf("Transfered %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);

    sqlite3_finalize(selectStmt);
    sqlite3_finalize(insertStmt);

    /* Close both databases */
    sqlite3_close(destDB);
    sqlite3_close(sourceDB);

    return 0;
}

На моем настольном компьютере с Windows этот код копирует 100 тыс. Записей с source.sqlite до dest.sqlite за 1,20 секунды. Я не знаю точно, какую производительность вы увидите на мобильном устройстве с флэш-память (но мне любопытно).

4 голосов
/ 03 февраля 2010

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

http://sqlite.org/cvstrac/wiki?p=SpeedComparison

Как вы можете видеть, SQLite 3 выполняет INSERT намного быстрее при использовании индексов и / или транзакций. Кроме того, INSERTs FROM SELECTs не является сильной стороной SQLite.

1 голос
/ 03 февраля 2010

Все ли 100 000 записей меняются очень часто? Или это подмножество, которое изменяется?

Если это так, вам следует рассмотреть возможность добавления столбца updated_since_last_sync, который будет помечен при обновлении, поэтому во время следующей синхронизации вы копируете только те записи, которые действительно изменились. После того, как записи скопированы, вы возвращаете столбец флага в ноль.

1 голос
/ 26 января 2010

INSERT INTO SELECT * из подключенных баз данных - самый быстрый доступный вариант в SQLite. Несколько вещей, на которые стоит обратить внимание.

  1. Сделки. Убедитесь, что все это внутри транзакции. Это действительно важно. Если это только один оператор SQL, то это не важно, но вы сказали, что журнал увеличивается «шаг за шагом», что указывает на то, что это более одного оператора.

  2. триггера. У вас есть триггеры? Это, очевидно, может повлиять на производительность.

  3. Ограничения. Есть ли у вас ненужные ограничения? Вы не можете отключить их или удалить / повторно добавить их, поэтому, если они необходимы, вы ничего не можете с ними поделать, но это то, что нужно учитывать.

Вы уже упоминали об отключении индексов.

0 голосов
/ 03 февраля 2010

Если вы еще этого не сделали, вам нужно заключить его в транзакцию. Создает значительную разницу в скорости.

0 голосов
/ 26 января 2010

Как насчет хранения таблицы базы данных sync.table в отдельном файле? Таким образом, вам просто нужно сделать копию этого файла для синхронизации. Могу поспорить, что это намного быстрее, чем синхронизация по SQL.

0 голосов
/ 24 января 2010

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

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