Se le pide que no use transacciones y que use una solución alternativa para simular una

43

He estado desarrollando T-SQL durante varios años y siempre estoy investigando más, continuando aprendiendo todo lo que puedo sobre todos los aspectos del lenguaje. Recientemente comencé a trabajar en una nueva empresa y he recibido lo que creo que es una sugerencia extraña con respecto a las transacciones. Nunca los uses. En su lugar, use una solución alternativa que simule una transacción. Esto proviene de nuestro DBA que trabaja en una base de datos con muchas transacciones y, posteriormente, muchos bloqueos. La base de datos en la que trabajo principalmente no sufre este problema y veo que las transacciones se han utilizado en el pasado.

Entiendo que se espera el bloqueo con las transacciones, ya que está en su naturaleza hacerlo y si puede escapar sin usar una, hágalo de todas maneras. Pero tengo muchas ocasiones donde cada declaración DEBE ejecutarse con éxito. Si uno falla, todos deben dejar de comprometerse.

Siempre mantuve el alcance de mis transacciones lo más estrecho posible, siempre lo utilicé junto con SET XACT_ABORT ON y siempre dentro de TRY / CATCH.

Ejemplo:

CREATE SCHEMA someschema;
GO


CREATE TABLE someschema.tableA
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColA VARCHAR(10) NOT NULL
);
GO

CREATE TABLE someschema.tableB
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColB VARCHAR(10) NOT NULL
); 
GO


CREATE PROCEDURE someschema.ProcedureName @ColA VARCHAR(10), 
                                          @ColB VARCHAR(10)
AS
SET NOCOUNT, XACT_ABORT ON;
BEGIN
BEGIN TRY
    BEGIN TRANSACTION;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);

--Implement error
    SELECT 1/0 

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF @@trancount > 0
    BEGIN
        ROLLBACK TRANSACTION;
    END;
    THROW;
    RETURN;
END CATCH;
END;
GO

Esto es lo que me sugirieron que haga.

GO



CREATE PROCEDURE someschema.ProcedureNameNoTransaction @ColA VARCHAR(10), 
                                                       @ColB VARCHAR(10)
AS
SET NOCOUNT ON;
BEGIN
BEGIN TRY
    DECLARE @tableAid INT;
    DECLARE @tableBid INT;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);
    SET @tableAid = SCOPE_IDENTITY();

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);
    SET @tableBid = SCOPE_IDENTITY();

--Implement error
    SELECT 1/0 

END TRY
BEGIN CATCH
    DELETE FROM someschema.tableA
    WHERE id = @tableAid;

    DELETE FROM someschema.tableB
    WHERE id = @tableBid;

    THROW;

    RETURN;
END CATCH;
END;
GO

Mi pregunta a la comunidad es la siguiente. ¿Tiene sentido esto como una solución viable para las transacciones?

Mi opinión sobre lo que sé sobre las transacciones y lo que propone la solución es que no, esta no es una solución viable e introduce muchos puntos de falla.

En la solución sugerida, veo que ocurren cuatro transacciones implícitas. Las dos inserciones en el intento y luego dos transacciones más para las eliminaciones en la captura. Hace "deshacer" las inserciones, pero sin deshacer nada, por lo que nada se revierte.

Este es un ejemplo muy básico para demostrar el concepto que sugieren. Algunos de los procedimientos almacenados reales en los que he estado haciendo esto los hacen exhaustivamente largos y difíciles de administrar porque "revertir" múltiples conjuntos de resultados frente a dos valores de parámetros en este ejemplo se vuelve bastante complicado como se podría imaginar. Como "retroceder" se está haciendo manualmente ahora, la oportunidad de perder algo porque es real.

Otro problema que creo que existe es por tiempos de espera o conexiones cortadas. ¿Esto todavía se revierte? Esta es mi comprensión de por qué SET XACT_ABORT ON debe usarse para que, en estos casos, la transacción retroceda.

