2009-08-03 10 views
10

Estoy realizando una gran cantidad de INSERTOS en una base de datos SQLite. Estoy usando solo un hilo. Loteo las escrituras para mejorar el rendimiento y tener un poco de seguridad en caso de un bloqueo. Básicamente guardo en la memoria caché un montón de datos y luego, cuando lo considero apropiado, recorro todos esos datos y realizo los INSERTOS. El código para esto se muestra a continuación:El archivo de base de datos está inexplicablemente bloqueado durante la confirmación SQLite

public void Commit() 
    { 
     using (SQLiteConnection conn = new SQLiteConnection(this.connString)) 
     { 
      conn.Open(); 
      using (SQLiteTransaction trans = conn.BeginTransaction()) 
      { 
       using (SQLiteCommand command = conn.CreateCommand()) 
       { 
        command.CommandText = "INSERT OR IGNORE INTO [MY_TABLE] (col1, col2) VALUES (?,?)"; 

        command.Parameters.Add(this.col1Param); 
        command.Parameters.Add(this.col2Param); 

        foreach (Data o in this.dataTemp) 
        { 
         this.col1Param.Value = o.Col1Prop; 
         this. col2Param.Value = o.Col2Prop; 

         command.ExecuteNonQuery(); 
        } 
       } 
       this.TryHandleCommit(trans); 
      } 
      conn.Close(); 
     } 
    } 

ahora emplean el siguiente truco para conseguir la cosa que finalmente funciona:

private void TryHandleCommit(SQLiteTransaction trans) 
    { 
     try 
     { 
      trans.Commit(); 
     } 
     catch (Exception e) 
     { 
      Console.WriteLine("Trying again..."); 
      this.TryHandleCommit(trans); 
     } 
    } 

Creo mi DB de este modo:

public DataBase(String path) 
    { 
     //build connection string 
     SQLiteConnectionStringBuilder connString = new SQLiteConnectionStringBuilder(); 
     connString.DataSource = path; 
     connString.Version = 3; 
     connString.DefaultTimeout = 5; 
     connString.JournalMode = SQLiteJournalModeEnum.Persist; 
     connString.UseUTF16Encoding = true; 

     using (connection = new SQLiteConnection(connString.ToString())) 
     { 
      //check for existence of db 
      FileInfo f = new FileInfo(path); 

      if (!f.Exists) //build new blank db 
      { 
       SQLiteConnection.CreateFile(path); 
       connection.Open(); 

       using (SQLiteTransaction trans = connection.BeginTransaction()) 
       { 
        using (SQLiteCommand command = connection.CreateCommand()) 
        { 
         command.CommandText = DataBase.CREATE_MATCHES; 
         command.ExecuteNonQuery(); 

         command.CommandText = DataBase.CREATE_STRING_DATA; 
         command.ExecuteNonQuery(); 
         //TODO add logging 
        } 
        trans.Commit(); 
       } 
       connection.Close(); 
      } 
     }    
    } 

Luego exporto la cadena de conexión y la uso para obtener nuevas conexiones en diferentes partes del programa.

En intervalos aparentemente aleatorios, aunque a una velocidad demasiado grande como para ignorar o solucionar este problema, me sale SQLiteException sin gestionar: el archivo de la base de datos está bloqueado. Esto ocurre cuando intento comprometer la transacción. No parece haber errores antes de eso. Esto no significa siempre suceder. A veces todo funciona sin problemas.

  • No se están realizando lecturas en estos archivos antes de que finalicen las confirmaciones.
  • Tengo el último binario de SQLite.
  • Estoy compilando para .NET 2.0.
  • Estoy usando VS 2008.
  • El db es un archivo local.
  • Toda esta actividad se encapsula dentro de un hilo/proceso.
  • La protección contra virus está desactivada (aunque creo que eso solo era relevante si se conectaba a través de una red).
  • Según el post de escocés He implementado los siguientes cambios:
  • Modo Diario configurar para que persistan
  • archivos de base de datos almacenada en C: \ Docs + Settings \ ApplicationData través System.Windows.Forms.Application.AppData ventanas llaman
  • Sin excepción interna
  • Presenciado en dos máquinas distintas (aunque hardware y software muy similares)
  • Ejecutamos Process Monitor, no hay procesos externos que se adjuntan a los archivos de la base de datos. Definitivamente el problema está en mi código ...

¿Alguien tiene alguna idea de lo que está pasando aquí?

Sé que acaba de caer un lío de código, pero he estado tratando de resolver esto por demasiado tiempo. ¡Mi agradecimiento a cualquiera que llegue al final de esta pregunta!

Brian

ACTUALIZACIONES:

, gracias por las sugerencias hasta ahora! Implementé muchos de los cambios sugeridos.Siento que nos estamos acercando a la respuesta ... sin embargo ...

