2010-10-17 21 views
11

Esto es complicado de explicar (y muy raro), así que tengan paciencia conmigo. Explicaré el problema y la solución, pero me gustaría ver si alguien puede explicar por qué funciona de la manera que funciona :)¿El manejador de la base de datos DBI con AutoCommit establecido en 0 no devuelve los datos correctos con SELECT?

Tengo una aplicación web que usa mod_perl. Utiliza la base de datos MySQL, y estoy escribiendo datos en una base de datos de forma regular. Es modular, por lo que también tiene su propio tipo de módulo de "base de datos", donde manejo conexión, actualizaciones, etc. base de datos :: db_connect() subrutina se usa para conectarse a la base de datos, y AutoCommit se establece en 0.

Hice otra aplicación Perl (daemon independiente), que periódicamente recupera datos de la base de datos y realiza varias tareas dependiendo de qué datos se devuelven. Incluyo el módulo database.pm, por lo que no tengo que volver a escribir/duplicar todo.

problema que estoy experimentando es:

aplicación se conecta a la base de datos en el arranque, y luego bucles para siempre, ir a buscar los datos de la base de datos cada X segundos. Sin embargo, si se actualizan los datos en la base de datos, mi aplicación sigue recibiendo datos 'viejos', que obtuve en la conexión/consulta inicial a la base de datos.

Por ejemplo, tengo 3 filas y la columna "Nombre" tiene los valores "a", "b" y "c" para cada registro. Si actualizo una de las filas (usando el cliente de mysql desde la línea de comandos, por ejemplo) y cambio el nombre de 'c' a 'x', mi demonio independiente no obtendrá esos datos; aún así obtendrá un/b/c devuelto por MySQL. Capturé el tráfico db con tcpdump, y definitivamente pude ver que MySQL realmente estaba devolviendo esa información. También intenté usar SQL_NO_CACHE con SELECT (ya que no estaba seguro de qué estaba pasando), pero tampoco sirvió de nada.

Luego, he modificado la cadena de conexión de base de datos en mi daemon independiente, y configuré AutoCommit en 1. De repente, la aplicación comenzó a obtener los datos adecuados.

Estoy desconcertado, porque pensé que AutoCommit solo afecta a los tipos de instrucciones INSERT/UPDATE y no afectó a la instrucción SELECT. Pero parece que sí, y no entiendo por qué.

¿Alguien sabe por qué la instrucción SELECT no devolverá filas 'actualizadas' de la base de datos cuando AutoCommit se establece en 0, y por qué devolverá las filas actualizadas cuando AutoCommit está establecido en 1?

Aquí hay un código simplificado (comprobado de errores, etc.) que estoy usando en el daemon independiente, y que no devuelve filas actualizadas.

#!/usr/bin/perl 

use strict; 
use warnings; 
use DBI; 
use Data::Dumper; 
$|=1; 

my $dsn = "dbi:mysql:database=mp;mysql_read_default_file=/etc/mysql/database.cnf"; 
my $dbh = DBI->connect($dsn, undef, undef, {RaiseError => 0, AutoCommit => 0}); 
$dbh->{mysql_enable_utf8} = 1; 

while(1) 
{ 
    my $sql = "SELECT * FROM queue"; 
    my $stb = $dbh->prepare($sql); 
    my $ret_hashref = $dbh->selectall_hashref($sql, "ID"); 
    print Dumper($ret_hashref); 
    sleep(30); 
} 

exit; 

Cambio AutoCommit a 1 permite reparar este problema. ¿Por qué?

Gracias :)

P.S: No estoy seguro si a alguien le interesa, pero la versión de DBI es 1.613, DBD :: mysql es 4.017, Perl es 5.10.1 (en Ubuntu 10.04).

+0

¿La opción 'auto_commit' está activada o desactivada en su cliente mysql de línea de comando (donde realizó la operación' UPDATE')? – Ether

+0

Está activado (está activado por defecto y no lo he cambiado). Puedo ver los 'nuevos' datos actualizados de cliente mysql, o cualquier otra 'sesión' (nueva sesión DBI que se conecta, o cualquier otro cliente que se conecta a DB) - es sólo la sesión con AutoCommit 0 que no se puede acceder a los datos actualizados . – sentinel

Respuesta

14

Supongo que está utilizando tablas InnoDB y no MyISAM.Como se describe en el InnoDB transaction model, todas sus consultas (incluyendo SELECT) se llevan a cabo dentro de una transacción.

Cuando AutoCommit está activado, se inicia una transacción para cada consulta y, si tiene éxito, se confirma de forma implícita (si falla, el comportamiento puede variar, pero se garantiza que la transacción finalizará). Puedes ver las confirmaciones implícitas en el binlog de MySQL. Al establecer AutoCommit en falso, debe administrar las transacciones por su cuenta.

El nivel de aislamiento de transacción predeterminado es REPEATABLE READ, lo que significa que todas las consultas SELECT leerán la misma instantánea (la establecida cuando se inició la transacción).

Además de la solución dada en la otra respuesta (ROLLBACK antes de comenzar a leer) Aquí hay un par de soluciones:

Puede elegir otro nivel de aislamiento, como READ COMMITTED, lo que hace que sus SELECT consultas leen una nueva instantánea todo el tiempo.

También puede dejar AutoCommit en true (la configuración predeterminada) y comenzar sus propias transacciones emitiendo BEGIN WORK. Esto deshabilitará temporalmente el comportamiento AutoCommit hasta que emita una declaración COMMIT o ROLLBACK después de la cual cada consulta obtiene su propia transacción nuevamente (o inicia otra con BEGIN WORK).

Yo, personalmente, elegiría el último método, ya que parece más elegante.

+0

Esto es realmente una respuesta increíble, y muchas gracias por tomarse el tiempo para explicar esto. Leí la documentación y traté de resolverlo, pero realmente no encontré lo que mencionaste aquí (probablemente estaba leyendo documentos incorrectos, entonces;). Gracias una vez más, esto realmente explica mucho. – sentinel

+0

Otra pregunta (suponiendo que incluso lea esto de nuevo :). Estamos usando procedimientos almacenados en el lado de MySQL, por lo que no realizo ningún material de transacción dentro del código de Perl. ¿Puedo usar AutoCommit = 1 y emitir "BEGIN WORK" justo antes de invocar el procedimiento almacenado desde el código Perl? – sentinel

+0

Sí, puedes hacer eso. También puede colocar la transacción dentro de un procedimiento almacenado (pero * no * dentro de una función almacenada), lo que mejor se adapte a su flujo de trabajo. – kixx

4

Creo que cuando desactivas el autocommit, también comienzas una transacción. Y, cuando inicie una transacción, puede estar protegido de los cambios de otras personas hasta que lo comprometa o lo retrotraiga. Por lo tanto, si mi suposición semi-informado es correcta, y ya que sólo se está consultando los datos, añadir una reversión antes de la operación de desconexión automática (ningún punto en la celebración de las cerraduras que no esté utilizando, etc):

$dbh->rollback; 
+0

Gracias por la respuesta :) – sentinel

Cuestiones relacionadas