¿Buenas razones para usar SELECT ... WITH XLOCK?

11

Me enfrento a algunos puntos muertos recurrentes, uno de los cuales es un bloqueo de teclas y contiene una consulta SELECT con una sugerencia de XLOCK que se convierte en la víctima del bloqueo. La otra instrucción es un INSERT en una de las tablas que forma parte de la vista de la primera consulta.

Ver:

create view dbo.viewE
 as
    select * from dbo.E  
    where myValue > 13000 

Seleccionar consulta:

select * from dbo.viewE with (XLOCK) where A > GETUTCDATE() 

INSERTAR declaración:

INSERT INTO [dbo].[E] (myValue,A) VALUES (10,GetDate())

La tabla subyacente dbo.E contiene aproximadamente 3 millones de filas en aproximadamente 20 columnas, algunas de ellas son ntext.

Sacando las consultas y simulándolas manualmente con dos transacciones, el comportamiento es reproducible. El comportamiento cambia si XLOCK se elimina de la selección.

Gráfico de punto muerto:

<deadlock-list>
 <deadlock victim="process222222221">
  <process-list>
   <process id="process222222221" taskpriority="0" logused="0" waitresource="KEY: 5:72057604035644444 (ccdf51accc0c)" waittime="2522" ownerId="27202256401" transactionname="SELECT" lasttranstarted="2015-09-14T16:32:36.160" XDES="0x2f1ec5ca0" lockMode="RangeX-X" schedulerid="15" kpid="12936" status="suspended" spid="359" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2015-09-14T16:32:36.160" lastbatchcompleted="2015-09-14T16:32:36.160" clientapp="x" hostname="x" hostpid="14536" loginname="x" isolationlevel="serializable (4)" xactid="27202256401" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="48" sqlhandle="0x02000000611e4523142b2318c47c87313a9b2ba587ff3130">
        SELECT * FROM viewE WITH (XLOCK) WHERE A &lt; GetUtcDate()      </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
(@UICulture nvarchar(5))SELECT * FROM viewE WITH (XLOCK) WHERE A &lt; GetUtcDate()    </inputbuf>
   </process>
   <process id="process6022222" taskpriority="0" logused="161152" waitresource="KEY: 5:72057604035644444 (cd874c2ba438)" waittime="1370" ownerId="27202248438" transactionguid="0x8de5ccd6eeef67469c6234af59e44ca5" transactionname="DTCXact" lasttranstarted="2015-09-14T16:32:34.767" XDES="0x4aa0bf950" lockMode="RangeI-N" schedulerid="14" kpid="6636" status="suspended" spid="329" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-09-14T16:32:37.300" lastbatchcompleted="2015-09-14T16:32:37.300" clientapp="x" hostname="x" hostpid="14536" loginname="x" isolationlevel="read uncommitted (1)" xactid="27202248438" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="936" sqlhandle="0x020000004853462f09790a4ddedc0d574c2afa539aef1c0e">
     INSERT INTO [E] ([a], [b], [c],...) VALUES (@aDate, @bDate, @c, ...)
     </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>INSERT INTO [E] ([a], [b], [c],...) VALUES (@aDate, @bDate, @c, ...)
    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <keylock hobtid="72057604035644444" dbid="5" objectname="db.dbo.E" indexname="IX_index1" id="lock258b6dc80" mode="X" associatedObjectId="72057604035644444">
    <owner-list>
     <owner id="process6022222" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process222222221" mode="RangeX-X" requestType="wait"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057604035644444" dbid="5" objectname="db.dbo.E" indexname="IX_index1" id="lock7b145c400" mode="RangeX-X" associatedObjectId="72057604035644444">
    <owner-list>
     <owner id="process222222221" mode="RangeX-X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process6022222" mode="RangeI-N" requestType="wait"/>
    </waiter-list>
   </keylock>
  </resource-list>
 </deadlock>
</deadlock-list>

Hasta donde entiendo esto, estoy viendo un punto muerto KEYLOCK causado básicamente por una consulta de índice descubierta que usa un índice no agrupado y un índice agrupado para recopilar los valores requeridos, ¿verdad?

Mis preguntas:

  1. No puedo crear un índice de cobertura debido a las columnas NTEXT requeridas involucradas. ¿Reducirá drásticamente la cantidad de filas ayudará aquí?
  2. ¿Hay alguna buena razón por la que simplemente no sé que SELECT se ejecuta con XLOCK? ¿El estancamiento también ocurriría sin XLOCK?
