Esta pregunta es similar a la optimización de la búsqueda de rango de IP? pero ese está restringido a SQL Server 2000.
Supongamos que tengo 10 millones de rangos almacenados provisionalmente en una tabla estructurada y poblada como se muestra a continuación.
CREATE TABLE MyTable
(
Id INT IDENTITY PRIMARY KEY,
RangeFrom INT NOT NULL,
RangeTo INT NOT NULL,
CHECK (RangeTo > RangeFrom),
INDEX IX1 (RangeFrom,RangeTo),
INDEX IX2 (RangeTo,RangeFrom)
);
WITH RandomNumbers
AS (SELECT TOP 10000000 ABS(CRYPT_GEN_RANDOM(4)%100000000) AS Num
FROM sys.all_objects o1,
sys.all_objects o2,
sys.all_objects o3,
sys.all_objects o4)
INSERT INTO MyTable
(RangeFrom,
RangeTo)
SELECT Num,
Num + 1 + CRYPT_GEN_RANDOM(1)
FROM RandomNumbers
Necesito saber todos los rangos que contienen el valor 50,000,000
. Intento la siguiente consulta
SELECT *
FROM MyTable
WHERE 50000000 BETWEEN RangeFrom AND RangeTo
SQL Server muestra que hubo 10,951 lecturas lógicas y se leyeron casi 5 millones de filas para devolver las 12 coincidentes.
¿Puedo mejorar este rendimiento? Cualquier reestructuración de la tabla o índices adicionales está bien.
sql-server
optimization
Martin Smith
fuente
fuente
contains
consultas y, aunque funcionan bien para reducir la cantidad de datos leídos, parecen agregar otros sobrecarga que contrarresta esto.Respuestas:
Columnstore es muy útil aquí en comparación con un índice no agrupado que escanea la mitad de la tabla. Un índice de almacén de columnas no agrupado proporciona la mayor parte del beneficio, pero insertar datos ordenados en un índice de almacén de columnas agrupado es aún mejor.
Por diseño, puedo obtener la eliminación de grupos de filas en la
RangeFrom
columna, lo que eliminará la mitad de mis grupos de filas. Pero debido a la naturaleza de los datos, también obtengo la eliminación del grupo de filas en laRangeTo
columna:Para tablas más grandes con más datos variables, hay diferentes formas de cargar los datos para garantizar la mejor eliminación posible de grupos de filas en ambas columnas. Para sus datos en particular, la consulta toma 1 ms.
fuente
Paul White señaló una respuesta a una pregunta similar que contiene un enlace a un artículo interesante de Itzik Ben Gan . Esto describe el modelo de "Árbol de intervalo relacional estático" que permite que esto se haga de manera eficiente.
En resumen, este enfoque implica almacenar un valor calculado ("forknode") basado en los valores de intervalo en la fila. Al buscar rangos que se cruzan con otro rango, es posible calcular previamente los posibles valores de forknode que deben tener las filas coincidentes y usar esto para encontrar los resultados con un máximo de 31 operaciones de búsqueda (lo siguiente admite números enteros en el rango 0 al máximo 32 con signo poco int)
En base a esto, reestructuré la tabla de la siguiente manera.
Y luego usó la siguiente consulta (el artículo busca intervalos de intersección, por lo que encontrar un intervalo que contenga un punto es un caso degenerado de esto)
Esto normalmente se ejecuta en
1ms
mi máquina cuando todas las páginas están en caché, con estadísticas de E / S.y plan
NB: La fuente utiliza TVF de varias etapas en lugar de un CTE recursivo para lograr que los nodos se unan, pero en aras de que mi respuesta sea autónoma, he optado por este último. Para uso en producción, probablemente usaría los TVF.
fuente
Pude encontrar un enfoque de modo de fila que es competitivo con el enfoque N / CCI, pero necesita saber algo sobre sus datos. Suponga que tiene una columna que contiene la diferencia de
RangeFrom
yRangeTo
y la indexa junto conRangeFrom
:Si conocía todos los valores distintos de
DiffOfColumns
entonces, podría realizar una búsqueda de cada valorDiffOfColumns
con un filtro de rango activadoRangeTo
para obtener todos los datos relevantes. Por ejemplo, si sabemos queDiffOfColumns
= 2, entonces los únicos valores permitidosRangeFrom
son 49999998, 49999999 y 50000000. La recursión se puede usar para obtener todos los valores distintosDiffOfColumns
y funciona bien para su conjunto de datos porque solo hay 256 de ellos. La consulta a continuación toma aproximadamente 6 ms en mi máquina:Puede ver la parte recursiva habitual junto con la búsqueda de índice para cada valor distinto:
El defecto con este enfoque es que comienza a disminuir cuando hay demasiados valores distintos para
DiffOfColumns
. Hagamos la misma prueba, pero use enCRYPT_GEN_RANDOM(2)
lugar deCRYPT_GEN_RANDOM(1)
.La misma consulta ahora encuentra 65536 filas de la parte recursiva y toma 823 ms de CPU en mi máquina. Hay PAGELATCH_SH esperas y otras cosas malas que suceden. Puedo mejorar el rendimiento al agrupar los valores de diferencias para mantener el número de valores únicos bajo control y ajustar el agrupamiento en el
CROSS APPLY
. Para este conjunto de datos, intentaré 256 cubos:Una forma de evitar obtener filas adicionales (ahora me estoy comparando con un valor redondeado en lugar del valor verdadero) es filtrando en
RangeTo
:La consulta completa ahora toma 6 ms en mi máquina.
fuente
Una forma alternativa de representar un rango sería como puntos en una línea.
Lo siguiente migra todos los datos a una nueva tabla con el rango representado como un
geometry
tipo de datos.La consulta equivalente para encontrar rangos que contengan el valor
50,000,000
está debajo.Las lecturas para esto muestran una mejora con
10,951
respecto a la consulta original.Sin embargo, no hay una mejora significativa sobre el original en términos de tiempo transcurrido . Los resultados de ejecución típicos son 250 ms frente a 252 ms.
El plan de ejecución es más complejo como se muestra a continuación.
El único caso en el que la reescritura funciona mejor para mí es con un caché frío.
Tan decepcionante en este caso y difícil recomendar esta reescritura, pero la publicación de resultados negativos también puede ser útil.
fuente
Como homenaje a nuestros nuevos señores de los robots, decidí ver si alguna de las nuevas funcionalidades de R y Python podría ayudarnos aquí. La respuesta es no, al menos para los scripts que podría obtener trabajando y devolviendo resultados correctos. Si viene alguien con mejores conocimientos, bueno, siéntase libre de pegarme. Mis tarifas son razonables.
Para hacer esto, configuré una máquina virtual con 4 núcleos y 16 GB de RAM, pensando que esto sería suficiente para manejar un conjunto de datos de ~ 200 MB.
¡Comencemos con el lenguaje que no existe en Boston!
R
Este fue un mal momento.
El plan de ejecución es bastante poco interesante, aunque no sé por qué el operador del medio tiene que llamarnos nombres.
¡A continuación, codificando con crayones!
Pitón
Justo cuando pensabas que no podía ser peor que R:
Otro plan de ejecución mal hablado :
Hmm y Hmmer
Hasta ahora, no estoy impresionado. No puedo esperar para eliminar esta VM.
fuente
DECLARE @input INT = 50000001; EXEC dbo.sp_execute_external_script @language = N'R', @script = N'OutputDataSet <- InputDataSet[which(x >= InputDataSet$RangeFrom & x <= InputDataSet$RangeTo) , ]', @parallel = 1, @input_data_1 = N'SELECT Id, RangeFrom, RangeTo FROM dbo.MyTable;', @params = N'@x INT', @x = 50000001 WITH RESULT SETS ( ( Id INT NOT NULL, RangeFrom INT NOT NULL, RangeTo INT NOT NULL ));
pero sí, el rendimiento no es excelente. Uso R para cosas que no puedes hacer en SQL, por ejemplo, si quieres predecir algo.Encontré una solución bastante buena usando una columna calculada, sin embargo, solo es buena para un solo valor. Dicho esto, si tienes un valor mágico, tal vez sea suficiente.
Comenzando con su muestra dada, luego modificando la tabla:
La consulta simplemente se convierte en:
Que devuelve los mismos resultados que su consulta inicial. Con los planes de ejecución desactivados, aquí están las estadísticas (truncadas por brevedad):
Y aquí está el plan de consulta :
fuente
WHERE (50000000 BETWEEN RangeFrom AND RangeTo) INCLUDE (..)
?CREATE INDEX IX1_redo ON dbo.MyTable (curtis_jackson) INCLUDE (RangeFrom, RangeTo) WHERE RangeTo >= 50000000 AND RangeFrom <= 50000000
trabajaría. Y la consulta loSELECT * FROM MyTable WHERE RangeTo >= 50000000 AND RangeFrom <= 50000000;
usa, así que no hay mucha necesidad del pobre CurtisMi solución se basa en la observación de que el intervalo tiene un máximo conocida anchura W . Para los datos de muestra, este es un byte o 256 enteros. Por lo tanto, para una búsqueda de valor de parámetro determinado P conocemos la RangeFrom más pequeño que puede estar en el conjunto de resultados es P - W . Agregar esto al predicado da
Dada la configuración original y la consulta, mi máquina (Windows 10 de 64 bits, i7 hyperthreaded de 4 núcleos, 2,8 GHz, 16 GB de RAM) devuelve 13 filas. Esa consulta utiliza una búsqueda de índice paralelo del índice (RangeFrom, RangeTo). La consulta revisada también realiza una búsqueda de índice paralelo en el mismo índice.
Las medidas para las consultas originales y revisadas son
Para la consulta original, el número de filas leídas es igual al número de filas que son menores o iguales a @P. El optimizador de consultas (QO) no tiene otra alternativa, pero léalas todas, ya que no puede determinar de antemano si estas filas satisfarán el predicado. El índice de varias columnas en (RangeFrom, RangeTo) no es útil para eliminar filas que no coinciden con RangeTo ya que no hay correlación entre la primera clave de índice y la segunda que se puede aplicar. Por ejemplo, la primera fila puede tener un intervalo pequeño y eliminarse, mientras que la segunda fila tiene un intervalo grande y se devuelve, o viceversa.
En un intento fallido intenté proporcionar esa certeza a través de una restricción de verificación:
No hizo ninguna diferencia.
Al incorporar mi conocimiento externo de la distribución de datos en el predicado, puedo hacer que el QO omita las filas RangeFrom de bajo valor, que nunca pueden ser parte del conjunto de resultados, y atraviese la columna principal del índice a las filas admisibles. Esto se muestra en el predicado de búsqueda diferente para cada consulta.
En un argumento espejo del límite superior de RangeTo es P + W . Sin embargo, esto no es útil, porque no existe una correlación entre RangeFrom y RangeTo que permita que la columna final de un índice de varias columnas elimine filas. Por lo tanto, no es beneficioso agregar esta cláusula a la consulta.
Este enfoque obtiene la mayor parte de su beneficio del pequeño intervalo de tamaño. A medida que aumenta el tamaño del intervalo posible, disminuye el número de filas omitidas de bajo valor, aunque algunas se omitirán. En el caso limitante, con el intervalo tan grande como el rango de datos, este enfoque no es peor que la consulta original (que es una comodidad fría, lo admito).
Pido disculpas por los errores ocasionales que puedan existir en esta respuesta.
fuente