El estado de actualización de SQL lleva mucho tiempo / alto uso del disco durante horas

8

Sí, parece un problema muy genérico, pero todavía no he podido reducirlo demasiado.

Entonces tengo una instrucción UPDATE en un archivo por lotes sql:

UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID

B tiene 40k registros, A tiene 4M registros y están relacionados de 1 a n a través de A.B_ID, aunque no hay FK entre los dos.

Básicamente, estoy precalculando un campo para fines de minería de datos. Aunque cambié el nombre de las tablas para esta pregunta, no cambié la declaración, es realmente así de simple.

Esto demora horas en ejecutarse, así que decidí cancelar todo. La base de datos se corrompió, así que la eliminé, restauré una copia de seguridad que hice justo antes de ejecutar la declaración y decidí entrar en detalles con un cursor:

DECLARE CursorB CURSOR FOR SELECT ID FROM B ORDER BY ID DESC -- Descending order
OPEN CursorB 
DECLARE @Id INT
FETCH NEXT FROM CursorB INTO @Id

WHILE @@FETCH_STATUS = 0
BEGIN
    DECLARE @Msg VARCHAR(50) = 'Updating A for B_ID=' + CONVERT(VARCHAR(10), @Id)
    RAISERROR(@Msg, 10, 1) WITH NOWAIT

    UPDATE A
    SET A.X = B.X
    FROM A JOIN B ON A.B_ID = B.ID
    WHERE B.ID = @Id

    FETCH NEXT FROM CursorB INTO @Id
END

Ahora puedo verlo ejecutándose con un mensaje con la identificación descendente. Lo que sucede es que toma alrededor de 5 minutos ir de id = 40k a id = 13

Y luego, en la identificación 13, por alguna razón, parece colgar. El DB no tiene ninguna conexión además del SSMS, pero en realidad no está colgado:

  • el disco duro se ejecuta continuamente, por lo que definitivamente está haciendo algo (verifiqué en Process Explorer que de hecho es el proceso sqlserver.exe que lo usa)
  • Ejecuté sp_who2, encontré el SPID (70) de la sesión SUSPENDIDA y luego ejecuté el siguiente script:

    seleccione * de sys.dm_exec_requests r únase a sys.dm_os_tasks t en r.session_id = t.session_id donde r.session_id = 70

Esto me da el wait_type, que es PAGEIOLATCH_SH la mayor parte del tiempo, pero en realidad cambia a WRITE_COMPLETION a veces, lo que supongo que sucede cuando está vaciando el registro

  • el archivo de registro, que era de 1.6 GB cuando restauré la base de datos (y cuando llegó a la identificación 13), ahora es de 3.5 GB

Otra información quizás útil:

  • el número de registros en la tabla A para B_ID 13 no es grande (14)
  • Mi colega no tiene el mismo problema en su máquina, con una copia de este DB (de hace un par de meses) con la misma estructura.
  • la tabla A es, con mucho, la tabla más grande en el DB
  • Tiene varios índices y varias vistas indizadas lo usan.
  • No hay otro usuario en la base de datos, es local y ninguna aplicación lo está utilizando.
  • El archivo LDF no tiene un tamaño limitado.
  • El modelo de recuperación es SIMPLE, el nivel de compatibilidad es 100
  • Procmon no me da mucha información: sqlserver.exe está leyendo y escribiendo mucho de los archivos MDF y LDF.

Todavía estoy esperando que termine (han pasado 1h30) pero esperaba que tal vez alguien me diera alguna otra acción que pudiera intentar solucionar este problema.

Editado: agregar extracto del registro procmon

15:24:02.0506105    sqlservr.exe    1760    ReadFile    C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf  SUCCESS Offset: 5,498,732,544, Length: 8,192, I/O Flags: Non-cached, Priority: Normal
15:24:02.0874427    sqlservr.exe    1760    WriteFile   C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf  SUCCESS Offset: 6,225,805,312, Length: 16,384, I/O Flags: Non-cached, Write Through, Priority: Normal
15:24:02.0884897    sqlservr.exe    1760    WriteFile   C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA_1.LDF    SUCCESS Offset: 4,589,289,472, Length: 8,388,608, I/O Flags: Non-cached, Write Through, Priority: Normal

Al usar DBCC PAGE, parece estar leyendo y escribiendo en campos que se parecen a la tabla A (o uno de sus índices), pero para diferentes B_ID que 13. ¿Reconstruir índices tal vez?

Editado 2: plan de ejecución

