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
¿Fue útil?

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:

http://msdn.microsoft.com/en-us/library /ms188317.aspx

http://msdn.microsoft.com/en-us/library /ms190230.aspx

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?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top