¡El código anterior funciona técnicamente, pero no es determinante! No está garantizado hacer nada aparte de girar en neutral para siempre. En la práctica, parece funcionar en algún lugar entre la primera y la décima iteración. Si hago un lote de mis compromisos a un intervalo razonable, el daño se mitigará, pero realmente no quiero dejar las cosas en este estado ...

¡Más sugerencias bienvenidas!

+0

Aunque no desea tener confirmaciones más pequeñas, ¿sigue apareciendo el problema cuando se mueve begin tran y commit tran en el batch interno? – pjp

Respuesta

5

Ejecuta Sysinternals Process Monitor y filtra el nombre del archivo mientras ejecutas el programa para descartarte si algún otro proceso hace algo al respecto y para ver qué hace exactamente tu programa al archivo. Posible, pero podría dar una pista.

+1

entonces ... ¿el problema era solo otro proceso de bloqueo del archivo? – Rory

2

¿Su archivo de base de datos está en la misma máquina que la aplicación o está almacenado en un servidor?

Debe crear una nueva conexión en cada hilo. Me simplefy la creación de una conexión, utilice en todas partes: la conexión = new SQLiteConnection (connString.ToString());

y utilice un archivo de base de datos en la misma máquina que la aplicación y vuelva a probar.

Por qué las dos maneras diferentes de crear una conexión?

+0

Ahora he implementado esto. ¡Sin suerte, pero un buen comienzo, estoy seguro! ¡Gracias! –

3

cosas a tener en cuenta:

  • no utilizan conexiones a través de múltiples hilos/procesos.

  • He visto que sucede cuando un escáner de virus detecta cambios en el archivo y trata de escanearlo. Bloquearía el archivo por un breve intervalo y causaría estragos.

+1

Acabo de intentarlo - ¡supongo que no fue el problema, pero gracias de todos modos! –

2

Estos chicos tenían problemas parecidos (en su mayoría, al parecer, con el archivo de diario de estar encerrado, tal vez TortoiseSVN interacciones ... ver los artículos referenciados).

Se les ocurrió un conjunto de recomendaciones (directorios correctos, cambiar los tipos de diario de eliminar a persistir, etc.). Se discuten http://sqlite.phxsoftware.com/forums/p/689/5445.aspx#5445


Las opciones de modo de diario aquí: http://www.sqlite.org/pragma.html. Podrías probar TRUNCATE.

¿Hay un seguimiento de la pila durante la excepción en SQL Lite?

Usted que indican "por lotes mis compromete en un intervalo razonable". ¿Cuál es el intervalo?

+0

muy interesante, de hecho, uso tortuga. Volveré a publicar y le diré si ayuda ... –

+0

el uso de persistir puede haber reducido la frecuencia de este problema, aunque no lo ha eliminado. también UserAppData fue una buena decisión. ¡Gracias! –

+0

Entonces, ¿dónde te deja eso? Si bien estoy seguro de que no se supone que sea un problema, ¿es posible que esté haciendo commits muy cerca (en el tiempo) juntos? – BlueShepherd

2

Siempre usaré una conexión, una transacción y un comando en una cláusula using. En su primer listado de códigos lo hizo, pero su tercero (crear las tablas) no lo hizo. Sugiero que hagas eso también, porque (¿quién sabe?) Tal vez los comandos que crean la tabla de alguna manera continúen bloqueando el archivo. Posible tiro ... ¿vale la pena intentarlo?

+0

bien, desafortunadamente eso no funcionó, pero es probablemente la mejor forma de hacerlo de todos modos así que he actualizado mi código en consecuencia ... thx! –

2

¿Tiene Google Desktop Search (u otro indexador de archivos) ejecutándose? Como se mencionó anteriormente, Sysinternals Process Monitor puede ayudarlo a rastrearlo.

Además, ¿cuál es el nombre del archivo de la base de datos?De PerformanceTuningWindows:?

ser muy, muy cuidadoso con lo que el nombre de su base de datos, en especial la extensión

Por ejemplo, si se le da todas las bases de datos de la extensión .sdb (base de datos SQLite, buen nombre bueno yo pensaba así que cuando lo elijo de todos modos ...) descubres que la extensión SDB ya está asociada a los PAQUETES APPFIX.

Ahora, aquí es la parte linda, AppFix es un ejecutable/paquete que Windows XP reconoce, y será, (el énfasis es mío) AÑADIR LA BASE DE DATOS PARA EL SISTEMA restaurar la funcionalidad

Esto quiere decir, quedarse conmigo aquí , cada vez que escribe NADA en la base de datos, el sistema Windows XP piensa que un ejecutable sangriento ha cambiado y copia su base de datos ENTERO 800 meg al directorio de restauración del sistema ...