Gracias por sus comentarios de antemano!

Para descanso
fuente
44
Los comentarios que no cumplen su propósito declarado se han eliminado o movido a la respuesta Wiki de la comunidad.
Paul White dice GoFundMonica

Respuestas:

61

No se puede no utilizar transacciones en SQL Server (y probablemente cualquier otro RDBMS adecuada). En ausencia de límites de transacción explícitos ( begin transaction... commit) cada instrucción SQL inicia una nueva transacción, que se confirma (o revierte) implícitamente después de que la instrucción se complete (o falle).

La simulación de transacción sugerida por la persona que se presenta como su "DBA" no garantiza tres de las cuatro propiedades requeridas del procesamiento de la transacción, ya que aborda solo los errores "blandos" y no es capaz de lidiar con los errores "duros", como desconexiones de red, cortes de energía, fallas de disco, etc.

  • Atomicidad: fallar. Si ocurre un error "difícil" en algún lugar en el medio de su pseudo-transacción, el cambio no será atómico.

  • Consistencia: falla. De lo anterior se deduce que sus datos estarán en un estado inconsistente después de un error "duro".

  • Aislamiento: falla. Es posible que una pseudo-transacción concurrente cambie algunos de los datos modificados por su pseudo-transacción antes de que se complete la suya.

  • Durabilidad: éxito. Los cambios que realice serán duraderos, el servidor de la base de datos se asegurará de eso; Esto es lo único que el enfoque de su colega no puede arruinar.

Los bloqueos son un método ampliamente utilizado y empíricamente exitoso para garantizar la ACIDidad de las transacciones en todo tipo o RDBMS (este sitio es un ejemplo). Me parece muy poco probable que un DBA aleatorio pueda encontrar una mejor solución al problema de concurrencia que cientos, posiblemente miles de informáticos e ingenieros que han estado construyendo algunos sistemas de bases de datos interesantes durante el último, ¿qué, 50? ¿60 años? (Me doy cuenta de que esto es un tanto falaz como argumento de "apelación a la autoridad", pero lo mantendré de todos modos).

En conclusión, ignora el consejo de tu "DBA" si puedes, lucha contra él si tienes el espíritu y vuelve aquí con problemas específicos de concurrencia si surgen.

mustaccio
fuente
14

Hay algunos errores que son tan graves que nunca se ingresa el bloque CATCH. De la documentación

Errores que tienen una gravedad de 20 o superior que detienen el procesamiento de tareas del Motor de base de datos de SQL Server para la sesión. Si se produce un error que tiene una gravedad de 20 o superior y la conexión de la base de datos no se interrumpe, INTENTAR ... CATCH se encargará del error.

Atenciones, como solicitudes de interrupción de clientes o conexiones de clientes interrumpidas.

Cuando un administrador del sistema finaliza la sesión utilizando la instrucción KILL.

...

Errores de compilación, como los errores de sintaxis, que impiden que se ejecute un lote.

Errores que ocurren ... debido a la resolución de nombre diferido.

Muchos de estos son fáciles de producir a través de SQL dinámico. Deshacer declaraciones como las que ha mostrado no protegerán sus datos de tales errores.

Michael Green
fuente
2
Correcto, y si nada más, el cliente que muere mientras ejecuta el código constituiría un error "tan grave que nunca se ingresa el bloque CATCH". No importa cuánto confíe en el software (no solo su propio código, sino CADA parte de TODAS las pilas de software involucradas), siempre existe la posibilidad de que una falla de hardware (nuevamente, potencialmente en cualquier lugar de la cadena) lo detenga en cualquier momento . Tener esto en cuenta es una buena defensa contra el pensamiento amable que conduce a este tipo de "solución".
El
2
Además, puede ser una víctima de punto muerto. Sus bloques CATCH se ejecutan pero se lanzan si intentan escribir en la base de datos.
Joshua
10