Así que cancelé la consulta (en realidad eliminé la base de datos y sus archivos y luego la restauré), y verifiqué el plan de ejecución para:

UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
WHERE B.ID = 13

El plan de ejecución (estimado) es el mismo que para cualquier B.ID, y parece bastante sencillo. La cláusula WHERE usa una búsqueda de índice en un índice no agrupado de B, JOIN usa una búsqueda de índice agrupado en ambas PK de las tablas. La búsqueda de índice agrupado en A usa paralelismo (x7) y representa el 90% del tiempo de CPU.

Más importante aún, la ejecución real de la consulta con ID 13 es inmediata.

Editado 3: fragmentación del índice

La estructura de los índices es la siguiente:

B tiene una PK agrupada (no el campo ID) y un índice único no agrupado, cuyo primer campo es B.ID; este segundo índice parece usarse siempre.

A tiene una PK agrupada (campo no relacionado).

También hay 7 vistas en A (todas incluyen el campo AX), cada una con su propia PK agrupada y otro índice que también incluye el campo AX

Las vistas se filtran (con campos que no están en esta ecuación), por lo que dudo que haya alguna forma de que la ACTUALIZACIÓN A use las vistas mismas. Pero tienen un índice que incluye AX, por lo que cambiar AX significa escribir las 7 vistas y los 7 índices que tienen que incluyen el campo.

Aunque se espera que la ACTUALIZACIÓN sea más lenta para esto, no hay ninguna razón por la cual una identificación específica sea mucho más larga que las demás.

Verifiqué la fragmentación de todos los índices, todos estaban en <0.1%, excepto los índices secundarios de las vistas , todos entre 25% y 50%. Los factores de relleno para todos los índices parecen estar bien, entre 90% y 95%.

Reorganicé todos los índices secundarios y volví a ordenar mi script.

Todavía está colgado, pero en un punto diferente:

...
(0 row(s) affected)

        Updating A for B_ID=14

(4 row(s) affected)

Mientras que anteriormente, el registro de mensajes se veía así:

...
(0 row(s) affected)

        Updating A for B_ID=14

(4 row(s) affected)

        Updating A for B_ID=13

Esto es extraño, porque significa que ni siquiera está colgado en el mismo punto del WHILEbucle. El resto se ve igual: la misma línea de ACTUALIZACIÓN esperando en sp_who2, el mismo tipo de espera PAGEIOLATCH_EX y el mismo uso pesado de HD de sqlserver.exe.

El siguiente paso es eliminar todos los índices y vistas y recrearlos, creo.

Editado 4: eliminación y reconstrucción de índices

Entonces, eliminé todas las vistas indexadas que tenía en la tabla (7 de ellas, 2 índices por vista, incluida la agrupada). Ejecuté el script inicial (sin cursor), y en realidad se ejecutó en 5 minutos.

Entonces mi problema se origina en la existencia de estos índices.

Volví a crear mis índices después de ejecutar la actualización, y me tomó 16 minutos.

Ahora entiendo que los índices tardan en reconstruirse, y estoy bien con la tarea completa que toma 20 minutos.

Lo que todavía no entiendo es por qué cuando ejecuto la actualización sin eliminar los índices primero, lleva varias horas, pero cuando los elimino primero y luego los vuelvo a crear, demoran 20 minutos. ¿No debería tomar el mismo tiempo de cualquier manera?

GFK
fuente
1
¿Hay algo en el registro de errores de SQL Server? También desde procmon, ¿cuáles son las compensaciones en el archivo en el que está escribiendo? Puede dividir entre 8.192 para obtener la página y luego usar DBCC PAGEpara ver en qué se está escribiendo.
Martin Smith
3,5 GB parece la cantidad máxima de RAM que puede manejar una unidad de trabajo de Windows de 32 bits ... ¿peligro?
tschmit007
@MartinSmith No hay absolutamente nada desde que restauré en los registros de SSMS SQL Server y tampoco en el registro de eventos de Windows
GFK
¿Cómo son sus índices en la tabla A (qué columnas, etc.)? ¿Están fragmentados?
Stuart Ainsworth
@ tschmit007 Su SQL 2008 R2 x64 Dev Edition en Win Server 2008 R2 x64. Es una máquina virtual que se ejecuta en Hyper-V (el host también es 2008 R2 x64); la VM tiene 4,2 GB de memoria física utilizada de 5 GB y 4,6 GB de compromiso de 10 GB como máximo; el host tiene 7,2 GB de memoria física utilizada de 8 GB y 7,8 de 16 GB máx. Ambas máquinas son más lentas debido al uso de HD pero no están obstruidas.
GFK

