Optymalizacja SQLite jest trudna. Wydajność wkładania luzem w aplikacji C może wynosić od 85 płytek na sekundę do ponad 96 000 płytek na sekundę!
Tło: Używamy SQLite jako części aplikacji komputerowej. Mamy duże ilości danych konfiguracyjnych przechowywanych w plikach XML, które są analizowane i ładowane do bazy danych SQLite w celu dalszego przetwarzania podczas inicjalizacji aplikacji. SQLite jest idealny w tej sytuacji, ponieważ jest szybki, nie wymaga specjalnej konfiguracji, a baza danych jest przechowywana na dysku jako pojedynczy plik.
Uzasadnienie: Początkowo byłem rozczarowany występem, który oglądałem. Okazuje się, że wydajność SQLite może się znacznie różnić (zarówno w przypadku wstawiania zbiorczego, jak i selekcji) w zależności od konfiguracji bazy danych i sposobu korzystania z interfejsu API. Nie było trywialne ustalenie, jakie były wszystkie opcje i techniki, więc pomyślałem, że rozsądnie jest utworzyć ten wpis wiki społeczności, aby udostępnić wyniki czytelnikom stosu przepełnienia, aby zaoszczędzić innym problemów związanych z tymi samymi dochodzeniami.
Eksperyment: Zamiast po prostu mówić o ogólnych wskazówkach dotyczących wydajności (tj. „Użyj transakcji!” ), Pomyślałem, że najlepiej napisać trochę kodu C i zmierzyć wpływ różnych opcji. Zaczniemy od kilku prostych danych:
- Plik tekstowy rozdzielony 28 MB TAB (około 865 000 rekordów) z kompletnym harmonogramem tranzytu dla miasta Toronto
- Moja maszyna testowa to 3,60 GHz P4 z systemem Windows XP.
- Kod jest kompilowany z Visual C ++ 2005 jako „Release” z „Full Optimization” (/ Ox) i Favor Fast Code (/ Ot).
- Używam SQLite „Amalgamation”, skompilowanego bezpośrednio w mojej aplikacji testowej. Wersja SQLite, którą posiadam, jest nieco starsza (3.6.7), ale podejrzewam, że te wyniki będą porównywalne z najnowszą wersją (proszę zostawić komentarz, jeśli uważasz inaczej).
Napiszmy kod!
Kod: Prosty program C, który odczytuje plik tekstowy wiersz po wierszu, dzieli ciąg na wartości, a następnie wstawia dane do bazy danych SQLite. W tej „podstawowej” wersji kodu baza danych jest tworzona, ale tak naprawdę nie wstawiamy danych:
/*************************************************************
Baseline code to experiment with SQLite performance.
Input data is a 28 MB TAB-delimited text file of the
complete Toronto Transit System schedule/route info
from http://www.toronto.ca/open/datasets/ttc-routes/
**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "sqlite3.h"
#define INPUTDATA "C:\\TTC_schedule_scheduleitem_10-27-2009.txt"
#define DATABASE "c:\\TTC_schedule_scheduleitem_10-27-2009.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 * db;
sqlite3_stmt * stmt;
char * sErrMsg = 0;
char * tail = 0;
int nRetCode;
int n = 0;
clock_t cStartClock;
FILE * pFile;
char sInputBuf [BUFFER_SIZE] = "\0";
char * sRT = 0; /* Route */
char * sBR = 0; /* Branch */
char * sVR = 0; /* Version */
char * sST = 0; /* Stop Number */
char * sVI = 0; /* Vehicle */
char * sDT = 0; /* Date */
char * sTM = 0; /* Time */
char sSQL [BUFFER_SIZE] = "\0";
/*********************************************/
/* Open the Database and create the Schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
/*********************************************/
/* Open input file and import into Database*/
cStartClock = clock();
pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {
fgets (sInputBuf, BUFFER_SIZE, pFile);
sRT = strtok (sInputBuf, "\t"); /* Get Route */
sBR = strtok (NULL, "\t"); /* Get Branch */
sVR = strtok (NULL, "\t"); /* Get Version */
sST = strtok (NULL, "\t"); /* Get Stop Number */
sVI = strtok (NULL, "\t"); /* Get Vehicle */
sDT = strtok (NULL, "\t"); /* Get Date */
sTM = strtok (NULL, "\t"); /* Get Time */
/* ACTUAL INSERT WILL GO HERE */
n++;
}
fclose (pFile);
printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);
sqlite3_close(db);
return 0;
}
Kontrola"
Uruchamianie kodu w obecnej postaci nie wykonuje żadnych operacji na bazie danych, ale da nam wyobrażenie o tym, jak szybkie są operacje we / wy nieprzetworzonego pliku C i przetwarzania łańcucha.
Zaimportowano 864913 rekordów w 0,94 sekundy
Świetny! Możemy wykonać 920 000 wstawek na sekundę, pod warunkiem, że tak naprawdę nie zrobimy żadnych wstawek :-)
„Scenariusz najgorszego przypadku”
Wygenerujemy ciąg SQL za pomocą wartości odczytanych z pliku i wywołamy tę operację SQL za pomocą sqlite3_exec:
sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", sRT, sBR, sVR, sST, sVI, sDT, sTM);
sqlite3_exec(db, sSQL, NULL, NULL, &sErrMsg);
To będzie powolne, ponieważ SQL zostanie skompilowany w kodzie VDBE dla każdej wstawki, a każda wstawka nastąpi w ramach własnej transakcji. Jak wolno
Zaimportowano 864913 rekordów w 9933.61 sekund
Yikes! 2 godziny i 45 minut! To tylko 85 wstawek na sekundę.
Korzystanie z transakcji
Domyślnie SQLite ocenia każdą instrukcję INSERT / UPDATE w ramach unikalnej transakcji. W przypadku wykonywania dużej liczby wstawek wskazane jest zawinięcie operacji w transakcję:
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);
pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {
...
}
fclose (pFile);
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
Zaimportowano 864913 rekordów w 38,03 sekundy
Tak lepiej Po prostu zawinięcie wszystkich naszych wkładek w jedną transakcję poprawiło naszą wydajność do 23 000 wkładek na sekundę.
Korzystanie z przygotowanego wyciągu
Wykorzystanie transakcji było ogromnym ulepszeniem, ale rekompilacja instrukcji SQL dla każdej wstawki nie ma sensu, jeśli używamy tego samego SQL w kółko. Użyjmy raz, sqlite3_prepare_v2
aby skompilować naszą instrukcję SQL, a następnie powiązać nasze parametry z tą instrukcją, używając sqlite3_bind_text
:
/* Open input file and import into the database */
cStartClock = clock();
sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, @RT, @BR, @VR, @ST, @VI, @DT, @TM)");
sqlite3_prepare_v2(db, sSQL, BUFFER_SIZE, &stmt, &tail);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);
pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {
fgets (sInputBuf, BUFFER_SIZE, pFile);
sRT = strtok (sInputBuf, "\t"); /* Get Route */
sBR = strtok (NULL, "\t"); /* Get Branch */
sVR = strtok (NULL, "\t"); /* Get Version */
sST = strtok (NULL, "\t"); /* Get Stop Number */
sVI = strtok (NULL, "\t"); /* Get Vehicle */
sDT = strtok (NULL, "\t"); /* Get Date */
sTM = strtok (NULL, "\t"); /* Get Time */
sqlite3_bind_text(stmt, 1, sRT, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, sBR, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, sVR, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 4, sST, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 5, sVI, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 6, sDT, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 7, sTM, -1, SQLITE_TRANSIENT);
sqlite3_step(stmt);
sqlite3_clear_bindings(stmt);
sqlite3_reset(stmt);
n++;
}
fclose (pFile);
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);
sqlite3_finalize(stmt);
sqlite3_close(db);
return 0;
Zaimportowano 864913 rekordów w 16,27 sekund
Miły! Jest trochę więcej kodu (nie zapomnij zadzwonić sqlite3_clear_bindings
i sqlite3_reset
), ale podwoiliśmy naszą wydajność do 53 000 wstawek na sekundę.
PRAGMA synchroniczny = WYŁ
Domyślnie SQLite zatrzyma się po wydaniu polecenia zapisu na poziomie systemu operacyjnego. To gwarantuje, że dane są zapisywane na dysku. Ustawiając synchronous = OFF
, instruujemy SQLite, aby po prostu przekazał dane do systemu operacyjnego w celu zapisu, a następnie kontynuował. Istnieje prawdopodobieństwo, że plik bazy danych zostanie uszkodzony, jeśli komputer ulegnie katastrofalnej awarii (lub awarii zasilania) przed zapisaniem danych na talerzu:
/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);
Zaimportowano 864913 rekordów w 12,41 sekund
Ulepszenia są teraz mniejsze, ale mamy do 69 600 wkładek na sekundę.
PRAGMA journal_mode = MEMORY
Rozważ zapisanie dziennika wycofania w pamięci, oceniając PRAGMA journal_mode = MEMORY
. Twoja transakcja będzie szybsza, ale jeśli stracisz moc lub program ulegnie awarii podczas transakcji, baza danych może pozostać w stanie uszkodzonym z częściowo zakończoną transakcją:
/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);
Zaimportowano 864913 rekordów w 13,50 sekundy
Nieco wolniej niż poprzednia optymalizacja przy 64 000 płytek na sekundę.
PRAGMA synchroniczny = WYŁ. I PRAGMA tryb_ dziennika = PAMIĘĆ
Połączmy dwie poprzednie optymalizacje. Jest to trochę bardziej ryzykowne (w przypadku awarii), ale po prostu importujemy dane (nie prowadzimy banku):
/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);
Zaimportowano 864913 rekordów w 12,00 sekund
Fantastyczny! Jesteśmy w stanie wykonać 72 000 wstawek na sekundę.
Korzystanie z bazy danych w pamięci
Na wszelki wypadek wykorzystajmy wszystkie poprzednie optymalizacje i ponownie zdefiniuj nazwę pliku bazy danych, abyśmy pracowali całkowicie w pamięci RAM:
#define DATABASE ":memory:"
Zaimportowano 864913 rekordów w 10,94 sekund
Przechowywanie naszej bazy danych w pamięci RAM nie jest zbyt praktyczne, ale imponujące jest to, że możemy wykonać 79 000 wstawek na sekundę.
Refaktoryzacja kodu C.
Chociaż nie jest to specjalnie poprawa SQLite, nie lubię dodatkowych char*
operacji przypisywania w while
pętli. Szybko przekształćmy ten kod, aby przekazać dane wyjściowe strtok()
bezpośrednio do sqlite3_bind_text()
, i pozwólmy kompilatorowi spróbować przyspieszyć dla nas:
pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {
fgets (sInputBuf, BUFFER_SIZE, pFile);
sqlite3_bind_text(stmt, 1, strtok (sInputBuf, "\t"), -1, SQLITE_TRANSIENT); /* Get Route */
sqlite3_bind_text(stmt, 2, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Branch */
sqlite3_bind_text(stmt, 3, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Version */
sqlite3_bind_text(stmt, 4, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Stop Number */
sqlite3_bind_text(stmt, 5, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Vehicle */
sqlite3_bind_text(stmt, 6, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Date */
sqlite3_bind_text(stmt, 7, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Time */
sqlite3_step(stmt); /* Execute the SQL Statement */
sqlite3_clear_bindings(stmt); /* Clear bindings */
sqlite3_reset(stmt); /* Reset VDBE */
n++;
}
fclose (pFile);
Uwaga: wróciliśmy do używania prawdziwego pliku bazy danych. Bazy danych w pamięci są szybkie, ale niekoniecznie praktyczne
Zaimportowano 864913 rekordów w 8,94 sekundy
Nieznaczne przefakturowanie kodu przetwarzania łańcucha używanego w naszym wiązaniu parametrów pozwoliło nam na wykonanie 96 700 wstawień na sekundę. Myślę, że można śmiało powiedzieć, że to dość szybko . Gdy zaczniemy dostosowywać inne zmienne (tj. Rozmiar strony, tworzenie indeksu itp.), Będzie to nasz punkt odniesienia.
Podsumowanie (do tej pory)
Mam nadzieję, że wciąż jesteś ze mną! Powodem, dla którego zaczęliśmy tę drogę, jest to, że wydajność wstawiania zbiorczego zmienia się tak bardzo w zależności od SQLite, i nie zawsze jest oczywiste, jakie zmiany należy wprowadzić, aby przyspieszyć naszą działalność. Korzystając z tego samego kompilatora (i opcji kompilatora), tej samej wersji SQLite i tych samych danych zoptymalizowaliśmy nasz kod i nasze użycie SQLite, aby przejść od najgorszego scenariusza z 85 wstawkami na sekundę do ponad 96 000 wstawień na sekundę!
UTWÓRZ INDEKS, następnie WSTAW vs. WSTAW, a następnie UTWÓRZ INDEKS
Zanim zaczniemy mierzyć SELECT
wydajność, wiemy, że będziemy tworzyć wskaźniki. Zasugerowano w jednej z poniższych odpowiedzi, że podczas wykonywania wstawiania zbiorczego szybciej jest tworzyć indeks po wstawieniu danych (w przeciwieństwie do tworzenia indeksu najpierw, a następnie wstawiania danych). Spróbujmy:
Utwórz indeks, a następnie wstaw dane
sqlite3_exec(db, "CREATE INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);
...
Zaimportowano 864913 rekordów w 18,13 sekund
Wstaw dane, a następnie Utwórz indeks
...
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "CREATE INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);
Zaimportowano 864913 rekordów w 13,66 sekund
Zgodnie z oczekiwaniami wstawianie zbiorcze jest wolniejsze, jeśli jedna kolumna jest indeksowana, ale robi to różnicę, jeśli indeks jest tworzony po wstawieniu danych. Nasza linia bazowa bez indeksu wynosi 96 000 wstawek na sekundę. Utworzenie indeksu najpierw, a następnie wstawienie danych daje nam 47 700 wstawek na sekundę, podczas gdy wstawienie danych, a następnie utworzenie indeksu daje nam 63 300 wstawek na sekundę.
Z przyjemnością skorzystam z sugestii dotyczących innych scenariuszy do wypróbowania ... I wkrótce skompiluję podobne dane dla zapytań SELECT.
źródło
sqlite3_clear_bindings(stmt);
? Powiązania ustawia się za każdym razem, gdy powinno wystarczyć: Przed pierwszym wywołaniem funkcji sqlite3_step () lub bezpośrednio po funkcji sqlite3_reset () aplikacja może wywołać jeden z interfejsów sqlite3_bind () w celu dołączenia wartości do parametrów. Każde wywołanie funkcji sqlite3_bind () zastępuje wcześniejsze wiązania tego samego parametru (patrz: sqlite.org/cintro.html ). Nie ma nic w docs dla tej funkcji mówiąc trzeba to nazwać.feof()
do kontrolowania zakończenia pętli wejściowej. Użyj wyniku zwróconego przezfgets()
. stackoverflow.com/a/15485689/827263Odpowiedzi:
Kilka wskazówek:
pragma journal_mode
). JestNORMAL
, a potem jestOFF
, co może znacznie zwiększyć szybkość wstawiania, jeśli nie martwisz się zbytnio możliwością uszkodzenia bazy danych w przypadku awarii systemu operacyjnego. Jeśli aplikacja ulegnie awarii, dane powinny być w porządku. Pamiętaj, że w nowszych wersjachOFF/MEMORY
ustawienia nie są bezpieczne w przypadku awarii na poziomie aplikacji.PRAGMA page_size
). Większe rozmiary stron mogą sprawić, że odczyty i zapisy będą przebiegać nieco szybciej, ponieważ większe strony są przechowywane w pamięci. Zauważ, że twoja baza danych wykorzysta więcej pamięci.CREATE INDEX
po wykonaniu wszystkich wstawek. Jest to znacznie szybsze niż tworzenie indeksu, a następnie wstawianie wstawek.INTEGER PRIMARY KEY
jeśli to możliwe, co zastąpi domyślną kolumnę z unikalnym numerem wiersza w tabeli.!feof(file)
!Zadałem również podobne pytania tutaj i tutaj .
źródło
Spróbuj użyć
SQLITE_STATIC
zamiastSQLITE_TRANSIENT
tych wkładek.SQLITE_TRANSIENT
spowoduje, że SQLite skopiuje dane ciągu przed zwróceniem.SQLITE_STATIC
informuje, że podany adres pamięci będzie ważny do momentu wykonania zapytania (co zawsze ma miejsce w tej pętli). Pozwoli to zaoszczędzić kilka operacji alokacji, kopiowania i cofania przydziału na pętlę. Prawdopodobnie duża poprawa.źródło
Unikać
sqlite3_clear_bindings(stmt)
.Kod w teście ustawia wiązania za każdym razem, przez które powinno wystarczyć.
C API Intro z SQLite docs mówi:
Dokumenty nie zawierają niczego
sqlite3_clear_bindings
, co mówi, że musisz to nazwać oprócz zwykłego ustawiania powiązań.Więcej szczegółów: Avoid_sqlite3_clear_bindings ()
źródło
Na wkładkach luzem
Zainspirowany tym postem i pytaniem o przepełnienie stosu, które mnie tu zaprowadziło - czy można wstawiać wiele wierszy jednocześnie w bazie danych SQLite? - Opublikowałem swoje pierwsze repozytorium Git :
https://github.com/rdpoor/CreateOrUpdatektóry masowo ładuje tablicę ActiveRecords do baz danych MySQL , SQLite lub PostgreSQL . Zawiera opcję zignorowania istniejących rekordów, zastąpienia ich lub zgłoszenia błędu. Moje podstawowe testy porównawcze pokazują 10-krotną poprawę prędkości w porównaniu do zapisu sekwencyjnego - YMMV.
Używam go w kodzie produkcyjnym, w którym często muszę importować duże zestawy danych i jestem z tego całkiem zadowolony.
źródło
Wydaje się, że importowanie zbiorcze działa najlepiej, jeśli można podzielić fragmenty instrukcji INSERT / UPDATE . Około 10 000 działało dla mnie dobrze na stole z tylko kilkoma rzędami, YMMV ...
źródło
Jeśli zależy ci tylko na czytaniu, nieco szybszą (ale może czytać nieaktualne dane) wersją jest czytanie z wielu połączeń z wielu wątków (połączenie na wątek).
Najpierw znajdź przedmioty w tabeli:
następnie czytaj na stronach (LIMIT / OFFSET):
gdzie i są obliczane dla wątku, tak jak to:
dla każdego wątku:
W przypadku naszego małego (200 MB) db spowodowało to 50-75% przyspieszenie (3.8.0.2 64-bit w Windows 7). Nasze tabele są mocno nienormalizowane (1000-1500 kolumn, około 100 000 lub więcej wierszy).
Zbyt wiele lub zbyt mało wątków tego nie zrobi, musisz sam przeprowadzić testy porównawcze i profilować.
Również dla nas SHAREDCACHE spowolnił działanie, więc ręcznie ustawiłem PRIVATECACHE (ponieważ dla nas został włączony globalnie)
źródło
Nie mogę uzyskać żadnego zysku z transakcji, dopóki nie podniosłem cache_size do wyższej wartości, tj
PRAGMA cache_size=10000;
źródło
cache_size
ustawia liczbę stron do buforowania , a nie całkowity rozmiar pamięci RAM. Przy domyślnym rozmiarze strony wynoszącym 4 kB to ustawienie może pomieścić do 40 MB danych na otwarty plik (lub na proces, jeśli działa ze współużytkowaną pamięcią podręczną ).Po przeczytaniu tego samouczka próbowałem zaimplementować go w moim programie.
Mam 4-5 plików zawierających adresy. Każdy plik ma około 30 milionów rekordów. Używam tej samej konfiguracji, którą sugerujesz, ale moja liczba INSERTÓW na sekundę jest zdecydowanie niska (~ 10.000 rekordów na sekundę).
Oto, gdzie twoja sugestia zawodzi. Używasz jednej transakcji dla wszystkich rekordów i jednej wstawki bez błędów / niepowodzeń. Powiedzmy, że dzielisz każdy rekord na wiele wstawek w różnych tabelach. Co się stanie, jeśli rekord zostanie pobity?
Polecenie ON CONFLICT nie ma zastosowania, ponieważ jeśli masz 10 elementów w rekordzie i potrzebujesz każdego elementu wstawionego do innej tabeli, jeśli element 5 otrzyma błąd CONSTRAINT, wówczas wszystkie poprzednie 4 wstawki również muszą przejść.
Oto, gdzie przychodzi wycofanie. Jedynym problemem związanym z wycofywaniem jest to, że tracisz wszystkie wstawki i zaczynasz od góry. Jak możesz to rozwiązać?
Moim rozwiązaniem było użycie wielu transakcji. Zaczynam i kończę transakcję co 10.000 rekordów (nie pytaj, dlaczego ta liczba była najszybsza, którą testowałem). Utworzyłem tablicę o rozmiarze 10.000 i wstawiłem tam udane rekordy. Kiedy wystąpi błąd, robię wycofywanie, rozpoczynam transakcję, wstawiam rekordy z mojej tablicy, zatwierdzam, a następnie rozpoczynam nową transakcję po uszkodzonym rekordzie.
To rozwiązanie pomogło mi ominąć problemy związane z plikami zawierającymi złe / zduplikowane rekordy (miałem prawie 4% złych rekordów).
Algorytm, który stworzyłem, pomógł mi zredukować proces o 2 godziny. Końcowy proces ładowania pliku 1 godz. 30 m, który wciąż jest powolny, ale nie jest porównywany z 4 godzinami, które początkowo trwały. Udało mi się przyspieszyć wkładki z 10.000 / s do ~ 14.000 / s
Jeśli ktoś ma jakieś pomysły na przyspieszenie, jestem otwarty na sugestie.
AKTUALIZACJA :
Oprócz mojej powyższej odpowiedzi należy pamiętać, że liczba wkładek na sekundę zależy od używanego dysku twardego. Przetestowałem to na 3 różnych komputerach z różnymi dyskami twardymi i otrzymałem ogromne różnice w czasie. PC1 (1 godz. 30 min), PC2 (6 godz.) PC3 (14 godz.), Więc zacząłem się zastanawiać, dlaczego to miałoby być.
Po dwóch tygodniach badań i sprawdzania wielu zasobów: dysku twardego, pamięci RAM, pamięci podręcznej odkryłem, że niektóre ustawienia na dysku twardym mogą wpływać na szybkość operacji we / wy. Klikając właściwości żądanego napędu wyjściowego, możesz zobaczyć dwie opcje na karcie ogólnej. Opt1: Kompresuj ten dysk, Opt2: Zezwalaj na pliki tego dysku na indeksowanie zawartości.
Wyłączając te dwie opcje, wszystkie 3 komputery zajmują teraz mniej więcej ten sam czas (1 godzina i 20 do 40 minut). Jeśli napotkasz wolne wstawianie, sprawdź, czy na dysku twardym są skonfigurowane te opcje. Zaoszczędzi ci to dużo czasu i bólu głowy, próbując znaleźć rozwiązanie
źródło
Odpowiedź na twoje pytanie brzmi: nowsza wersja SQLite 3 poprawiła wydajność, skorzystaj z niej.
Ta odpowiedź Dlaczego SQLAlchemy wstawia z sqlite 25 razy wolniej niż bezpośrednio z sqlite3? autor: SqlAlchemy Orm Autor ma 100k wstawek w 0,5 sekundy i widziałem podobne wyniki z python-sqlite i SqlAlchemy. Co prowadzi mnie do przekonania, że wydajność poprawiła się dzięki SQLite 3.
źródło
Użyj ContentProvider do wstawiania danych zbiorczych w db. Poniższa metoda służy do wstawiania danych zbiorczych do bazy danych. Powinno to poprawić wydajność SQLite INSERT na sekundę.
Wywołaj metodę bulkInsert:
Link: https://www.vogella.com/tutorials/AndroidSQLite/article.html sprawdź Więcej sekcji Korzystanie z ContentProvider
źródło