Magier
fuente

Respuestas:

15

Hasta donde entiendo esto, estoy viendo un punto muerto KEYLOCK causado básicamente por una consulta de índice descubierta que usa un índice no agrupado y un índice agrupado para recopilar los valores requeridos, ¿verdad?

Básicamente sí. La operación de lectura (select) accede primero al índice no agrupado, luego al índice agrupado (búsqueda). La operación de escritura (inserción) accede primero al índice agrupado, luego al índice no agrupado. Acceder a los mismos recursos en un orden diferente con bloqueos incompatibles puede llevar a un punto muerto.

¿Reducirá drásticamente la cantidad de filas ayudará aquí?

Se podría , debido a que menos recursos están cerradas y la operación tenderá a completar más rápidamente. Si ayuda, puede reducir los puntos muertos, pero lo más probable es que no los elimine (pero siga leyendo).

¿Hay alguna buena razón por la que simplemente no sé que SELECT se ejecuta con XLOCK?

Realmente no. Las sugerencias de bloqueo como esta a menudo son introducidas por personas sin una comprensión completa de cómo funcionan el aislamiento, el bloqueo y los puntos muertos, en un intento desesperado por reducir o eliminar un problema.

¿El estancamiento también ocurriría sin XLOCK?

No , si la selección realmente se ejecuta en el aislamiento de lectura no confirmada porque los bloqueos incompatibles no se tomarán (y retendrán) en un orden diferente.

, si se usa un nivel de aislamiento de bloqueo , y los bloqueos incompatibles se toman y mantienen en un orden inconsistente, por ejemplo, compartido (S) en el no agrupado, luego S en el agrupado al leer. La probabilidad de un punto muerto en este escenario depende de cuántos bloqueos se tomen y por cuánto tiempo se mantengan.

Consejo

Lo que realmente se destaca (en revisión) es que la transacción seleccionada se ejecuta bajo aislamiento serializable . Su marco podría establecerlo, o debido al uso del DTC (Coordinador de transacciones distribuidas): consulte nombre de transacción = "DTCXact" en el gráfico de punto muerto. Debe investigar las razones de esto y buscar cambiarlo si es posible.

Sin esta escalada a serializable, hay muchas posibilidades de que este punto muerto no ocurra, suponiendo que XLOCKse elimine la pista. Dicho esto, estarías leyendo bajo lectura sin compromiso , lo que viene con muy pocas garantías de consistencia.

Si su aplicación y el código de SQL Server pueden tolerar la lectura de versiones de filas, cambiar a lectura de aislamiento de instantáneas comprometidas (RCSI) o aislamiento de instantáneas (SI) para las lecturas también evitaría el punto muerto ( XLOCKeliminado), al tiempo que presenta un punto coherente -Vista en tiempo de los datos comprometidos. Esto también supone que puede evitar el aislamiento serializable, por supuesto.

En última instancia, la XLOCKsugerencia es contraproducente, pero realmente debe analizar el motivo del uso del nivel de aislamiento serializable. Esto trancount = 2también es interesante: quizás esté anidando involuntariamente transacciones aquí. Algo más para verificar.

Paul White 9
fuente
2
  1. Reducir drásticamente el número de filas reducirá la probabilidad de obtener el punto muerto, pero no desaparecerá por completo.

En términos simples, la selección utiliza primero el índice para determinar las filas para seleccionar, luego busca las filas, mientras la inserción inserta una fila, luego intenta actualizar el índice (BLOQUEADO).

  1. Los desarrolladores de aplicaciones tienden a usar XLOCK si en la misma transacción desean hacer una actualización posterior de los datos. Esto garantiza que nadie pueda actualizar los datos debajo de ellos. Investigaría qué está haciendo la aplicación para ver si se requiere el XLOCK.

Dicho esto, quitar el XLOCK probablemente no resolverá el problema. El SELECT aún eliminará un bloqueo compartido en el índice, y el INSERTAR querrá un XLOCK para actualizarlo. Un bloqueo compartido y un XLOCK no pueden existir en el objeto juntos, por lo que aún obtendrá un punto muerto. IX_Index1 debe ser MyValue o A, o ambos.

Este tipo de punto muerto a menudo ocurre debido a índices mal diseñados y / o demasiados índices. O código mal escrito. Su mejor opción es ver si hay alguna forma de que la selección se pueda volver a escribir para usar otro índice.

Leo Miller
fuente