Registros duplicados devueltos de la tabla sin duplicados


8

Tengo un procedimiento almacenado que consulta una tabla de cola ocupada que se utiliza para distribuir el trabajo en nuestro sistema. La tabla en cuestión tiene una clave principal en WorkID y no tiene duplicados.

Una versión simplificada de la consulta es:

INSERT INTO #TempWorkIDs (WorkID)
SELECT
        W.WorkID

    FROM
        dbo.WorkTable W

    WHERE
        (@bool_param = 0 AND
        ((W.InProgress = 0
         AND ISNULL(W.UserID, -1) != @userid_param
         AND (@bool_filtered = 0
              OR W.TypeID IN (SELECT TypeID FROM #Types AS t)))
         OR 
         (@bool_param = 1
          AND W.InProgress = 1
          AND W.UserID != @userid_param)
        OR
        (@Auto_Param = 0
         AND W.UserID = @userid_param)))
         OR
         (@bool_param = 1 AND W.UserID = @userid_param)
    OPTION
        (RECOMPILE)

La #Typestabla se rellena anteriormente en el procedimiento.

Como dije, WorkTableestá ocupado y, a veces, mientras se ejecuta esta consulta, SUSPECTO que uno de los registros se mueve de un conjunto de filtros en el WHEREotro. Específicamente, esto sucede cuando alguien comienza a trabajar en un elemento y los W.InProgresscambios de 0 a 1. Cuando esto sucede, obtengo una violación de clave duplicada cuando intento agregar una clave principal a la tabla temporal en la que se inserta esta consulta.

He confirmado en el plan de consulta generado cuando ocurre el error que no hay paralelismo, el nivel de aislamiento es READ COMMITTED, y no hay registros duplicados en la tabla de origen. También puede ver que no hay JOINotra forma de obtener productos cartesianos aquí.

Este es el plan de consulta anónimo:

ingrese la descripción de la imagen aquí

La pregunta es, ¿qué está causando los duplicados y cómo puedo hacer que se detenga?

Creo que READ COMMITTEDdebería funcionar aquí, necesito un bloqueo. Estoy casi seguro de que los engaños ocurren cuando el InProgressbit en un registro cambia mientras estoy consultando. Sé esto porque la tabla almacena el tiempo de ese cambio y está dentro de milisegundos de cuando consulto y obtengo el error.

Respuestas:


9

Hay algunos escenarios difíciles que pueden hacer que la misma fila se lea dos veces desde un índice, incluso bajo el READ COMMITTEDnivel de aislamiento .

Su consulta no califica para una exploración de orden de asignación, por lo que el motor de almacenamiento leerá los datos de la tabla en el orden de la clave agrupada.

Para su tabla, tiene InProgresscomo la primera columna de la clave agrupada. Es probable que obtenga bloqueos de fila o página a medida que escanea a través de la tabla. Si lee una fila cerca del inicio del escaneo, libere el bloqueo, esa fila se actualiza de modo que InProgresscambie de 0 a 1, y luego la fila se lee nuevamente en una página diferente, entonces podría ver WorkIDvalores duplicados de su consulta .

Hay muchas soluciones alternativas. Puede insertar en un montón y simplemente eliminar valores duplicados. Puede agregar un DISTINCTa la consulta. También puede habilitar un nivel de aislamiento de versiones de fila, para proporcionar una vista estable del estado comprometido de la base de datos, ya sea al comienzo de la transacción ( aislamiento de instantánea ) o como al principio de la instrucción ( leer el aislamiento de instantánea comprometida )

Quizás sea apropiado agregar sugerencias de bloqueo o cambiar la estructura de la tabla. Para una solución bastante divertida (probablemente no apropiada para la producción), puede intentar leer el índice al revés. Esto se puede hacer con un superfluo TOPjunto con un ORDER BY. A continuación se muestra una demostración muy simple para ilustrar el punto:

CREATE TABLE #WorkTable (
    InProgress TINYINT NOT NULL,
    WorkID INT NOT NULL
    , PRIMARY KEY (InProgress, WorkID)
);

INSERT INTO #WorkTable WITH (TABLOCK)
SELECT (RN - 1) / 5000, RN
FROM
(
    SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t
OPTION (MAXDOP 1);

La siguiente consulta tiene la propiedad Ordered: false pero aún así leerá los datos en orden de clave en clúster:

SELECT WorkId
FROM #WorkTable;

Sin embargo, la siguiente consulta leerá los datos en orden agrupado inverso:

SELECT TOP (9223372036854775807) WorkId
FROM #WorkTable
ORDER BY InProgress DESC, WorkId DESC;

Podemos ver esto mirando las propiedades de escaneo:

escaneo hacia atrás

Para su tabla, esto significa que si se actualiza una fila de modo que InProgresscambie de 0 a 1, será mucho menos probable que aparezca dos veces. Es posible que no aparezca en absoluto, lo que podría ser un problema diferente.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.