Что самый быстрый путь состоит в том, чтобы вставить 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);

Я задаюсь вопросом, вставка в... выбор * от не является самым быстрым встроенным способом сделать крупную копию данных?

ОТРЕДАКТИРОВАННЫЙ 3

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

17
задан Sid M 14 July 2014 в 09:04
поделиться

8 ответов

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

6
ответ дан 30 November 2019 в 13:40
поделиться

Делить два больших числа трудно. Возможно, улучшением было бы сначала немного охарактеризовать k, посмотрев на некоторые из меньших простых чисел. Скажем, 2, 3 и 5. Если k делится на любое из них, то, чем inputnum также должно быть или inputnum не делится на k. Конечно, есть больше трюков, чтобы играть (вы можете использовать побитовые и inputnum к 1, чтобы определить, разделяете ли вы на 2), но я думаю, что просто удаление низких простых возможностей даст разумное улучшение скорости (стоит все равно выстрел).

-121--2913839-

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

0
ответ дан 30 November 2019 в 13:40
поделиться

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

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

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

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

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

1
ответ дан 30 November 2019 в 13:40
поделиться

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

0
ответ дан 30 November 2019 в 13:40
поделиться

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

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

1
ответ дан 30 November 2019 в 13:40
поделиться

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

0
ответ дан 30 November 2019 в 13:40
поделиться

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

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

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

4
ответ дан 30 November 2019 в 13:40
поделиться

Если целью является какая-то версия 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, если вы включите ее в свой пакет.

9
ответ дан 30 November 2019 в 13:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: