2012-09-27 28 views
5

Actualmente estoy jugando con las transacciones y no puedo entender el siguiente escenario:¿Por qué esta transacción no funciona en ActiveRecord?

Dado que hay un usuario con nombre de usuario "johnny" y el nombre completo "John Smith".

empiezo dos consolas carriles y llevar a cabo los siguientes comandos en este orden:

consola A:

ActiveRecord::Base.transaction { user = User.find_by_username("foo"); sleep 10; user.update_attribute(:full_name, "#{user.full_name}-1"); } 

consola B:

ActiveRecord::Base.transaction { user = User.find_by_username("foo"); sleep 10; user.update_attribute(:full_name, "#{user.full_name}-2"); } 

Así que el momento es el siguiente:

A dice "John Smith"

B lee "John Smith"

A escribe "John Smith-1"

B escribe "John Smith-2"

De acuerdo con mi base de datos de transacciones de clase B debe dejar de escribir "John Smith-2 "porque los datos han cambiado desde que lo leyeron. Por lo tanto, la transacción debería revertirse y la transacción A debería ganar. Espero que el nombre de usuario sea "John Smith-1", pero el resultado es "John Smith-2".

¿Alguna idea de por qué sucede esto o cómo obtener el comportamiento esperado?

Saludos cordiales

Nils

+0

Podría ser el nivel de aislamiento de la conexión. Asegúrese de que sea un bloqueo optimista y no una estabilidad del cursor. Además, podría ser, irónicamente, debido a la transacción. Si ambos bloques están en una transacción, técnicamente, el segundo debe * comenzar * hasta que el primero finalice, o debe seguir las reglas definidas en ACID que la segunda transacción * como si * la primera no se haya realizado, pero confirmar * como si * el segundo no comenzó hasta que el primero terminó. –

+0

¿Qué te hace pensar que esto es activerecord específico?si realiza una tarea similar desde la interfaz de línea de comandos de la base de datos, ¿el resultado es diferente? –

+0

@FrederickCheung Depende del SQL que use. Si fuera 'SELECCIONAR ... PARA ACTUALIZAR ', sería diferente. Aún no es lo que OP espera. Pero entonces sería un bloqueo pesimista: http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html –

Respuesta

4

Por lo que yo entiendo transacción no está a punto de bloqueo, el objetivo principal de la operación es asegurar cambios atómicos. Por ejemplo, cuando deduce dinero de su cuenta de cheques y lo deposita en su cuenta de ahorros, necesita estar seguro de que tanto INSERT tuvo éxito o que ambos fallaron o que quedará inconsistente. Lo que necesita es bloquear, p. http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html.

ACTUALIZACIÓN: ACID no está destinado a deshacer la transacción como yo lo entiendo. Si no hay errores, ambas transacciones tendrán éxito. Lo que obtienes como resultado depende del aislamiento. Si se usó el nivel SERIALIZABLE, obtendría "John Smith-1-2", pero InnoDB usa por defecto REPEATABLE READ nivel http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read, lo que significa que si SELECT no está bloqueado (User.find_by...) no bloqueará el registro para leer y obtendrá el resultado original de la instantánea que se creó en el momento en que se inició la transacción A (es decir, SELECT desde B no se bloqueará hasta que A haya finalizado como en el caso de SERIALIZABLE).

ACTUALIZACIÓN: Mientras tanto puede comprobar http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html para el bloqueo pesimista.

+0

Gracias primero por su respuesta. Hasta donde yo sé, las Transacciones InnoDB de MySQL son compatibles con ACID (http://en.wikipedia.org/wiki/ACID). Su explicación ignora la "I", el aislamiento significa que el resultado de las transacciones concurrentes es el mismo ya que se ejecutan secuencialmente, lo que en realidad no es el caso aquí. Entonces, la pregunta es: ¿por qué ActiveRecord no funciona así? – NilsHaldenwang

+0

Ah, ya veo, gracias por la aclaración. – NilsHaldenwang

Cuestiones relacionadas