2009-04-16 11 views
11

Estamos usando SQL Server 2005 para rastrear una buena cantidad de datos constantemente entrantes (5-15 actualizaciones por segundo). Nos dimos cuenta después de que ha estado en producción durante un par de meses que una de las tablas ha comenzado a tomar una cantidad obscena de tiempo para consultar.Pregunta lenta distinta en SQL Server en un conjunto de datos grande

La tabla tiene 3 columnas:

  • id - autonumber (agrupado)
  • typeUUID - GUID generado antes de que ocurra el inserto; se utiliza para agrupar los tipos juntos
  • typeName - El nombre del tipo (duh ...)

Una de las consultas que corremos es un distintivo en el campo typeName:

SELECT DISTINCT [typeName] FROM [types] WITH (nolock); 

El typeName campo tiene un índice ascendente no agrupado, no único en él. La tabla contiene aproximadamente 200 millones de registros en este momento. Cuando ejecutamos esta consulta, ¡la consulta tardó 5m 58s en volver! Quizás no estamos entendiendo cómo funcionan los índices ... Pero no pensé que los entendiéramos mal que mucho.

Para probar esto un poco más, nos encontramos con la siguiente consulta:

SELECT DISTINCT [typeName] FROM (SELECT TOP 1000000 [typeName] FROM [types] WITH (nolock)) AS [subtbl] 

Esta consulta devuelve en unos 10 segundos, lo que cabe esperar, que está escaneando la tabla.

¿Hay algo que nos falta aquí? ¿Por qué la primera consulta toma tanto tiempo?

Edit: Ah, disculpas, la primera consulta devuelve 76 registros, gracias ninesided.

Seguimiento: Gracias a todos por sus respuestas, tiene más sentido para mí ahora (no sé por qué no lo hizo antes ...). Sin un índice, está haciendo un escaneo de tabla en 200M filas, con un índice, está haciendo un escaneo de índice en 200M filas ...

SQL Server prefiere el índice, y da un poco de un aumento de rendimiento , pero no hay nada de lo que emocionarse. Reconstruir el índice redujo el tiempo de consulta a poco más de 3m en lugar de 6m, una mejora, pero no suficiente. Voy a recomendar a mi jefe que normalicemos la estructura de la mesa.

¡Una vez más, gracias a todos por su ayuda!

+0

¿Cuántos tipos distintos esperas normalmente? – ninesided

+0

Honestamente, parece que su diseño es fundamentalmente defectuoso. 200M registros en una tabla "entrante"? ¿No puedes empujarlos a otro lugar después de que han estado por aquí un rato? Es difícil dar un mejor consejo sin entender su aplicación, pero parece que es posible que necesite una refactorización seria. – kquinn

+0

Sí, tenemos muchos datos con los que estamos lidiando, actualmente eso equivale a 4 meses de datos. Vamos a necesitar dividir los datos, pero todavía no hemos llegado. – Miquella

Respuesta

9

No entiende bien el índice. Incluso si usara el índice, seguiría haciendo un escaneo de índice en 200 millones de entradas. Esto llevará mucho tiempo, más el tiempo que lleva hacer DISTINCT (provoca un tipo) y es malo ejecutarlo. Ver un DISTINCT en una consulta siempre levanta una bandera roja y me obliga a verificar la consulta. En este caso, ¿quizás tienes un problema de normalización?

+0

Sin duda, esto es en parte una cuestión de la normalización de datos, pero que ya está teniendo problemas de rendimiento de la normalización de los datos antes. estamos casi siempre por delante de los datos de entrada como es. es – Miquella

+0

un escaneo de índice, pero no debería escanear el índice (al menos en este caso) solo golpear los nodos del árbol, no escanear a través de las hojas? – Miquella

+1

El índice puede estar muy fragmentado, lo que aumenta el tiempo de escaneo. ¿Ejecutas trabajos de mantenimiento? Deberías estar haciendo eso todas las noches con tanta información. (Suponiendo que pueda programar la hora) – beach

0

