¿Debo confirmar o revertir una transacción de lectura?

95

Tengo una consulta de lectura que ejecuto dentro de una transacción para poder especificar el nivel de aislamiento. Una vez completada la consulta, ¿qué debo hacer?

  • Confirmar la transacción
  • Revertir la transacción
  • No hacer nada (lo que hará que la transacción se revierta al final del bloque de uso)

¿Cuáles son las implicaciones de hacer cada uno?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

EDITAR: La pregunta no es si se debe usar una transacción o si hay otras formas de establecer el nivel de transacción. La pregunta es si hace alguna diferencia que una transacción que no modifica nada se confirme o se deshaga. ¿Existe alguna diferencia de rendimiento? ¿Afecta a otras conexiones? ¿Alguna otra diferencia?

Stefan Moser
fuente
1
Probablemente ya sepa sobre esto, pero dado el ejemplo que ha proporcionado, puede obtener resultados equivalentes simplemente ejecutando la consulta: SELECT * FROM SomeTable with NOLOCK
JasonTrue
@ Stefan, parece que la mayoría de nosotros nos preguntamos por qué se molesta en realizar transacciones en una operación de solo lectura. ¿Puede informarnos si sabe acerca de NOLOCK y, si lo sabe, por qué no siguió ese camino?
StingyJack
Conozco NOLOCK, pero este sistema funciona con diferentes bases de datos, así como con SQL Server, por lo que estoy tratando de evitar sugerencias de bloqueo específicas de SQL Server. Esta es una pregunta más por curiosidad que por cualquier otra cosa, ya que la aplicación funciona bien con el código anterior.
Stefan Moser
Ah, en ese caso estoy eliminando la etiqueta sqlserver, porque eso denota MSSqlServer como el producto de destino.
StingyJack
@StingyJack - Tienes razón, no debería haber usado la etiqueta sqlserver.
Stefan Moser

Respuestas:

51

Te comprometes. Período. No hay otra alternativa sensata. Si inició una transacción, debe cerrarla. Confirmar libera cualquier bloqueo que haya tenido y es igualmente sensato con los niveles de aislamiento ReadUncommitted o Serializable. Confiar en la reversión implícita, aunque quizás sea técnicamente equivalente, es simplemente una forma deficiente.

Si eso no lo ha convencido, imagínese al siguiente tipo que inserta una declaración de actualización en medio de su código y tiene que rastrear la reversión implícita que ocurre y eliminar sus datos.

Mark Brackett
fuente
44
Existe una alternativa sensata: la reversión. Reversión explícita, eso es. Si no tenía la intención de cambiar nada, la reversión garantiza que todo se deshaga. Por supuesto, no debería haber habido ningún cambio; rollback garantiza eso.
Jonathan Leffler
2
Diferentes DBMS pueden tener diferentes semánticas de "finalización de transacciones implícitas". IBM Informix (y creo que DB2) realiza una reversión implícita; por rumor, Oracle hace un compromiso implícito. Prefiero la reversión implícita.
Jonathan Leffler
8
Supongamos que creo una tabla temporal, la lleno con identificadores, la una con una tabla de datos para seleccionar los datos que van con los identificadores y luego elimino la tabla temporal. Realmente solo estoy leyendo datos, y no me importa lo que suceda con la tabla temporal, ya que es temporal ... pero desde una perspectiva de rendimiento, ¿sería más caro deshacer la transacción o confirmarla? ¿Cuál es el efecto de una confirmación / reversión cuando solo se trata de tablas temporales y operaciones de lectura?
Triynko
4
@Triynko - Intuitivamente, supongo que ROLLBACK es más caro. COMMIT es el caso de uso normal y ROLLBACK el caso excepcional. Pero, excepto académicamente, ¿a quién le importa? Estoy seguro de que hay 1000 puntos de optimización mejores para su aplicación. Si tiene mucha curiosidad, puede encontrar el código de manejo de transacciones de mySQL en bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/…
Mark Brackett
3
@Triynko: la única forma de optimizar es perfilar. Es un cambio de código tan simple, no hay razón para no perfilar ambos métodos si realmente desea optimizarlo. ¡Asegúrate de actualizarnos con los resultados!
Mark Brackett
28

Si no ha cambiado nada, puede usar COMMIT o ROLLBACK. Cualquiera de los dos liberará los bloqueos de lectura que haya adquirido y, dado que no ha realizado ningún otro cambio, serán equivalentes.

Graeme Perrow
fuente
2
Gracias por informarme que son equivalentes. En mi opinión, esto responde mejor a la pregunta real.
Chowey
daría que la transacción está inactiva si usamos la confirmación sin actualizaciones reales. Acabo de enfrentarlo en mi sitio en vivo
Muhammad Omer Aslam
6

