2009-11-09 11 views
26

He oído que las declaraciones preparadas con SQLite deberían mejorar el rendimiento. Escribí un código para probar eso, y no vi ninguna diferencia en el rendimiento con el uso de ellos. Entonces, pensé que tal vez mi código era incorrecto. Por favor, hágamelo saber si hay errores en la forma en que estoy haciendo esto ...En SQLite, ¿las declaraciones preparadas realmente mejoran el rendimiento?

[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

hay algunas métricas (sobre eso y otras mejoras de velocidad) aquí: http://stackoverflow.com/questions/1711631/improve-insert-per-second-performance-of-sqlite – cyrilchampier

Respuesta

19

Declaraciones preparadas mejorar el rendimiento de almacenamiento en caché el execution plan for a query después de la query optimizer ha encontrado el mejor plan.

Si la consulta que está utilizando no tiene un plan complicado (como simples selecciones/inserciones sin uniones), las declaraciones preparadas no le darán una gran mejora ya que el optimizador encontrará rápidamente el mejor plan .

Sin embargo, si ejecuta la misma prueba con una consulta que tiene algunas combinaciones y utiliza algunos índices, verá la diferencia de rendimiento ya que el optimizador no se ejecutará cada vez que se realice la consulta.

+3

Además, tal vez hay * es * una diferencia visible para más iteraciones. – Tomalak

+1

Hay una gran diferencia incluso para consultas simples. Hice varias inserciones simples en cada actualización de estado en un juego, era una cuestión de tartamudeo notable frente a un funcionamiento fluido. – Arahman

+0

@Omokoii Realmente deberías evitar ejecutar operaciones DB en el hilo del bucle del juego ... – PSIXO

6

Sí - se hace una diferencia enorme si su uso de sqlite3_exec() vs sqlite3_prepare_v2()/sqlite3_bind_xxx()/sqlite3_step() para las inserciones.

sqlite3_exec() es solo un método de conveniencia. Internamente solo llama a la misma secuencia de sqlite3_prepare_v2() and sqlite3_step(). El código de ejemplo está llamando sqlite3_exec() más años y más en una cadena literal:

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); 
} 

No sé el funcionamiento interno del analizador de SQLite, pero tal vez el analizador es lo suficientemente inteligente como para reconocer que está utilizando el misma cadena literal y luego omite volver a analizar/volver a compilar con cada iteración.

Si prueba el mismo experimento con valores que cambian, verá una diferencia mucho mayor en el rendimiento.

+0

Me sorprendió que esto marcara una gran diferencia. Se afeitó unos segundos de una inserción grande para mí, y me da esperanza para inserciones más grandes. Voy a convertir todas las consultas para usar prepare bind step ahora. :) – Joe

-3

Usando prepare + step en lugar de ejecutar grandes mejoras de rendimiento son posibles. En algunos casos, la ganancia de rendimiento es más del 100% en tiempo de ejecución.

+0

fuente y o métrica? – cyrilchampier

+0

O, y el 100% es imposible, eso es absurdo. –

Cuestiones relacionadas