2009-11-09 11 views
26

Słyszałem, że przygotowane oświadczenia z SQLite powinny poprawić wydajność. Napisałem kod do przetestowania i nie widziałem żadnej różnicy w wydajności z ich użyciem. Więc pomyślałem, że może mój kod był niepoprawny. Proszę dać mi znać, jeśli pojawią się jakieś błędy, w jaki to robię ...Czy w SQLite przygotowywane instrukcje naprawdę poprawiają wydajność?

[self testPrep:NO dbConn:dbConn]; 
[self testPrep:YES dbConn:dbConn]; 

reuse=0 
recs=2000 
2009-11-09 10:39:18 -0800 
processing... 
2009-11-09 10:39:32 -0800 

reuse=1 
recs=2000 
2009-11-09 10:39:32 -0800 
processing... 
2009-11-09 10:39:46 -0800 

-(void)testPrep:(BOOL)reuse dbConn:(sqlite3*)dbConn{ 
    int recs = 2000; 
    NSString *sql; 
    sqlite3_stmt *stmt; 

    sql = @"DROP TABLE test"; 
    sqlite3_exec(dbConn, [sql UTF8String],NULL,NULL,NULL); 

    sql = @"CREATE TABLE test (id INT,field1 INT, field2 INT,field3 INT,field4 INT,field5 INT,field6 INT,field7 INT,field8 INT,field9 INT,field10 INT)"; 
    sqlite3_exec(dbConn, [sql UTF8String],NULL,NULL,NULL); 

    for(int i=0;i<recs;i++){ 
     sql = @"INSERT INTO test (id,field1,field2,field3,field4,field5,field6,field7,field8,field9,field10) VALUES (%d,1,2,3,4,5,6,7,8,9,10)"; 
     sqlite3_exec(dbConn, [sql UTF8String],NULL,NULL,NULL); 
    } 

    sql = @"BEGIN"; 
    sqlite3_exec(dbConn, [sql UTF8String],NULL,NULL,NULL); 

    if (reuse){ 
     sql = @"select * from test where field1=?1 and field2=?2 and field3=?3 and field4=?4 and field5=?5 and field6=?6 and field6=?6 and field8=?8 and field9=?9 and field10=?10 and id=?11"; 
     sqlite3_prepare_v2(dbConn, [sql UTF8String], -1, &stmt, NULL); 
    } 

    NSLog(@"reuse=%d",reuse); 
    NSLog(@"recs=%d",recs); 
    NSDate *before = [NSDate date]; 
    NSLog([before description]); 
    NSLog(@"processing..."); 
    for(int i=0;i<recs;i++){ 
     if (!reuse){ 
      sql = @"select * from test where field1=?1 and field2=?2 and field3=?3 and field4=?4 and field5=?5 and field6=?6 and field6=?6 and field8=?8 and field9=?9 and field10=?10 and id=?11"; 
      sqlite3_prepare_v2(dbConn, [sql UTF8String], -1, &stmt, NULL); 
     } 
     sqlite3_bind_int(stmt, 1, 1); 
     sqlite3_bind_int(stmt, 2, 2); 
     sqlite3_bind_int(stmt, 3, 3); 
     sqlite3_bind_int(stmt, 4, 4); 
     sqlite3_bind_int(stmt, 5, 5); 
     sqlite3_bind_int(stmt, 6, 6); 
     sqlite3_bind_int(stmt, 7, 7); 
     sqlite3_bind_int(stmt, 8, 8); 
     sqlite3_bind_int(stmt, 9, 9); 
     sqlite3_bind_int(stmt, 10, 10); 
     sqlite3_bind_int(stmt, 11, i); 

     while(sqlite3_step(stmt) == SQLITE_ROW) { 
     } 
     sqlite3_reset(stmt); 
    } 

    sql = @"BEGIN"; 
    sqlite3_exec(dbConn, [sql UTF8String],NULL,NULL,NULL); 

    NSDate *after = [NSDate date]; 
    NSLog([after description]); 
} 
+0

Istnieje kilka wskaźników (na temat tego i innych ulepszeń prędkości) tutaj: http://stackoverflow.com/questions/1711631/improve-insert-per-second- -forms-sqlite – cyrilchampier

Odpowiedz

19

Przygotowane sprawozdanie poprawy wydajności poprzez buforowanie execution plan for a query po query optimizer znalazła najlepszy plan.

Jeśli zapytanie, z którego korzystasz, nie ma skomplikowanego planu (np. Proste zaznaczenia/wstawienia bez złączeń), przygotowane instrukcje nie dadzą dużego postępu, ponieważ optymalizator szybko znajdzie najlepszy plan .

Jeśli jednak wykonano ten sam test z zapytaniem, które miało kilka sprzężeń i wykorzystywało niektóre indeksy, różnica wydajności byłaby widoczna, ponieważ optymalizator nie byłby uruchamiany przy każdym zapytaniu.

+3

Również - może tam * jest * różnica widoczna dla kolejnych iteracji. – Tomalak

+1

Istnieje duża różnica nawet dla prostych zapytań. Zrobiłem kilka prostych wstawek w każdej aktualizacji stanu w grze, było to kwestią zauważalnego jąkania w porównaniu z płynnym bieganiem. – Arahman

+0

@Omokoii Naprawdę powinieneś unikać uruchamiania operacji DB w wątku pętli ... – PSIXO

6

Tak - powoduje to, że jest ogromna różnica w porównaniu do tego, czy używasz sqlite3_exec() w porównaniu do sqlite3_prepare_v2()/sqlite3_bind_xxx()/sqlite3_step() w przypadku insertów zbiorczych.

sqlite3_exec() jest tylko wygodną metodą. Wewnętrznie po prostu wywołuje tę samą sekwencję sqlite3_prepare_v2() and sqlite3_step(). Twój przykładowy kod dzwoni sqlite3_exec() over-and-over na dosłownym wyrażenie:

for(int i=0;i<recs;i++){ 
    sql = @"INSERT INTO test (id,field1,field2,field3,field4,field5,field6,field7,field8,field9,field10) VALUES (%d,1,2,3,4,5,6,7,8,9,10)"; 
    sqlite3_exec(dbConn, [sql UTF8String],NULL,NULL,NULL); 
} 

nie wiem wewnętrzne funkcjonowanie parsera SQLite, ale może parser jest wystarczająco inteligentny, aby uznać, że jesteś przy użyciu ten sam łańcuch literowy, a następnie pomija ponowne przetwarzanie/ponowne kompilowanie przy każdej iteracji.

Jeśli spróbujesz tego samego eksperymentu z wartościami, które ulegną zmianie - zobaczysz znacznie większą różnicę w skuteczności.

+0

Byłem zaskoczony, że zrobiło to wielką różnicę. Ogoliłam kilka sekund z dużej wkładki dla mnie i mam nadzieję na większe wkładki. Zamierzam przekonwertować wszystkie zapytania, aby użyć kroku Przygotuj wiązanie teraz. :) – Joe

-3

Używanie funkcji Przygotuj + krok zamiast wykonywania ogromnych ulepszeń wydajności jest możliwe. W niektórych przypadkach przyrost wydajności przekracza czas realizacji rzędu 100%.

+0

źródło i/lub metryki? – cyrilchampier

+0

O, a 100% to niemożliwe, to absurd. –

Powiązane problemy