Si comienza una transacción, la mejor práctica es siempre confirmarla. Si se lanza una excepción dentro de su bloque de uso (transacción), la transacción se revertirá automáticamente.

Neil Barnwell
fuente
3

En mi humilde opinión, puede tener sentido envolver consultas de solo lectura en transacciones ya que (especialmente en Java) puede decirle a la transacción que sea de "solo lectura", lo que a su vez el controlador JDBC puede considerar optimizar la consulta (pero no tiene que hacerlo, por lo que nadie le impedirá emitir un INSERTno obstante). Por ejemplo, el controlador de Oracle evitará por completo los bloqueos de tablas en consultas en una transacción marcada como de solo lectura, lo que gana mucho rendimiento en aplicaciones con mucha lectura.

Oliver Drotbohm
fuente
3

Considere las transacciones anidadas .

La mayoría de los RDBMS no admiten transacciones anidadas o intentan emularlas de forma muy limitada.

Por ejemplo, en MS SQL Server, una reversión en una transacción interna (que no es una transacción real, ¡MS SQL Server solo cuenta los niveles de transacción!) Revertirá todo lo que sucedió en la transacción más externa (que es la transacción real).

Algunos envoltorios de bases de datos pueden considerar una reversión en una transacción interna como una señal de que ha ocurrido un error y revertir todo en la transacción más externa, independientemente de si la transacción más externa se comprometió o revertió.

Por lo tanto, COMMIT es la forma segura, cuando no puede descartar que su componente sea utilizado por algún módulo de software.

Tenga en cuenta que esta es una respuesta general a la pregunta. El ejemplo de código soluciona inteligentemente el problema con una transacción externa abriendo una nueva conexión de base de datos.

En cuanto al rendimiento: según el nivel de aislamiento, los SELECT pueden requerir un grado variable de LOCK y datos temporales (instantáneas). Esto se limpia cuando se cierra la transacción. No importa si esto se hace a través de COMMIT o ROLLBACK. Puede haber una diferencia insignificante en el tiempo de CPU empleado: un COMMIT es probablemente más rápido de analizar que un ROLLBACK (dos caracteres menos) y otras diferencias menores. Obviamente, esto solo es cierto para operaciones de solo lectura.

Totalmente no solicitado: otro programador que pudiera llegar a leer el código podría asumir que un ROLLBACK implica una condición de error.

Klaws
fuente
2

Solo una nota al margen, pero también puede escribir ese código así:

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

Y si reestructura las cosas solo un poco, es posible que también pueda mover el bloque de uso para IDataReader hacia arriba.

Joel Coehoorn
fuente
1

Si coloca el SQL en un procedimiento almacenado y agrega esto encima de la consulta:

set transaction isolation level read uncommitted

entonces no tiene que pasar por ningún obstáculo en el código C #. Establecer el nivel de aislamiento de la transacción en un procedimiento almacenado no hace que la configuración se aplique a todos los usos futuros de esa conexión (que es algo de lo que debe preocuparse con otras configuraciones, ya que las conexiones están agrupadas). Al final del procedimiento almacenado, simplemente vuelve a lo que sea que se inicializó la conexión.

Eric Z Barba
fuente
1

ROLLBACK se utiliza principalmente en caso de error o circunstancias excepcionales, y COMMIT en caso de finalización satisfactoria.

Deberíamos cerrar transacciones con COMMIT (para el éxito) y ROLLBACK (para el error), incluso en el caso de transacciones de solo lectura donde no parece importar. De hecho, importa, por coherencia y garantía de futuro.

Una transacción de solo lectura puede "fallar" lógicamente de muchas formas, por ejemplo:

  • una consulta no devuelve exactamente una fila como se esperaba
  • un procedimiento almacenado genera una excepción
  • se encuentra que los datos obtenidos son inconsistentes
  • el usuario aborta la transacción porque está tardando demasiado
  • punto muerto o tiempo de espera

Si COMMIT y ROLLBACK se usan correctamente para una transacción de solo lectura, continuará funcionando como se esperaba si se agrega código de escritura DB en algún momento, por ejemplo, para almacenamiento en caché, auditoría o estadísticas.

El ROLLBACK implícito solo debe usarse para situaciones de "error fatal", cuando la aplicación falla o se cierra con un error irrecuperable, falla de red, falla de energía, etc.

Sam Watkins
fuente
0

Dado que READ no cambia de estado, no haría nada. Realizar una confirmación no hará nada, excepto desperdiciar un ciclo para enviar la solicitud a la base de datos. No ha realizado una operación que haya cambiado de estado. Lo mismo ocurre con la reversión.

Sin embargo, debe asegurarse de limpiar sus objetos y cerrar sus conexiones a la base de datos. No cerrar sus conexiones puede generar problemas si este código se llama repetidamente.