Recomiendo algo como DB o DAT.

9

Parece que no pudo vincular el comando con la transacción que ha creado. En lugar de:

using (SQLiteCommand command = conn.CreateCommand()) 

Deberá utilizar:

using (SQLiteCommand command = new SQLiteCommand("<INSERT statement here>", conn, trans)) 

O puede establecer su propiedad de transacciones después de su construcción.

Mientras estamos en ello - el manejo de los fracasos es incorrecto:

método ExecuteNonQuery del comando también puede fallar y no está realmente protegido. Debe cambiar el código a algo como:

public void Commit() 
    { 
     using (SQLiteConnection conn = new SQLiteConnection(this.connString)) 
     { 
      conn.Open(); 
      SQLiteTransaction trans = conn.BeginTransaction(); 
      try 
      { 
       using (SQLiteCommand command = conn.CreateCommand()) 
       { 
        command.Transaction = trans; // Now the command is linked to the transaction and don't try to create a new one (which is probably why your database gets locked) 
        command.CommandText = "INSERT OR IGNORE INTO [MY_TABLE] (col1, col2) VALUES (?,?)"; 

        command.Parameters.Add(this.col1Param); 
        command.Parameters.Add(this.col2Param); 

        foreach (Data o in this.dataTemp) 
        { 
         this.col1Param.Value = o.Col1Prop; 
         this. col2Param.Value = o.Col2Prop; 

         command.ExecuteNonQuery(); 
        } 
       } 

       trans.Commit(); 
      } 
      catch (SQLiteException ex) 
      { 
       // You need to rollback in case something wrong happened in command.ExecuteNonQuery() ... 
       trans.Rollback(); 
       throw; 
      } 
     } 
    } 

Otra cosa es que no necesita almacenar nada en la memoria caché. Puede confiar en el mecanismo de diario de SQLite para almacenar el estado de transacción incompleto.

+0

Genial Necesitaré algo de tiempo para digerir todo esto y probarlo, ¡pero gracias por la contribución! –

+0

Además, me alegra que menciones el almacenamiento en caché. Originalmente lo estaba haciendo de la forma en que sugieres, pero me alejé de eso porque estaba preocupado de que estaba contribuyendo a la confusión de este compromiso. Una vez que lo haga funcionar, volveré y publicaré algunas muestras de código adecuadas ... –

+0

, así que probé ambos métodos: construir al pasar la transacción y establecer la propiedad post-hoc. ninguno ha aliviado mi problema. gracias por las sugerencias, aunque! –

4

Tuvimos un problema muy similar al usar transacciones anidadas con la clase TransactionScope. Nos pensamiento todas las acciones de la base de datos se produjeron en el mismo hilo ... sin embargo, nos atrapó el mecanismo de transacción ... más específicamente, la transacción Ambient.

Básicamente hubo una transacción más arriba en la cadena que, por la magia del ado, la conexión se enlistó automáticamente. El resultado fue que, a pesar de que pensábamos que estábamos escribiendo en la base de datos en un solo hilo, la escritura no Realmente sucede hasta que se comprometió la transacción más alta. En este punto "indeterminado", la base de datos se escribió para que se bloqueara fuera de nuestro control.

La solución fue garantizar que la base de datos SQLite no tomó parte directa en la transacción ambiente, asegurando que usamos algo como:

using(TransactionScope scope = new TransactionScope(TransactionScopeOptions.RequiresNew)) 
{ 
    ... 
    scope.Complete() 
} 
3

empecé a hacer frente a este problema hoy mismo: estoy estudiando asp. net mvc, construyendo mi primera aplicación completamente desde cero. A veces, cuando escribía en la base de datos, recibía la misma excepción, diciendo que el archivo de la base de datos estaba bloqueado.

Me pareció realmente extraño, ya que estaba completamente seguro de que solo había una conexión abierta en ese momento (según el listado de exploradores de proceso de los identificadores de archivos activos).

También he creado toda la capa de acceso a datos desde cero, utilizando el proveedor System.Data.SQLite .Net, y cuando lo planeé, tuve especial cuidado con las conexiones y transacciones, para asegurar que no hubiera conexión o transacción fue dejada dando vueltas.

La parte difícil era que establecer un punto de interrupción en el comando ExecuteNonQuery() y ejecutar la aplicación en modo de depuración haría desaparecer el error. Google, encontré algo interesante en este sitio: http://www.softperfect.com/board/read.php?8,5775. Allí, alguien respondió el hilo sugiriendo al autor que coloque la ruta de la base de datos en la lista de ignorar antivirus.

Agregué el archivo de base de datos a la lista de ignorar mi antivirus (Microsoft Security Essentials) y resolvió mi problema. ¡No más errores bloqueados en la base de datos!

Cuestiones relacionadas