i-one : la solución alternativa que se le sugiere hace posible (al menos) violar la "A" de la ACID . Por ejemplo, si un cliente remoto está ejecutando SP y se interrumpe la conexión, puede ocurrir un "commit" / "rollback" parcial, debido a que el servidor puede terminar la sesión entre dos inserciones / eliminaciones (y cancelar la ejecución del SP antes de que llegue a su fin) .

¿Tiene sentido esto como una solución viable para las transacciones?

dan-guzman : No, elCATCHbloque nunca se ejecuta en el caso de un tiempo de espera de consulta porque la API del cliente canceló el lote. Sin una transacción,SET XACT_ABORT ONno puede deshacer otra cosa que no sea la declaración actual.

tibor-karaszi : tiene 4 transacciones, lo que significa más registro en el archivo de registro de transacciones. Recuerde que cada transacción requiere una escritura síncrona de los registros de registro hasta ese punto, es decir, obtiene un peor rendimiento también de ese aspecto cuando usa muchas transacciones.

rbarryyoung : Si están bloqueando mucho, entonces necesitan arreglar su diseño de datos, racionalizar su orden de acceso a la tabla o usar un nivel de aislamiento más apropiado. Asumen que sus problemas (y su incapacidad para comprenderlos) se convertirán en su problema. La evidencia de millones de otras bases de datos es que no lo hará.

Además, lo que intentan implementar manualmente es efectivamente una concurrencia optimista para los pobres. Lo que deberían hacer es utilizar la mejor concurrencia optimista del mundo, ya integrada en SQL Server. Esto va al punto de aislamiento anterior. Con toda probabilidad, necesitan cambiar de cualquier nivel de aislamiento de concurrencia pesimista que estén utilizando actualmente a uno de los niveles de aislamiento de concurrencia optimista, SNAPSHOTo READ_COMMITTED_SNAPSHOT. Estos efectivamente harán lo mismo que su código manual, excepto que lo harán correctamente.

ross-presser : Si tiene procesos de ejecución extremadamente largos, como si algo sucediera hoy y la próxima semana, algo tiene que hacer un seguimiento, y si las cosas de la próxima semana fallan, las de hoy tienen que fallar retroactivamente; es posible que desee investigar las sagas . Estrictamente hablando, esto está fuera de la base de datos, ya que requiere un bus de servicio.

usuario126897
fuente
5

El código de mala idea será más costoso de arreglar.

Si hay problemas de bloqueo al usar transacciones explícitas (rollback / commit), dirija su DBA a internet para obtener algunas ideas geniales para abordar los problemas.

Aquí hay una manera de ayudar a aliviar el bloqueo: https://www.sqlservercentral.com/articles/using-indexes-to-reduce-blocking-in-concurrent-transactions

Los índices reducen el número de búsquedas que deben ocurrir en una tabla / página para encontrar una fila / conjunto de filas. Generalmente se consideran como un método para reducir los tiempos de ejecución de las consultas SELECT * y con razón también. No se consideran adecuados para tablas involucradas en gran cantidad de ACTUALIZACIONES. De hecho, los ÍNDICES se encuentran desfavorables en estos casos, ya que aumentan el tiempo necesario para completar las consultas de ACTUALIZACIÓN.

Pero este no es siempre el caso. Profundizando ligeramente en la ejecución de una instrucción UPDATE, encontramos que también implica ejecutar primero una instrucción SELECT. Este es un escenario especial y a menudo visto donde las consultas actualizan conjuntos de filas mutuamente excluyentes. Los ÍNDICES aquí pueden conducir a un aumento significativo en el rendimiento del motor de base de datos contrario a la creencia popular.

usuario238855
fuente
4

La estrategia de transacción falsa es peligrosa porque permite problemas de concurrencia que las transacciones específicamente evitan. Tenga en cuenta que en el segundo ejemplo, cualquiera de los datos puede cambiarse entre declaraciones.