Respuestas:

0
  1. Seguir con el comando ACTUALIZAR. CURSOR será más lento para lo que intentas hacer.
  2. Descarte / deshabilite todos los índices, incluidos los de las vistas indizadas. Si tiene una clave foránea en AX, suéltela.
  3. Cree un índice que contendrá solo A.B_ID y otro para B.ID.
  4. Aunque esté utilizando el modelo de recuperación simple, la última transacción siempre estará en el registro de transacciones antes de que se vacíe al disco. Es por eso que necesita pre-crecer su registro de transacciones y configurarlo para que crezca por una cantidad mayor (por ejemplo, 100 MB).
  5. Además, establezca el crecimiento del archivo de datos en una cantidad mayor.
  6. Asegúrese de tener suficiente espacio en disco para un mayor crecimiento de los archivos de registro y datos.
  7. Cuando finalice la actualización, vuelva a crear / habilitar los índices que eliminó / deshabilitó en el paso 2.
  8. Si ya no los necesita, elimine los índices creados en el paso 3.

Editar: como no puedo comentar tu publicación original, responderé aquí tu pregunta de Edición 4. Tienes 7 índices en AX Index es un árbol B , y cada actualización en ese campo hace que el árbol B se reequilibre. Es más rápido reconstruir esos índices desde cero que reequilibrarlo cada vez.

bojan
fuente
Para el punto 1, vea mi respuesta a ik_zelf. El cursor está allí por razones de investigación y no tiene tanto impacto. Voy a implementar el resto de sus sugerencias, creo que es todo lo que me queda por hacer. Si funciona, aún me quedaré sin una explicación de lo que sucede ahora ...
GFK
Puede publicar DDL para sus tablas (incluidos todos los índices, restricciones, etc.). Tal vez hay algo que ralentiza su rendimiento y se lo está perdiendo.
bojan
1
Caer índices / Actualizar / Reconstruir índices funciona, y aunque prefiero no tener que hacer algo tan drástico, no veo que tenga otra opción. ¡Gracias!
GFK
0

Una cosa a tener en cuenta son los recursos del sistema (memoria, disco, CPU) durante este proceso. Intenté insertar 7 millones de filas individuales en una sola tabla en un gran trabajo y mi servidor se colgó de manera similar a la suya.

Resulta que no tenía suficiente memoria en mi servidor para ejecutar este trabajo de inserción masiva. En situaciones como esta, a SQL le gusta conservar la memoria y no dejarla ir ... incluso después de que dicho comando de inserción se haya completado o no. Cuantos más comandos se procesan en trabajos grandes, más memoria se consume. Un reinicio rápido liberó dicha memoria.

Lo que haría es comenzar este proceso desde cero con el Administrador de tareas en ejecución. Si el uso de la memoria supera el 75%, las posibilidades de que su sistema / procesos se congelen se disparan astronómicamente.

Si su memoria / recursos son realmente limitados como se indicó anteriormente, sus opciones son cortar el proceso en partes más pequeñas (con el reinicio ocasional si el uso de memoria es alto) en lugar de un gran trabajo o actualizar a un servidor de 64 bits con mucha memoria.

Techie Joe
fuente
0

El escenario de actualización es siempre más rápido que usar un procedimiento.

Como está actualizando la columna X de todas las filas de la tabla A, asegúrese de colocar primero el índice en esa. También asegúrese de que no haya cosas como activadores y restricciones activas en esa columna.

La actualización de índices es un negocio costoso, al igual que validar restricciones y ejecutar disparadores de nivel de fila que realizan búsquedas en otros datos.

ik_zelf
fuente
No creo que ese sea el punto. Me doy cuenta de que la actualización de los registros indexados lleva tiempo, y sé que, en general, parte del tiempo que lleva se debe a esto. Pero espero esto, y estoy de acuerdo: como dije, actualizar el 99% de las líneas lleva 5 minutos (incluso usando el cursor), pero por alguna razón, una línea (y no siempre la misma) toma 5 horas. Lo que me preocupa es este comportamiento particular.
GFK
Locks no es un problema que dijiste ... ¿qué tal la utilización del sistema de archivos, que alcanza el 90% o más?
ik_zelf
no, son 31GB gratis de 120GB, así que creo que está bien
GFK
¿Qué sucede si intenta copiar la tabla como crear una tabla a_copy como select * from a;
ik_zelf