Как быстрее всего вставить 100 000 записей из одной базы данных в другую?

У меня есть мобильное приложение. У моего клиента большой набор данных ~ 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 не требует использования сервера, поэтому, пожалуйста, прекратите голосование за конкретный ответ, потому что я уверен, что это не тот ответ.


person Pentium10    schedule 23.01.2010    source источник
comment
Похоже на продолжение уже имеющегося у вас вопроса ?? stackoverflow.com/questions/2121336 /   -  person AdaTheDev    schedule 24.01.2010
comment
это так, я не знаю, как я могу отвечать в своей беседе, я могу только комментировать сообщения или отвечать на них .... но я хочу задать другой вопрос ...   -  person Pentium10    schedule 24.01.2010
comment
Как выглядит каждая строка? 100000 записей - это не много, строки маленькие. Мы регулярно выполняем импорт данных с 300 000 записей, и он выполняется за несколько секунд (что мы считаем медленным).   -  person Samuel Neff    schedule 26.01.2010
comment
см. мое второе изменение, я добавил схему таблицы. Вы запускаете эту большую вставку на Compact Framework на портативном устройстве?   -  person Pentium10    schedule 26.01.2010
comment
Когда я запускаю инструмент командной строки sqlite3, создаю две базы данных со схемой инвентаризации, вставляю 100 000 записей с данными в каждом поле в одну, а затем копирую из одной базы данных в другую, каждая операция возвращается менее чем за секунду. Я могу копировать одни и те же 100 000 записей снова и снова. Каждый раз, когда размер журнала увеличивается до менее 10 КБ, запрос выполняется менее чем за секунду. Я делаю это на ПК, а не на устройстве, но все равно время от секунды до 10 кажется чрезмерным.   -  person Samuel Neff    schedule 26.01.2010
comment
Вот и комп, там все нормально. На мобильном устройстве с процессором 350 МГц и ограниченной памятью, например: 60 МБ оперативной памяти, 10 МБ свободного места и запись на диск 12 МБ, занимающая 20 секунд, процесс ужасен. Я не знаю, как, если на Compact Framework есть режим командной строки sqlite.   -  person Pentium10    schedule 26.01.2010
comment
Что касается EDITED2: вы должны иметь возможность ускорить запрос, создав индекс для (itemtype, invivem): CREATE INDEX idx_inventory_typeandinv ON inventory (itemtype, invivem);   -  person Christian Schwarz    schedule 26.01.2010


Ответы (8)


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

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

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

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

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


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

Я не знаю всех этапов создания исполняемого файла 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 секунды. Я не знаю точно, какую производительность вы увидите на мобильном устройстве с флэш-памятью (но я любопытный).

person Mike Willekes    schedule 05.02.2010
comment
Две базы данных уже находятся в одной файловой системе. Файловая система является более медленной флэш-памятью и может быть узким местом. Я не знаю, как скомпилировать / запустить необработанный код SQLite C на моем устройстве. Если вы укажете мне правильное направление, я попробую. - person Pentium10; 05.02.2010
comment
Если вы можете использовать Visual Studio для создания исполняемого файла C ++ для Windows Mobile, это должно быть просто. Объединение SQLite3 содержит весь код SQLite в одном .c файле и одном .h файле. - person Mike Willekes; 06.02.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, если включите ее в свой пакет.

