Con SourceTable
tener> 15MM registros y Bad_Phrase
tener> 3K registros, la siguiente consulta tarda casi 10 horas en ejecutarse en SQL Server 2005 SP4.
UPDATE [SourceTable]
SET
Bad_Count=
(
SELECT
COUNT(*)
FROM Bad_Phrase
WHERE
[SourceTable].Name like '%'+Bad_Phrase.PHRASE+'%'
)
En inglés, esta consulta cuenta el número de frases distintas enumeradas en Bad_Phrase que son una subcadena del campo Name
en SourceTable
y luego coloca ese resultado en el campo Bad_Count
.
Quisiera algunas sugerencias sobre cómo hacer que esta consulta se ejecute considerablemente más rápido.
sql-server
sql-server-2005
update
subquery
Matthew Lehrer
fuente
fuente
Respuestas:
Aunque estoy de acuerdo con otros comentaristas que este es un problema computacionalmente caro, creo que hay mucho margen de mejora por ajustar el SQL que está utilizando. Para ilustrar esto, se crea un conjunto de datos falsos con nombres 15MM y 3K frases, corrió el enfoque de edad, y corrió un nuevo enfoque.
Script completo para generar un conjunto de datos falsos y probar el nuevo enfoque
TL; DR
En mi máquina y este conjunto de datos falsos, el enfoque original tarda aproximadamente 4 horas en ejecutarse. La propuesta de nuevo enfoque toma alrededor de 10 minutos , una mejora considerable. Aquí hay un breve resumen del enfoque propuesto:
enfoque original: análisis algorítmico
Del plan de la
UPDATE
declaración original , podemos ver que la cantidad de trabajo es linealmente proporcional tanto a la cantidad de nombres (15MM) como a la cantidad de frases (3K). Entonces, si multiplicamos el número de nombres y frases por 10, el tiempo de ejecución general será ~ 100 veces más lento.La consulta es realmente proporcional a la longitud de la
name
así; Si bien esto está un poco oculto en el plan de consulta, aparece en el "número de ejecuciones" para buscar en el carrete de la tabla. En el plan real, podemos ver que esto ocurre no solo una vez porname
, sino en realidad una vez por desplazamiento de carácter dentro dename
. Así que este enfoque es O (# names
*# phrases
*name length
) de la complejidad en tiempo de ejecución.Nuevo enfoque: Código
Este código también está disponible en el pleno Pastebin pero he copiado aquí por conveniencia. El Pastebin también tiene la definición del procedimiento completo, que incluye el
@minId
y@maxId
las variables que se ven a continuación para definir los límites del lote actual.Nuevo enfoque: los planes de consulta
Primero, generamos la subcadena comenzando en cada desplazamiento de caracteres
Luego cree un índice agrupado en estas subcadenas
Ahora, para cada frase incorrecta buscamos en estas subcadenas para identificar cualquier coincidencia. Luego calculamos el número de frases malas distintas que coinciden con una o más subcadenas de esa cadena. Este es realmente el paso clave; Debido a la forma en que hemos indexado las subcadenas, ya no tenemos que verificar un producto cruzado completo de frases y nombres malos. Este paso, que realiza el cálculo real, representa solo alrededor del 10% del tiempo de ejecución real (el resto es el preprocesamiento de las subcadenas).
Por último, realice la declaración de actualización real, utilizando a
LEFT OUTER JOIN
para asignar un recuento de 0 a cualquier nombre para el que no encontramos frases malas.Nuevo enfoque: análisis algorítmico
El nuevo enfoque se puede dividir en dos fases, preprocesamiento y coincidencia. Definamos las siguientes variables:
N
= # De nombresB
= # de frases malasL
= longitud promedio del nombre, en caracteresLa fase de pre-procesamiento es
O(N*L * LOG(N*L))
con el fin de crearN*L
subseries y luego ordenarlos.El juego real es
O(B * LOG(N*L))
con el fin de buscar en las subseries para cada frase mal.De esta manera, hemos creado un algoritmo que no se escala linealmente con el número de frases malas, un desbloqueo clave del rendimiento a medida que escalamos a frases de 3K y más. Dicho de otra manera, la implementación original demora aproximadamente 10 veces siempre que pasemos de 300 frases malas a 3K frases malas. Del mismo modo, tomaría otros 10 veces más si pasáramos de 3K frases malas a 30K. La nueva implementación, sin embargo, se ampliará de forma sub-lineal y, de hecho, toma menos del doble del tiempo medido en 3K frases malas cuando se escala hasta 30K frases malas.
Supuestos / Advertencias
SORT
en las subcadenas sea independiente para cada lote y quepa fácilmente en la memoria. Puede manipular el tamaño del lote según sea necesario, pero no sería aconsejable probar todas las filas de 15MM en un lote.Publicación de blog relacionada
Aaron Bertrand explora este tipo de solución con más detalle en su publicación de blog. Una forma de obtener un índice para buscar un% comodín líder .
fuente
Vamos a dejar de lado la cuestión obvia criado por Aaron Bertrand en los comentarios de un segundo:
El hecho de que su subconsulta utiliza los comodines en ambos lados afecta dramáticamente sargability . Para tomar una cita de esa publicación de blog:
Cambie la palabra "tuerca" por cada "mala palabra" y "Producto" para
SourceTable
, luego combine eso con el comentario de Aaron y debería comenzar a ver por qué es extremadamente difícil (lectura imposible) hacer que se ejecute rápidamente utilizando su algoritmo actual.Veo algunas opciones:
Dependiendo de sus requisitos, recomendaría la opción 3 o 4.
fuente
primero eso es solo una actualización extraña
Como '%' + [Bad_Phrase]. [PHRASE] te está matando
Eso no puede usar un índice
El diseño de los datos no es óptimo para la velocidad.
¿Puede dividir [Bad_Phrase]. [PHRASE] en una (s) frase (s) / palabra?
Si la misma frase / palabra aparece más de una, puede ingresarla más de una vez si desea que tenga un recuento más alto.
Entonces, el número de filas en mala frase aumentaría.
Si puede, esto será mucho más rápido.
No estoy seguro si 2005 lo admite, pero el índice de texto completo y el uso contiene
fuente