Si detengo una consulta de larga duración, ¿se revierte?
-
03-07-2019 - |
Pregunta
Una consulta que se utiliza para recorrer 17 millones de registros para eliminar duplicados se ha estado ejecutando durante aproximadamente 16 horas y quería saber si la consulta se detuvo correctamente. ¿ahora si finalizará las instrucciones de eliminación o si se ha eliminado al ejecutar esta consulta? De hecho, si lo detengo, ¿finaliza las eliminaciones o las reversiones?
He encontrado que cuando hago un
select count(*) from myTable
Que las filas que devuelve (mientras realiza esta consulta) es aproximadamente 5 menos que lo que era el conteo de la fila inicial. Obviamente, los recursos del servidor son extremadamente pobres, por lo que significa que este proceso ha tardado 16 horas en encontrar 5 duplicados (cuando en realidad hay miles), y esto podría durar varios días.
Esta consulta tardó 6 segundos en 2000 filas de datos de prueba, y funciona muy bien en ese conjunto de datos, por lo que pensé que tomaría 15 horas para el conjunto completo.
¿Alguna idea?
A continuación se muestra la consulta:
--Declare the looping variable
DECLARE @LoopVar char(10)
DECLARE
--Set private variables that will be used throughout
@long DECIMAL,
@lat DECIMAL,
@phoneNumber char(10),
@businessname varchar(64),
@winner char(10)
SET @LoopVar = (SELECT MIN(RecordID) FROM MyTable)
WHILE @LoopVar is not null
BEGIN
--initialize the private variables (essentially this is a .ctor)
SELECT
@long = null,
@lat = null,
@businessname = null,
@phoneNumber = null,
@winner = null
-- load data from the row declared when setting @LoopVar
SELECT
@long = longitude,
@lat = latitude,
@businessname = BusinessName,
@phoneNumber = Phone
FROM MyTable
WHERE RecordID = @LoopVar
--find the winning row with that data. The winning row means
SELECT top 1 @Winner = RecordID
FROM MyTable
WHERE @long = longitude
AND @lat = latitude
AND @businessname = BusinessName
AND @phoneNumber = Phone
ORDER BY
CASE WHEN webAddress is not null THEN 1 ELSE 2 END,
CASE WHEN caption1 is not null THEN 1 ELSE 2 END,
CASE WHEN caption2 is not null THEN 1 ELSE 2 END,
RecordID
--delete any losers.
DELETE FROM MyTable
WHERE @long = longitude
AND @lat = latitude
AND @businessname = BusinessName
AND @phoneNumber = Phone
AND @winner != RecordID
-- prep the next loop value to go ahead and perform the next duplicate query.
SET @LoopVar = (SELECT MIN(RecordID)
FROM MyTable
WHERE @LoopVar < RecordID)
END
Solución
no, el servidor SQL no revertirá las eliminaciones que ya ha realizado si detiene la ejecución de la consulta. Oracle requiere una confirmación explícita de consultas de acción o los datos se retrotraen, pero no mssql.
con el servidor sql no se retrotraerá a menos que esté ejecutándose específicamente en el contexto de una transacción y restituya la transacción, o la conexión se cierre sin que la transacción se haya confirmado. pero no veo un contexto de transacción en su consulta anterior.
También puedes intentar reestructurar tu consulta para hacer que las eliminaciones sean un poco más eficientes, pero básicamente si las especificaciones de tu caja no están a la altura, entonces podrías quedarte esperando.
en el futuro, debe crear un índice único en la tabla para evitar tener que pasar por esto nuevamente.
Otros consejos
Su consulta no está envuelta en una transacción, por lo que no revertirá los cambios ya realizados por las declaraciones de eliminación individuales.
Lo probé específicamente yo mismo en mi propio servidor SQL usando la siguiente consulta, y la tabla ApplicationLog estaba vacía a pesar de que cancelé la consulta:
declare @count int
select @count = 5
WHILE @count > 0
BEGIN
print @count
delete from applicationlog;
waitfor time '20:00';
select @count = @count -1
END
Sin embargo, es probable que su consulta tarde muchos días o semanas, mucho más que 15 horas. Su estimación de que puede procesar 2000 registros cada 6 segundos es incorrecta porque cada iteración en su bucle while demorará significativamente más con 17 millones de filas que con 2000 filas. Por lo tanto, a menos que su consulta tarde significativamente menos de un segundo para 2000 filas, tomará días para los 17 millones.
Debes hacer una nueva pregunta sobre cómo puedes eliminar filas duplicadas de manera eficiente.
Si no hace nada explícito acerca de las transacciones, la conexión será en Autocommitar transacciones modo. En este modo, cada declaración SQL se considera una transacción.
La pregunta es si esto significa que las sentencias SQL individuales son transacciones y, por lo tanto, se confirman a medida que avanza, o si el bucle WHILE externo cuenta como una transacción.
No parece haber ninguna discusión de esto en la descripción de la construcción WHILE en MSDN . Sin embargo, dado que una declaración WHILE no puede modificar directamente la base de datos, parece lógico que no inicie una transacción de confirmación automática.
Transacciones implícitas
Si no se ha establecido 'Transacciones implícitas', cada iteración en su ciclo confirmó los cambios.
Es posible que cualquier servidor SQL se configure con 'Transacciones implícitas'. Esta es una configuración de base de datos (por defecto está desactivada). También puede tener transacciones implícitas en las propiedades de una consulta en particular dentro de Management Studio (haga clic con el botón derecho en el panel de consulta > opciones), por la configuración predeterminada en el cliente o una declaración SET.
SET IMPLICIT_TRANSACTIONS ON;
De cualquier manera, si este fuera el caso, aún tendría que ejecutar un COMMIT / ROLLBACK explícito independientemente de la interrupción de la ejecución de la consulta.
Referencia de transacciones implícitas:
Heredé un sistema que tenía una lógica similar a la suya implementada en SQL. En nuestro caso, intentamos unir filas usando una coincidencia aproximada que tenía nombres / direcciones similares, etc., y esa lógica se hizo únicamente en SQL. En el momento en que lo heredé teníamos alrededor de 300,000 filas en la tabla y, de acuerdo con los tiempos, calculamos que tomaría A YEAR para que coincidiera con todas.
Como experimento para ver cuánto más rápido podía hacerlo fuera de SQL, escribí un programa para volcar la tabla db en archivos sin formato, leer los archivos sin formato en un programa en C ++, crear mis propios índices y hacer el borroso. lógica allí, luego reimportar los archivos planos en la base de datos. Lo que tomó A YEAR en SQL tomó aproximadamente 30 segundos en la aplicación C ++.
Por lo tanto, mi consejo es que ni siquiera intentes lo que estás haciendo en SQL. Exportar, procesar, reimportar.
Los BORRADOS realizados hasta este momento no se revertirán.
Como el autor original del código en cuestión , y Habiendo emitido la advertencia de que el rendimiento dependerá de los índices, propondría los siguientes elementos para acelerar esto.
RecordId mejor ser la clave primaria. No me refiero a IDENTIDAD, me refiero a CLAVE PRIMARIA. Confirma esto usando sp_help
Se debe usar algún índice para evaluar esta consulta. Averigua cuál de estas cuatro columnas tiene menos repeticiones e índice que ...
SELECT *
FROM MyTable
WHERE @long = longitude
AND @lat = latitude
AND @businessname = BusinessName
AND @phoneNumber = Phone
Antes y después de agregar este índice, verifique el plan de consulta para ver si se ha agregado la exploración del índice.
Como un bucle, su consulta tendrá dificultades para escalar bien, incluso con los índices adecuados. La consulta se debe volver a escribir en una sola declaración, según las sugerencias en su pregunta anterior en esto.
Si no lo está ejecutando explícitamente dentro de una transacción, solo revertirá la instrucción de ejecución.
Creo que esta consulta sería mucho más eficiente si se reescribiera usando un algoritmo de un solo paso usando un cursor. Ordenaría la tabla de cursor por longitud, latitud, nombre de negocio y @ número de teléfono. Pasarías por las filas una a la vez. Si una fila tiene la misma longitud, latitud, nombre comercial y número de teléfono que la fila anterior, elimínela.
Creo que necesitas considerar seriamente tu metodología. Debe comenzar a pensar en conjuntos (aunque para el rendimiento puede necesitar procesamiento por lotes, pero no fila por fila en una tabla de registro de 17 millones).
Primero, ¿todos sus registros tienen duplicados? Sospecho que no, por lo que lo primero que debe hacer es limitar su procesamiento a solo aquellos registros que tienen duplicados. Debido a que esta es una tabla grande y es posible que tenga que hacer las eliminaciones en lotes a lo largo del tiempo, dependiendo de qué otro procesamiento se esté realizando, primero arrastre los registros que desea tratar en una tabla propia que luego indexará. También puede usar una tabla temporal si va a poder hacer todo esto al mismo tiempo sin detenerlo de otra manera, cree una tabla en su base de datos y suéltela al final.
Algo así (Nota: no escribí las declaraciones de índice de creación, imagino que puedes buscarlo tú mismo):
SELECT min(m.RecordID), m.longitude, m.latitude, m.businessname, m.phone
into #RecordsToKeep
FROM MyTable m
join
(select longitude, latitude, businessname, phone
from MyTable
group by longitude, latitude, businessname, phone
having count(*) >1) a
on a.longitude = m.longitude and a.latitude = m.latitude and
a.businessname = b.businessname and a.phone = b.phone
group by m.longitude, m.latitude, m.businessname, m.phone
ORDER BY CASE WHEN m.webAddress is not null THEN 1 ELSE 2 END,
CASE WHEN m.caption1 is not null THEN 1 ELSE 2 END,
CASE WHEN m.caption2 is not null THEN 1 ELSE 2 END
while (select count(*) from #RecordsToKeep) > 0
begin
select top 1000 *
into #Batch
from #RecordsToKeep
Delete m
from mytable m
join #Batch b
on b.longitude = m.longitude and b.latitude = m.latitude and
b.businessname = b.businessname and b.phone = b.phone
where r.recordid <> b.recordID
Delete r
from #RecordsToKeep r
join #Batch b on r.recordid = b.recordid
end
Delete m
from mytable m
join #RecordsToKeep r
on r.longitude = m.longitude and r.latitude = m.latitude and
r.businessname = b.businessname and r.phone = b.phone
where r.recordid <> m.recordID
También intente pensar en otro método para eliminar filas duplicadas:
delete t1 from table1 as t1 where exists (
select * from table1 as t2 where
t1.column1=t2.column1 and
t1.column2=t2.column2 and
t1.column3=t2.column3 and
--add other colums if any
t1.id>t2.id
)
Supongo que tienes una columna de ID de entero en tu tabla.
Si su máquina no tiene un hardware muy avanzado, puede tardar mucho tiempo en completar el comando. No sé con certeza cómo se realiza esta operación bajo el capó, pero según mi experiencia, esto podría hacerse de manera más eficiente al sacar los registros de la base de datos y guardarlos en la memoria para un programa que utiliza una estructura de árbol con una regla de eliminación de duplicados. para la inserción. Intente leer la totalidad de la tabla en chuncks (digamos 10000 filas a la vez) en un programa de C ++ usando ODBC. Una vez en el programa C ++, use std :: map donde key es la clave única y struct es una estructura que contiene el resto de los datos en variables. Recorra todos los registros y realice la inserción en el mapa. La función de inserción de mapa se encargará de eliminar los duplicados. Dado que la búsqueda dentro de un mapa es tiempo de lg (n), mucho menos tiempo para encontrar duplicados que usar su bucle while. Luego puede eliminar toda la tabla y agregar las tuplas a la base de datos desde el mapa formando consultas de inserción y ejecutándolas a través de odbc o creando un script de archivo de texto y ejecutándolo en el estudio de administración.
Estoy bastante seguro de que es un negativo. De lo contrario, ¿cuál sería el punto de las transacciones?