2017-02-03 37 views
5

Moje pytanie pochodzi bezpośrednio z this, ale interesuje mnie tylko UPDATE i tylko to.Poprawić wydajność SQLite za aktualizację na sekundę?

Mam aplikacja napisana w C/C++ co sprawia ciężki wykorzystanie SQLite, głównie SELECT/UPDATE, na bardzo częstych odstępach (około 20 zapytań co 0,5 do 1 sekundy)

Moja baza danych nie jest duży, około zapisy w momentach, tutaj jest struktura tabeli:

CREATE TABLE player (
    id INTEGER PRIMARY KEY AUTOINCREMENT, 
    name VARCHAR(64) UNIQUE, 
    stats VARBINARY, 
    rules VARBINARY 
); 

do tej pory nie stosowane transactions bo poprawa kodu i chciał ukłucie ility raczej wydajność.

Potem mierzono moją wydajność bazy danych jedynie poprzez wykonanie 10 update zapytań, co następuje (w pętli różnych wartości):

// 10 times execution of this 
UPDATE player SET stats = ? WHERE (name = ?) 

gdzie stats jest JSON dokładnie 150 znaków i name wynosi od 5-10 postacie.

bez operacji wynik jest nie do przyjęcia - o jeden pełny sekund (0,096 każdy)

z transakcji, czas spada razy x7.5: - 0,11 - 0,16 sekundy (0,013 każdy)

Próbowałem usunąć dużą część bazy danych i/lub zmienić kolejność/usunięcie kolumn, aby zobaczyć, czy to coś zmieni, ale nie. Otrzymuję powyższe liczby, nawet jeśli baza danych zawiera tylko 100 rekordów (testowanych).

Potem próbował gry z PRAGMA opcji:

PRAGMA synchronous = NORMAL 
PRAGMA journal_mode = MEMORY 

dał mi mniejsze razy, ale nie zawsze, bardziej jak około 0,08 - 0,14 sekundy

PRAGMA synchronous = OFF 
PRAGMA journal_mode = MEMORY 

Wreszcie dał mi bardzo małe czasy około 0.002 - 0.003 sekund, ale nie chcę go używać, ponieważ moja aplikacja zapisuje bazę danych co sekundę i istnieje duża szansa na uszkodzenie bazy danych o n OS/brak zasilania.

Mój kod C SQLite zapytań jest: (Komentarze/obsługa błędów/niepowiązanych części pominięto)

// start transaction 
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL); 

// query 
sqlite3_stmt *statement = NULL; 
int out = sqlite3_prepare_v2(query.c_str(), -1, &statement, NULL); 
// bindings 
for(size_t x = 0, sz = bindings.size(); x < sz; x++) { 
    out = sqlite3_bind_text(statement, x+1, bindings[x].text_value.c_str(), bindings[x].text_value.size(), SQLITE_TRANSIENT); 
    ... 
} 

// execute 
out = sqlite3_step(statement); 

if (out != SQLITE_OK) { 
    // should finalize the query no mind the error 
    if (statement != NULL) { 
     sqlite3_finalize(statement); 
    } 
} 

// end the transaction 
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL); 

Jak widać, jest to dość typowe TABLE, rejestruje ilość jest niewielka i robię zwykły prosty UPDATE dokładnie 10 razy. Czy jest coś jeszcze co mogłem zrobić, aby zmniejszyć mój czas? Używam najnowszego SQLite 3.16.2.

UWAGA: synchronizacją powyżej pochodzą bezpośrednio z jednego END TRANSACTION zapytania.Kwerendy są wykonywane w prostej transakcji i jestem za pomocą przygotowanego oświadczenia.

UPDATE:

Przeprowadziłem kilka testów z włączonym transakcji oraz osób niepełnosprawnych i różnych aktualizacji liczyć. Przeprowadziłem testy z następującymi ustawieniami:

VACUUM; 
PRAGMA synchronous = NORMAL; -- def: FULL 
PRAGMA journal_mode = WAL; -- def: DELETE 
PRAGMA page_size = 4096;  -- def: 1024 

Wyniki następująco:

żadnych transakcji (10 Updates)

  • 0,30800 sek (0,0308 za update)
  • 0,30200 sekundy
  • 0.36200 secs
  • 0.28600 s ECS

żadne transakcje (100 Updates)

  • 2.64400 sek (0.02644 każda aktualizacja)
  • 2.61200 sek
  • 2.76400 sek
  • 2.68700 sek

bez transacti Świat (1000 aktualizacje)

  • 28.02800 sek (0,028 każdy update)
  • 27.73700 sek
  • ..

z transakcji (10 aktualizacje)

  • 0,12800 secs (0.0128 każda aktualizacja)
  • 0,08100 sekund
  • 0.16400 sek
  • 0,10400 sekund

z transakcji (100 aktualizacje)

  • 0,088 sek (0,00088 każda aktualizacja)
  • 0,091 sek
  • 0.052 sek
  • 0.101 sek

z transakcjami (1000 Updates)

  • 0.08900 sek (0.000089 każda aktualizacja)
  • 0.15000 sek
  • 0.11000 sek
  • 0.09100 sek

