Recientemente hemos tratado un incidente con un cliente que tenía problemas de bloqueos en SQL Server. Me gustaría explicar el caso de este cliente — simplificando el problema, y aprovechar la oportunidad que los seguidores de nuestros blogs, entendáis algunos aspectos importantes de SQL Server, a saber, gestión de bloqueos, planes de ejecución, e indexación.

El escenario

El cliente tiene las siguientes dos consultas:

Consulta1:

UPDATE [Sales].[SalesOrderDetail]
SET UnitPrice+=1
WHERE SalesOrderID = 43659

Consulta2:

SELECT COUNT(*) FROM [Sales].[SalesOrderDetail]
WHERE SalesOrderID = 43659

Las dos consultas intentan tratar datos del mismo pedido: uno intenta modificar el precio unitario, y la otra intenta contar cuantas líneas de pedido existen.

A nivel de planes de ejecución tenemos lo siguiente:

Consulta1:

captura1-UPDATE

Consulta2:

captura1-SELECT

 

La tabla en cuestión tiene los siguientes índices:

captura1-INDICES

Con ello, si lanza la primera instrucción en contexto de transacción, la segunda consulta, no podrá resolverse porque no podrá “pasar” por el índice CLUSTERED (PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID). Para comprobarlo puede utilizar el comando SP_LOCK, que le muestra los bloqueos adquiridos:

captura1-LOCKS

Donde puede apreciar (en color amarillo), como la conexión 54 intenta adquirir un bloqueo de lectura (tipo Shared – S), que no puede adquirir (estado WAIT) porque otra conexión (la 56) tiene un bloqueo (tipo Exclusivo – X) sobre la misma clave (0ca7b7436f59).

Para solucionar esa situación, debe proporcionar un camino alternativo a la consulta que quiere leer, para que no tenga que pasar sobre ese registro bloqueado.

Si vuelve a la consulta SELECT notará que sólo necesita contar cuantos registros cumplen la condición. Por otra parte, la consulta de actualización, necesita bloquear las claves correspondientes del índice CLUSTERED para proceder a hacer la modificación (el UPDATE).

Con ello, cualquiera de los otros índices de la tabla podría servir para satisfacer el SELECT COUNT(*). Para ello podemos contar con los hints de índices, e indicar cuál es el índice que deseas utilizar.

Por ejemplo, si cambiamos la instrucción como sigue:

SELECT COUNT(*) FROM [Sales].[SalesOrderDetail]
WITH (INDEX = [IX_SalesOrderDetail_ProductID])
WHERE SalesOrderID = 43659

A pesar de seguir el UPDATE en contexto de transacción la consulta se resolverá. La razón es porque estará utilizando un índice que no está bloqueando el UPDATE.

Es decir, hemos forzado un índice, para que evite pasar por el índice CLUSTERED. Para este caso, nos salva la situación pero deberá tener cuidado porque este índice incurrirá en un número mayor de lecturas.

Si ejecutamos las dos instrucciones juntas, podrá ver como SSMS nos indica que el índice forzado es ineficiente:

captura1-PLANES_EJECUCION

Y por otro lado, si muestras las estadísticas de E/S, podrás ver que usando el índice CLUSTERED se leen 3 páginas frente a las más de 300 de índice forzado.

captura1-ESTADISTICAS-LECTURAS

Alternativamente podría crear un índice específico para esa query, que realizaría el mismo número de lecturas:

CREATE NONCLUSTERED INDEX nci_test
ON [Sales].[SalesOrderDetail]
(SalesOrderID )

Pero considere que necesitaría mantener también ese índice, con el coste que ello conllevaría. De hecho, el índice creado, lo considera el optimizador automáticamente para resolver la consulta sin necesidad de introducir el hint.

Conclusión:

  • Para gestionar bien los bloqueos, debe entender qué recursos se bloquean, y cuál es la causa por la que otras conexiones no se pueden resolver sus consultas.
  • Forzar índices puede ayudar a mejorar la gestión de bloqueos.
  • Crear índices de apoyo (cobertura) suele ayudar a solventar estas situaciones.
0 Shares:
2 comments
  1. Tengo una incógnita (o varias) en relación a cómo resolver una concurrencia. Programo en VB6 y cuando ocurre una concurrencia tengo un Resume Next que supera el error. Pero es posible un procedimiento que de alguna forma evite el error de concurrencia? Por otra parte, cómo resuelven las concurrencias los sistemas cuando se trata de 10, 20 o más usuarios entrando al mismo tiempo a un registro?

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

You May Also Like
Leer más

NOEXPAND y las vistas indizadas

Optimizar vistas indexes NOEXPAND. No siempre el optimizador de consultas de SQL tiene toda la información necesaria para generar el mejor plan de optimización y a veces hay que ayudarle, en este caso los desarrolladores de Navision han utilizado la siguiente opción para salvaguardarse.