Las eliminaciones de transacciones falsas NO ESTÁN GARANTIZADAS para ejecutarse o tener éxito. Si el servidor de la base de datos se apaga durante la transacción falsa, quedarán algunos pero no todos los efectos. Tampoco se garantiza que tengan éxito de la misma manera que lo es una reversión de transacciones.

Esta estrategia podría funcionar con inserciones, pero definitivamente no funcionaría con actualizaciones o eliminaciones (sin sentencias SQL de máquina del tiempo).

Si la concurrencia estricta de la transacción está causando el bloqueo, hay muchas soluciones, incluso las que reducen el nivel de protección ... esta es la forma correcta de resolver el problema.

Su DBA ofrece una solución que podría funcionar bien si solo hubiera un usuario de la base de datos, pero no es apto para ningún tipo de uso serio.

Baileys
fuente
4

Este no es un problema de programación, más bien es un problema interpersonal / de falta de comunicación. Lo más probable es que su "DBA" esté preocupado por bloqueos, no por transacciones.

Las otras respuestas ya explican por qué tiene que usar las transacciones ... Quiero decir, eso es lo que hace RDBMS, sin las transacciones usadas adecuadamente no hay integridad de datos, así que me enfocaré en cómo resolver el problema real, que es: descubra por qué su "DBA" desarrolló una alergia a las transacciones y lo convenció de cambiar de opinión.

Creo que este tipo está confundiendo "un escenario particular en el que un código incorrecto resultó en un rendimiento terrible" con "todas las transacciones son malas". No esperaría que un DBA competente cometiera ese error, así que eso es realmente extraño. ¿Quizás tuvo una experiencia realmente mala con un código terrible?

Considere un escenario como este:

BEGIN
UPDATE or DELETE some row, which takes locks it
...do something that takes a while
...perform other queries
COMMIT

Este estilo de uso de transacciones tiene un bloqueo (o varios bloqueos), lo que significa que otras transacciones que lleguen a las mismas filas tendrán que esperar. Si los bloqueos se mantienen durante mucho tiempo, y especialmente si muchas otras transacciones desean bloquear las mismas filas, esto realmente puede afectar el rendimiento.

Lo que podría hacer es preguntarle por qué tiene esta idea curiosamente incorrecta de no usar transacciones, qué tipos de consultas eran problemáticas, etc. Luego, intente convencerlo de que definitivamente evitará situaciones similares, que controlará el uso de su bloqueo y rendimiento, tranquilizarlo, etc.

Lo que te está diciendo es "¡no toques el destornillador!" entonces el código que publicaste en tu pregunta es básicamente usar un martillo para atornillar. Una opción mucho mejor es convencerlo de que sabes cómo usar un destornillador ...

Puedo pensar en varios ejemplos ... bueno, estaban en MySQL pero eso también debería funcionar.

Hubo un foro donde el índice de texto completo tardó un tiempo en actualizarse. Cuando un usuario envía una publicación, la transacción actualiza la tabla de temas para aumentar el recuento de publicaciones y la última fecha de publicación (bloqueando así la fila del tema), luego inserta la publicación y la transacción retiene el bloqueo hasta que el índice de texto completo haya terminado de actualizarse y se hizo el COMPROMISO.

Dado que esto se ejecutó en un depósito de óxido con muy poca RAM, la actualización de dicho índice de texto completo a menudo resultó en varios segundos de E / S aleatorias intensas en la única unidad de giro lento en la caja.

El problema era que las personas que hacían clic en el tema causaban que una consulta aumentara el recuento de vistas sobre el tema, lo que también requería un bloqueo en la fila del tema. Por lo tanto, nadie podía ver el tema mientras se actualizaba su índice de texto completo. Quiero decir, la fila se podía leer, pero la actualización se bloquearía.

Peor aún, la publicación actualizaría el recuento de publicaciones en la tabla de foros principales y también mantendría el bloqueo mientras se actualizaba el índice de texto completo ... lo que congeló todo el foro durante unos segundos y provocó la acumulación de toneladas de solicitudes en la cola del servidor web .