La segunda consulta funciona en 1000000 registros, pero el primero es 200M. Creo que esta es una gran diferencia :)

+0

Sí, debería ser una gran diferencia. Pero la diferencia debería revertirse, porque la primera consulta usa un índice, la segunda pregunta está haciendo una exploración de tabla – Miquella

1

Mi primer pensamiento son las estadísticas. Para encontrar la última actualización:

SELECT 
    name AS index_name, 
    STATS_DATE(object_id, index_id) AS statistics_update_date 
FROM 
    sys.indexes 
WHERE 
    object_id = OBJECT_ID('MyTable'); 

Editar: Las estadísticas se actualizan cuando se vuelven a generar índices, lo que veo no son mantenidos

Mi segundo pensamiento es que es el índice sigue ahí? La consulta TOP aún debe usar un índice. Acabo de probar en una de mis tablas con 57 millones de filas y ambas usan el índice.

+0

Sí, el índice está ahí, y está usando el índice. :(Eso fue lo primero que revisé. Está escaneando el índice, pero no sé por qué el escaneo del único campo del índice debería llevar tanto tiempo ... – Miquella

4

Dudo que SQL Server incluso intente usar el índice, tendría que hacer prácticamente la misma cantidad de trabajo (dada la tabla estrecha), leyendo todas las filas de 200M independientemente de si mira la tabla o el índice . Si el índice en typeName estaba agrupado, puede reducir el tiempo necesario, ya que no debería ser ordenado antes de agruparlo.

Si la cardinalidad de sus tipos es baja, ¿qué hay de mantener una tabla de resumen que contiene la lista de valores distintos type? Un desencadenante de inserción/actualización de la tabla principal verificaría la tabla de resumen e insertaría un nuevo registro cuando se encuentre un nuevo tipo.

+0

+1; trigger on insert es mejor de lo que estaba pensando (agregue un segundo INSERT después del principal, inserte en dicha tabla de resumen, y capture/ignore la violación de restricción UNIQUE). – kquinn

+0

Estaba pensando exactamente lo mismo. Haga la tabla de resumen si necesita ejecutar la consulta distinta a menudo. También deberá agregar un desencadenador DELETE para limpiar la tabla después de eliminar las filas. O si no es un gran problema, programe un trabajo SQL para actualizar la tabla de resumen cada noche. (eliminando los tipos eliminados). – beach

+0

Una idea si hay DELETE involucrados: haga que la tabla de resumen tenga una columna de recuento de referencias; los disparadores en INSERT lo incrementan y en DELETE lo disminuyen. Eso debería funcionar bastante bien. – kquinn

1

Como otros ya han señalado, cuando selecciona SELECT DISTINCT (typename) sobre su mesa, terminará con un escaneo completo de la tabla sin importar nada.

Por lo tanto, es realmente una cuestión de limitar el número de filas que deben escanearse.

La pregunta es: ¿para qué necesita sus nombres de tipos DISTINCT? ¿Y cuántas de tus filas de 200M son distintas? ¿Tiene solo un puñado (unos cientos como máximo) de nombres de tipos distintos?

Si es así, puede tener una tabla DISTINCT_TYPENAMES por separado o algo así y completar las que inicialmente haciendo un escaneo completo de la tabla, y luego al insertar nuevas filas en la tabla principal, simplemente compruebe si su nombre de tipo ya está en DISTINCT_TYPENAMES, y si no, agrégalo.

De esta manera, tendrías una tabla separada, pequeña con solo las entradas distintas de TypeName, que sería muy rápida de consultar y/o mostrar.

Marc

+0

Es un escaneo de índice, no un escaneo de tabla (ya lo he verificado). Tenía entendido que si el índice se construye correctamente, simplemente escanearía el índice, en lugar de la tabla completa. – Miquella

+0

Puede y lo hace escaneando el índice en lugar de la tabla. Pero este no es el problema que los índices están diseñados para resolver, por lo que una exploración de índice completa no puede resolver esta consulta significativamente más rápido que una exploración de tabla completa. – kquinn

+0

El índice contendrá todavía 200M entradas ...... –

0

que debería intentar algo como esto:

SELECT typeName FROM [types] WITH (nolock) 
group by typeName; 

Y al igual que otros, diría que es necesario para normalizar esa columna.

0

Un índice lo ayuda a encontrar rápidamente una fila. Pero le está pidiendo a la base de datos que liste todos los tipos únicos para toda la tabla. Un índice no puede ayudar con eso.

Puede ejecutar un trabajo nocturno que ejecute la consulta y la almacene en una tabla diferente. Si necesita los datos hasta a la fecha, se podría almacenar el último ID incluido en la exploración de todas las noches, y combinar los resultados:

select type 
from nightlyscan 
union 
select distinct type 
from verybigtable 
where rowid > lastscannedid 

Otra opción es la normalización de la mesa grande en dos tablas:

talbe1: id, guid, typeid 
type table: typeid, typename 

Esto sería muy beneficioso si el número de tipos fuera relativamente pequeño.

3

Hay un isse con el optimizador de SQL Server cuando se utiliza la palabra clave DISTINCT. La solución fue forzarlo a mantener el mismo plan de consulta separando la consulta distinta por separado.

Así que nosotros también consultas tales como:

SELECT DISTINCT [typeName] FROM [types] WITH (nolock); 

y dividirla en los siguientes

SELECT typeName INTO #tempTable1 FROM types WITH (NOLOCK) 
SELECT DISTINCT typeName FROM #tempTable1 

Otra manera de conseguir alrededor de él es utilizar un GROUP BY, que consigue un plan de optimización diferente .

+1

Puede ser útil agregar más información acerca de cómo esto cambia el plan de ejecución. – EdC

1

Un enfoque de bucle debe usar múltiples búsquedas (pero pierde algo de paralelismo). Puede valer la pena intentarlo en los casos con relativamente pocos valores distintos en comparación con el número total de filas (cardinalidad baja).

idea fue de esta question:

select typeName into #Result from Types where 1=0; 

declare @t varchar(100) = (select min(typeName) from Types); 
while @t is not null 
begin 
    set @t = (select top 1 typeName from Types where typeName > @t order by typeName);  
    if (@t is not null) 
     insert into #Result values (@t); 
end 

select * from #Result; 

Y parece que también hay algunos otros métodos (especialmente el CTE recursiva @ Pablo Blanco):

different-ways-to-find-distinct-values-faster-methods

sqlservercentral Topic873124-338-5

+0

Sí. También escribí sobre el CTE 'skip-scan' [aquí] (http://sqlperformance.com/2014/10/t-sql-queries/performance-tuning-whole-plan) –

0

Me podría estar faltando algo pero sería más eficiente si se carga una sobrecarga para crear una vista con valores distintos y consulta que en su lugar?

Esto daría respuestas casi instantáneas a la selección si el conjunto de resultados es significativamente más pequeño con la sobrecarga que sobrepoblarla en cada escritura, dada la naturaleza de la vista que podría ser trivial en sí misma.

Hace la pregunta de cuántas escrituras en comparación con la frecuencia que desea la diferencia y la importancia de la velocidad cuando lo hace.

0

Una vista indexada puede hacer esto más rápido.

create view alltypes 
with schemabinding as 
select typename, count_big(*) as kount 
from dbo.types 
group by typename 

create unique clustered index idx 
on alltypes (typename) 

El trabajo para mantener a la vista hasta la fecha en cada cambio en la tabla de base debe ser moderada (dependiendo de su aplicación, por supuesto - mi punto es que no tiene que escanear toda la tabla . cada vez o hacer nada terriblemente caro por el estilo)

Alternativamente, usted podría hacer una pequeña mesa con todos los valores:

select distinct typename 
into alltypes 
from types 

alter table alltypes 
add primary key (typename) 

alter table types add foreign key (typename) references alltypes 

la clave externa se asegurará de que todos los valores utilizados aparecen en la alltypes tabla primaria. El problema está en garantizar que alltypes no no contenga valores no utilizados en la tabla types secundaria.

Cuestiones relacionadas