Brett McCann
fuente
3
Dependiendo del nivel de aislamiento, un seleccionado PUEDE obtener bloqueos que bloquearán otras transacciones.
Graeme Perrow
La conexión se cerrará al final del bloque de uso, para eso está. Pero es un buen punto que el tráfico de la red es probablemente la parte más lenta de la ecuación.
Joel Coehoorn
1
La transacción se confirmará o revertirá de una forma u otra, por lo que la mejor práctica sería emitir siempre una confirmación si tuvo éxito.
Neil Barnwell
0

Si establece AutoCommit falso, entonces SÍ.

En un experimento con JDBC (controlador Postgresql), descubrí que si la consulta de selección se rompe (debido al tiempo de espera), no puede iniciar una nueva consulta de selección a menos que retroceda.

Shiv Krishna Jaiswal
fuente
-2

En su ejemplo de código, donde tiene

  1. // Haz algo útil

    ¿Está ejecutando una instrucción SQL que cambia datos?

Si no es así, no existe una transacción de "lectura" ... Solo los cambios de un Insertar, Actualizar y Eliminar declaraciones (declaraciones que pueden cambiar datos) están en una transacción ... Lo que está hablando son los bloqueos que SQL El servidor coloca los datos que está leyendo, debido a OTRAS transacciones que afectan esos datos. El nivel de estos bloqueos depende del nivel de aislamiento de SQL Server.

Pero no puede confirmar, o devolver nada, si su declaración SQL no ha cambiado nada.

Si está cambiando datos, puede cambiar el nivel de aislamiento sin iniciar explícitamente una transacción ... Cada declaración SQL individual está implícitamente en una transacción. iniciar explícitamente una Transacción solo es necesario para garantizar que 2 o más extractos estén dentro de la misma transacción.

Si todo lo que quiere hacer es establecer el nivel de aislamiento de la transacción, simplemente establezca el CommandText de un comando en "Establecer el nivel de aislamiento de la transacción de lectura repetible" (o el nivel que desee), establezca CommandType en CommandType.Text y ejecute el comando. (puede usar Command.ExecuteNonQuery ())

NOTA: Si está haciendo MÚLTIPLES declaraciones de lectura y desea que todas "vean" el mismo estado de la base de datos que la primera, entonces debe establecer el Nivel de aislamiento en la parte superior Lectura repetible o Serializable ...

Charles Bretana
fuente
// Hacer algo útil no cambia ningún dato, solo lee. Todo lo que quiero hacer es especificar el nivel de aislamiento de la consulta.
Stefan Moser
Entonces puede hacer eso sin iniciar explícitamente una transacción desde el cliente ... Simplemente ejecute la cadena sql "Establecer nivel de aislamiento de transacción ReadUncommitted", "... Read Committed", "... RepeatableRead", "... Snapshot" , o "... Serializable" "Establecer nivel de aislamiento leído comprometido"
Charles Bretana
3
Las transacciones siguen siendo importantes incluso si solo está leyendo. Si desea realizar varias operaciones de lectura, realizarlas dentro de una transacción garantizará la coherencia. Hacerlos sin uno no lo hará.
MarkR
sí, lo siento, tiene razón, al menos esto es cierto si el nivel de aislamiento está configurado en lectura repetible o superior.
Charles Bretana
-3

¿Necesita impedir que otros lean los mismos datos? ¿Por qué utilizar una transacción?

@Joel - Mi pregunta estaría mejor redactada como "¿Por qué utilizar una transacción en una consulta de lectura?"

@Stefan: si va a usar AdHoc SQL y no un proceso almacenado, simplemente agregue CON (NOLOCK) después de las tablas en la consulta. De esta manera no incurrirá en gastos generales (aunque mínimos) en la aplicación y la base de datos para una transacción.

SELECT * FROM SomeTable WITH (NOLOCK)

EDITAR @ Comentario 3: Ya que tenía "sqlserver" en las etiquetas de preguntas, había asumido que MSSQLServer era el producto de destino. Ahora que se ha aclarado ese punto, he editado las etiquetas para eliminar la referencia específica del producto.

Todavía no estoy seguro de por qué desea realizar una transacción en una operación de lectura en primer lugar.

StingyJack
fuente
1
Al nivel de aislamiento establecido de una vez. Puede utilizar la transacción para reducir realmente la cantidad de bloqueo de la consulta.
Joel Coehoorn
1
Estoy usando la transacción para poder usar un nivel de aislamiento más bajo y reducir el bloqueo.
Stefan Moser
@StingyJack: este código se puede ejecutar en varias bases de datos diferentes, por lo que NOLOCK no es una opción.
Stefan Moser