La solución fue tomar los bloqueos en el orden correcto: COMIENCE, inserte la publicación y actualice el índice de texto completo sin tomar ningún bloqueo, luego actualice rápidamente las filas de tema / foro con el recuento de publicaciones y la última fecha de publicación, y COMPROMÉTASE. Eso resolvió por completo el problema. Solo se movían algunas consultas, realmente simple.

En este caso, las transacciones no eran el problema ... Estaba adquiriendo un bloqueo innecesario antes de una operación larga. Otros ejemplos de cosas que se deben evitar mientras se mantiene un bloqueo en una transacción: esperar la entrada del usuario, acceder a muchos datos no almacenados en caché desde unidades de giro lento, IO de red, etc.

Por supuesto, a veces, no tiene otra opción y tiene que hacer un procesamiento prolongado mientras mantiene bloqueos engorrosos. Hay trucos en torno a esto (operar con una copia de los datos, etc.) pero con frecuencia el cuello de botella en el rendimiento proviene de un bloqueo que no se adquirió intencionalmente, y simplemente reordenar las consultas resuelve el problema. Aún mejor, es ser consciente de los bloqueos tomados al escribir las consultas ...

No repetiré las otras respuestas pero realmente ... usa transacciones. Su problema es convencer a su "DBA", no trabajar con la característica más importante de una base de datos ...

Peufeu
fuente
3

TLDR: utilice el nivel de aislamiento adecuado .

Como notó correctamente, el enfoque sin transacciones y con recuperación "manual" puede ser muy complejo. La alta complejidad significa normalmente mucho más tiempo para implementarlo y mucho más tiempo para corregir errores (porque la complejidad conduce a más errores en la implementación). Significa que tal enfoque puede costarle mucho más a su cliente.

La principal preocupación de su colega "dba" es el rendimiento. Una de las formas de mejorarlo es usar el nivel de aislamiento adecuado. Suponga que tiene un procedimiento que proporciona algún tipo de información general al usuario. Tal procedimiento no necesariamente tiene que usar el nivel de aislamiento SERIALIZABLE. En muchos casos, LEER NO COMPROMETIDO puede ser bastante suficiente. Esto significa que dicho procedimiento no será bloqueado por su transacción que crea o modifica algunos datos.

Le sugiero que revise todas las funciones / procedimientos existentes en su base de datos, evalúe el nivel de aislamiento razonable para cada uno, explique los beneficios de rendimiento a su cliente. Luego ajuste estas funciones / procedimientos en consecuencia.

Mentallurg
fuente
2

También puede decidir usar tablas OLTP en memoria. Por supuesto, todavía usan transacciones, pero no hay ningún bloqueo involucrado.
En lugar de bloquear, todas las operaciones serán exitosas, pero durante la fase de confirmación, el motor verificará si hay conflictos de transacción y una de las confirmaciones puede fallar. Microsoft usa el término "bloqueo optimista".
Si el problema de escalado es causado por un conflicto entre dos operaciones de escritura, como dos transacciones concurrentes que intentan actualizar la misma fila, In-Memory OLTP permite que una transacción tenga éxito y falla la otra transacción. La transacción fallida se debe volver a enviar explícita o implícitamente, volviendo a intentar la transacción.
Más en: En memoria OLTP

Piotr
fuente
-5

Hay una forma de usar las transacciones de forma limitada y eso es cambiando su modelo de datos para que esté más orientado a objetos. Entonces, en lugar de almacenar, por ejemplo, datos demográficos sobre una persona en varias tablas y relacionarlos entre sí y requerir transacciones, podría tener un solo documento JSON que almacene todo lo que sabe sobre esa persona en un solo campo. Por supuesto, trabajar lejos que los dominios se extienden es otro desafío de diseño, mejor realizado por desarrolladores no por DBA

usuario2127
fuente