Moje wnioski są następujące: transactions nie ma sensu w time cost per query. Być może czasy stają się większe dzięki ogromnej liczbie aktualizacji, ale nie interesują mnie te liczby. Dosłownie nie ma różnicy w kosztach czasowych między aktualizacjami 10 a 1000 w pojedynczej transakcji. Jednak zastanawiam się, czy jest to ograniczenie sprzętowe na moim komputerze i nie mogę wiele zrobić. Wygląda na to, że nie mogę zejść poniżej poziomu ~100 milisekund przy użyciu pojedynczej transakcji i aktualizacji 10-1000, nawet przy użyciu WAL.

Bez transakcji nie ma ustalonego kosztu czasu około 0.025 sekund.

+0

Jeśli używasz languiage C/C++, używać odpowiedniego znacznika. W przeciwnym razie wygląda na C++, a nie na ** inny ** język C! I to nie jest strona z recenzowaniem kodu. – Olaf

+0

@Olaf, jedyne rzeczy 'C++' to 'std :: string'; reszta to 'C'. Szczególnie podkreślam to powyżej. Po drugie nie chcę, aby ktoś przejrzał mój kod, chcę lepszego podejścia do SQLite, aby rozwiązać mój problem – user6096479

+4

Nie ** nie ** kompiluje się jako C, więc nie jest C. Tylko dlatego, że masz taką samą składnię/gramatykę nie oznacza, że ​​masz tę samą semantykę! Ten, kto mówi ci, że C++ to "C z klasami", jest zupełnie błędny i nie zna przynajmniej jednego z nich na tyle dobrze, by napisać nietrywialny kod. – Olaf

Odpowiedz

3

Przy takich małych ilości danych, czas działania samej bazy danych jest nieznaczna; to, co mierzysz, to narzut transakcji (czas potrzebny na wymuszenie zapisu na dysku), który zależy od systemu operacyjnego, systemu plików i sprzętu.

Jeśli możesz żyć z ograniczeniami (głównie bez sieci), możesz użyć zapisu asynchronicznego, włączając WAL mode.

+0

Próbowałem 'WAL' ustawiając' PRAGMA synchronous = NORMAL' i 'PRAGMA journal_mode = WAL', ale nie otrzymałem żadnej poprawy, mam na myśli w ogóle. Nie potrzebuję w ogóle sieci i chciałbym skorzystać z WAL, po prostu nie dostaję żadnego zysku (być może potrzebne są dodatkowe opcje?!?). Z drugiej strony, wystarczy "zaktualizować" maksymalnie 20 zapytań na maksymalnie 1 sekundę. Ich (zapytania) może być mniej, ale nie może być więcej. Wydaje mi się, że używając 'synchronous = NORMAL lub FULL' nie mogę przełamać bariery' 10 ms/update query', chyba że zdecyduję się dać obsługę OS, zmniejszając bezpieczeństwo. – user6096479

+0

Aby sprawdzić, czy WAL jest włączone, uruchom 'PRAGMA tryb_dokumentu;'. –

+0

Nie wiem dlaczego, ale mój "PRAGMA journal_mode = WAL" nie był w ogóle sprawdzany na moim C, więc tryb pozostał "DELETE". Kiedy użyłem kwerendy na 'PHPLiteAdmin' wykonane normalnie. Jednak moje czasy lekko spadły do ​​około 0,07 - 0,11, co wydaje się być w porządku. – user6096479

3

Możesz nadal być ograniczony przez czas potrzebny na zatwierdzenie transakcji. W pierwszym przykładzie każda transakcja zajęła około 0,10, co jest dość zbliżone do czasu transakcji dla wstawienia 10 rekordów. Jakie wyniki uzyskasz, jeśli wsadujesz 100 lub 1000 aktualizacji w pojedynczej transakcji?

Ponadto, SQLite spodziewa się około 60 transakcji na sekundę na przeciętnym dysku twardym, a otrzymujesz tylko około 10. Czy problem z wydajnością dysku może być tutaj problemem?

https://sqlite.org/faq.html#q19

+0

Czy jestem ograniczony z powodu szybkości dysku twardego i trybu synchronicznego, czekającego na SQLite w celu zweryfikowania zapisanych danych? Więc jeśli wybiorę bezpieczeństwo w stosunku do wydajności, jestem ograniczony do '~ 10 ms' na zapytanie aktualizacyjne z typowym dyskiem 7200? Nie testowałem przy 100 lub 1000 aktualizacjach, ponieważ moja aplikacja obsługuje maksymalnie 15-20 zapytań na transakcję (z kilkoma SELECTami pomiędzy), więc emulowałem scenariusz na pytanie. Odrzuciłem dyskietkę tydzień temu, zrobię to ponownie i odpowiem :) – user6096479

+0

Nie, czasy są wciąż od "0.10 do 0.14" s przez transakcję o 10 zapytaniach. – user6096479

+0

Proszę zobaczyć moje pytanie aktualizacji – user6096479

0

spróbuj dodać indeksy do bazy danych:

CREATE INDEX IDXname ON player (name)