person jspcal    schedule 23.01.2010
comment
Как указано в тегах, это sqlite. Я уже использую массовую копию SQLite, подключив две базы данных и используя метод INSERT INTO .. ​​SELECT * FROM для копирования данных. Мои индексы, триггеры и несколько прагм уже отключены. - person Pentium10; 24.01.2010
comment
для sqlite попробуйте команды .dump/.import, а не выберите их (в вашем теге не указано, что оба dbs были sqlite) - person jspcal; 24.01.2010
comment
Я не уверен, смогу ли я запускать .dump/.import команды на мобильном телефоне. Это меньше сервера. (что касается не упоминания, если оба dbs являются sqlite, я пропустил это. Но вы можете вычесть это, зная, что вы не можете ПРИКЛЮЧАТЬ другой тип db в SQL) - person Pentium10; 24.01.2010
comment
@ Pentium10: вы можете, связанные серверы с odbc - person jspcal; 24.01.2010
comment
да, это довольно полезно, если вам это нужно - person jspcal; 24.01.2010
comment
У вас активирована функция транзакции-фиксации? - person Tristan; 24.01.2010
comment
@jspcal, вы можете посоветовать мне, какую утилиту cmd line мне нужно включить? и как им пользоваться? - person Pentium10; 25.01.2010
comment
@Tristan Да, у меня есть ....., но попробовал выключить, но все равно медленно. вероятно, это медленная запись на диск, файл db на мобильном телефоне составляет 12 мегабайт, это какое-то время, чтобы его записать. - person Pentium10; 25.01.2010
comment
@ Pentium10: shell.c имеет командную строку exe из архива. компилируется нормально на CF. несколько советов по компиляции здесь: sqlite.org/howtocompile.html - person jspcal; 25.01.2010
comment
compiles fine on CF что ты имеешь в виду? Мне нужно компилировать на Windows Mobile? - person Pentium10; 25.01.2010
comment
нет, вам просто нужно ссылаться на правильный фреймворк, как и любой другой исполняемый файл - person jspcal; 25.01.2010

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

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

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

person Alix Axel    schedule 03.02.2010
comment
Итак, вы говорите, что делать выбор, а затем прогонять его для создания подготовленных операторов вставки и выполнения оператора вставки, суммировавшего все это быстрее, чем INSERTs FROM SELECTs? - person Pentium10; 12.02.2010

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

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

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

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

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

person Samuel Neff    schedule 26.01.2010
comment
также упомянул, что у меня отключен режим журнала, это означает, что транзакции отключены, требуется много времени для записи файла транзакции на диск, это не помогло ... выключено. У меня нет ни ограничений, ни триггеров для вставки. - person Pentium10; 26.01.2010

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

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

person Tom van Enckevort    schedule 03.02.2010
comment
У меня уже есть это встроенное. 100 000 записей совершенно новые. - person Pentium10; 12.02.2010

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

person Viktor Klang    schedule 23.01.2010
comment
в каждой строке будет хотя бы 1 измененный столбец, я не знаю, какой из них. Было бы сложно обнаружить это и выполнить обновление этих строк. Подумайте о моем вопросе как об операции обновления. - person Pentium10; 24.01.2010

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

person Christian Schwarz    schedule 26.01.2010
comment
Я не понимаю твою идею. Два файла уже разделены: maindb.sqlite и sync.sqlite. В SQLite нет отдельных файлов для каждой таблицы. Файл - это база данных. - person Pentium10; 26.01.2010
comment
Я полагаю, что в maindb.sqlite есть несколько таблиц, и вы просто хотите синхронизировать одну из этих таблиц в одном направлении (= резервное копирование?). В этом случае вы можете ввести новый файл базы данных (например, maindb-syncable.sqlite), который содержит только ту таблицу, которую вам нужно синхронизировать. Затем просто создайте копию этого файла всякий раз, когда вам нужно синхронизировать. - person Christian Schwarz; 26.01.2010
comment
Это полный процесс синхронизации. Есть основная центральная база данных, а также у продукта есть версия Desktop. Это процесс синхронизации, когда данные из центральной базы данных синхронизируются с мобильным устройством. Проблема возникла после того, как на борт пришел клиент с большим запасом. Так что не только инвентарный стол большой, но рано или поздно счета-фактуры, расходы, таблицы запчастей тоже могут быть большими. Я хочу вставить или заменить все записи из синхронизации (новый результат с сервера) - ›на основную (локальный мобильный). База данных sync.sqlite загружается в процессе синхронизации. - person Pentium10; 27.01.2010

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

person Jay    schedule 03.02.2010
comment
вставка в table1 ... select * from table2 - это один запрос, поэтому он находится в транзакции - person Pentium10; 03.02.2010