19

Tengo dos tablas masivas con aproximadamente 100 millones de registros cada una y me temo que tenía que realizar una unión interna entre las dos. Ahora, ambas tablas son muy simples; aquí está la descripción:SQL: unión interna de dos tablas masivas

mesa bioentity:

  • BioEntityId (int)
  • Nombre (nvarchar 4000, aunque esto es una exageración)
  • TypeId (int)

mesa de EGM (una tabla auxiliar, de hecho, resultante de las operaciones de importación masiva):

  • EMGId (int)
  • PID (int)
  • Nombre (nvarchar 4000, aunque esto es una exageración)
  • TypeId (int)
  • LastModified (fecha)

Necesito obtener un Nombre coincidente para asociar BioEntityId con el PId que reside en la tabla de EGM. Originalmente, traté de hacer todo con una sola combinación interna pero la consulta parecía llevar demasiado tiempo y el archivo de registro de la base de datos (en modo de recuperación simple) logró masticar todo el espacio disponible en el disco (eso es un poco más de 200 GB, cuando la base de datos ocupa 18 GB) y la consulta fallaría después de esperar dos días, si no me equivoco. Pude evitar que el registro creciera (solo 33 MB ahora) pero la consulta se ha estado ejecutando sin interrupción durante 6 días y no parece que se vaya a detener pronto.

Lo estoy ejecutando en una computadora bastante decente (4 GB de RAM, Core 2 Duo (E8400) 3GHz, Windows Server 2008, SQL Server 2008) y me he dado cuenta de que la computadora se atasca cada 30 segundos (dar o tomar) por un par de segundos. Esto hace que sea bastante difícil de usar para cualquier otra cosa, lo que realmente me está poniendo de los nervios.

Ahora, aquí está la pregunta:

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX 
FROM EGM INNER JOIN BioEntity 
ON EGM.name LIKE BioEntity.Name AND EGM.TypeId = BioEntity.TypeId 

tuve configurar manualmente algunos índices; tanto EGM como BioEntity tenían un índice de cobertura no agrupado que contenía TypeId y Name. Sin embargo, la consulta se ejecutó durante cinco días y tampoco finalizó, así que intenté ejecutar Database Tuning Advisor para que funcione. Sugirió eliminar mis índices anteriores y crear estadísticas y dos índices agrupados (uno en cada tabla, que solo contiene el TypeId que me parece bastante extraño, o simplemente tonto, pero de todos modos lo aprobé).

Ha estado funcionando durante 6 días y todavía no estoy seguro de qué hacer ... ¿Alguna idea, chicos? ¿Cómo puedo hacer esto más rápido (o, al menos, finito)?

Actualización: - Ok, he cancelado la consulta y reinicia el servidor para obtener el sistema operativo en funcionamiento de nuevo - Estoy volviendo a ejecutar el flujo de trabajo con los cambios propuestos, de cultivo específicamente el campo nvarchar a una un tamaño mucho más pequeño e intercambiando "me gusta" por "=".Esto se va a tomar por lo menos dos horas, así que voy a publicar más actualizaciones más adelante

Actualización 2 (tiempo 13:00 GMT, 18/11/09): - El plan de ejecución estimado revela un costo 67% con respecto a los escaneos de tabla seguidos por un 33% de coincidencia hash. Luego viene el 0% de paralelismo (¿no es extraño? Esta es la primera vez que uso el plan de ejecución estimado, pero este hecho particular me levantó la ceja), 0% hash match, más 0% paralelismo, 0% top, 0 % table insert y finalmente otro 0% select into. Parece que los índices son basura, como se esperaba, así que haré índices manuales y descartaré los sugeridos.

+1

Sólo por curiosidad ... ¿por qué necesita más de 100 millones de las filas atrás y ¿qué vas a hacer con todos estos datos ?? –

+0

¿Cuál es el valor más grande almacenado en su campo de nombre 4k? Si es sustancialmente menor que 4k, reduzca el tamaño en cada tabla. –

+0

Bioinformática ...:] –

Respuesta

6

Para une enorme, a veces la elección de una forma explícita loop join acelera las cosas:

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX 
FROM EGM 
INNER LOOP JOIN BioEntity 
    ON EGM.name LIKE BioEntity.Name AND EGM.TypeId = BioEntity.TypeId 

Como siempre, la publicación de su plan de ejecución estimado podría ayudarnos a proporcionar mejores respuestas.

EDIT: Si se ordenan las dos entradas (que deberían ser, con el índice de cobertura), puede intentar una MERGE JOIN:

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX 
FROM EGM 
INNER JOIN BioEntity 
    ON EGM.name LIKE BioEntity.Name AND EGM.TypeId = BioEntity.TypeId 
OPTION (MERGE JOIN) 
+1

Estoy cancelando la consulta ahora, veamos si SQL Server puede regresar de la muerte y denos el plan ... –

+0

Ok, el servidor murió , reiniciado, rehice el flujo de trabajo durante la noche; publicando los resultados ahora –

+0

'inner loop join' ¿usa menos memoria y más CPU? – seyed

1

100 millones de discos es enorme. Yo diría que para trabajar con una base de datos tan grande necesitarías un servidor de prueba dedicado. Usar la misma máquina para hacer otro trabajo mientras se realizan consultas como esa no es práctico.

Su hardware es bastante capaz, pero para uniones tan grandes como para funcionar decentemente necesitaría aún más potencia. Un sistema de cuatro núcleos con 8 GB sería un buen comienzo. Más allá de eso, debes asegurarte de que tus índices estén configurados correctamente.

+1

Voy a twittear esto a mi jefe;) gracias –

+1

LOL, sí, dígale StackOverflow dice que usted necesita una nueva computadora portátil AlienWare también! –

+0

Y dos monitores de 30 pulgadas. Esos son muchos datos para mirar –

4

Intentaré tal vez eliminar el operador 'LIKE'; ya que no parece estar haciendo ningún comodín coincidente.

+0

No, en realidad no, también probé con los caracteres iguales ("="), pero de todos modos no parecía prometedor. Lo cambiaré, gracias! –

+4

Sin comodines, el LIKE debería optimizarse en un "=" de todos modos. –

14

No soy un experto en ajuste de SQL, pero unir cientos de millones de filas en un campo VARCHAR no parece una buena idea en ningún sistema de base de datos que conozca.

Puede intentar agregar una columna entera a cada tabla y calcular un hash en el campo NOMBRE que debería obtener las posibles coincidencias con un número razonable antes de que el motor tenga que mirar los datos VARCHAR reales.

+0

Interesante idea. Voy a intentarlo más tarde, gracias. –

+0

CHECKSUM es bueno para esto. –

+0

La suma de comprobación funcionaría pero, dependiendo de la naturaleza de los datos en NAME, es posible que pueda usar un algoritmo hash más rápido (tal vez el NAME tiende a ser único en los primeros diez caracteres, o algo así). –

3

Como lo recomiendo, me gustaría el nombre para hacer la unión más razonable. Consideraría seriamente investigar la asignación de la identificación durante la importación de lotes a través de una búsqueda, si es posible, ya que esto eliminaría la necesidad de hacer la unión más tarde (y potencialmente tener que realizar repetidamente una unión tan ineficiente).

Veo que tiene este índice en el TypeID - esto ayudaría inmensamente si esto fuera del todo selectivo. Además, agregue la columna con el hash del nombre para el mismo índice:

SELECT EGM.Name 
     ,BioEntity.BioEntityId 
INTO AUX 
FROM EGM 
INNER JOIN BioEntity 
    ON EGM.TypeId = BioEntity.TypeId -- Hopefully a good index 
    AND EGM.NameHash = BioEntity.NameHash -- Should be a very selective index now 
    AND EGM.name LIKE BioEntity.Name 
+0

Voy a intentar esto más adelante en el camino, necesito explorar el plan de estimación ahora. Gracias :) –

2

Otra sugerencia que podría ofrecer es tratar de conseguir un subconjunto de los datos en lugar de procesar todos los 100 M filas a la vez para afinar su consulta. De esta forma, no tendrá que perder tanto tiempo esperando a ver cuándo terminará su consulta. Luego, podría considerar inspeccionar el plan de ejecución de la consulta, que también puede proporcionar una idea del problema en cuestión.

+1

esto y tener los índices exactamente correctos, más pequeños posibles (posiblemente otro paso de preprocesamiento) son claves para la tractabilidad. – Don

0

Dado que no le está pidiendo al DB que realice operaciones relacionales sofisticadas, podría escribir fácilmente esto. En lugar de matar al DB con una consulta masiva pero simple, intente exportar las dos tablas (¿puede obtener copias sin conexión de las copias de seguridad?).

Una vez que haya exportado las tablas, escriba una secuencia de comandos para realizar esta simple unión por usted.Llevará aproximadamente la misma cantidad de tiempo ejecutar, pero no matará al DB.

Debido al tamaño de los datos y al tiempo que tarda la consulta en ejecutarse, no lo hará con mucha frecuencia, por lo que un proceso de lote sin conexión tiene sentido.

Para la secuencia de comandos, querrá indexar el conjunto de datos más grande, luego recorrer el conjunto de datos más pequeño y realizar búsquedas en el índice grande de datos. Será O (n * m) para ejecutar.

1

¿Tiene alguna clave o índice principal? ¿Puedes seleccionarlo en etapas? es decir, dónde aparece el nombre 'A%', donde el nombre es 'B%', etc.

+0

Tengo PK (EMGId y BioEntityId) y los índices están publicados en la pregunta –

1

Había configurado manualmente algunos índices; tanto EGM como BioEntity tenían un índice de cobertura no agrupado que contenía TypeId y Name. Sin embargo, la consulta se ejecutó durante cinco días y tampoco finalizó, así que intenté ejecutar Database Tuning Advisor para que funcione. Sugirió eliminar mis índices anteriores y crear estadísticas y dos índices agrupados (uno en cada tabla, que solo contiene el TypeId que me parece bastante extraño, o simplemente tonto, pero de todos modos lo aprobé).

Dijiste que cometió un índice agrupado en TypeId en ambas tablas, aunque parece que tiene una clave principal en cada tabla ya (BioEntityId & EGMId, respectivamente). Usted no desea que su TypeId sea el índice agrupado en esas tablas. Desea que se agrupe BioEntityId & EGMM (físicamente clasifique sus datos en orden del índice agrupado en el disco. Desea índices no agrupados en claves externas que se utilizarán para las búsquedas. Es decir, TypeId. Intente realizar las claves principales agrupadas y agregando un índice no agrupado en ambas tablas que SOLAMENTE CONTIENE TypeId.

En nuestro entorno tenemos unas tablas que son aproximadamente 10-20 millones de registros cada una. Hacemos muchas consultas similares a las suyas , donde estamos combinando dos conjuntos de datos en una o dos columnas. Agregar un índice para cada clave externa debería ayudar mucho con su rendimiento.

Tenga en cuenta que con 100 millones de registros, esos índices requerirán mucho de espacio en disco. Sin embargo, parece que el rendimiento es clave aquí, por lo que debería valer la pena.

K. Scott tiene un artículo bastante bueno here que explica algunos problemas más a fondo.

+0

Lo sé. Lo hice, pero los resultados no fueron realmente lo que esperaba. Lo probé porque SQL Server Database Tuning Advisor lo sugirió; Todavía creo que es estúpido –

1

Reiterando unos mensajes anteriores aquí (que voy a votar hacia arriba) ...

Cómo selectiva es TypeId? Si solo tiene 5, 10 o incluso 100 valores distintos en sus filas de más de 100M +, el índice no hace nada por usted, especialmente porque de todos modos está seleccionando todas las filas.

Sugeriría que crear una columna en CHECKSUM (Nombre) en ambas tablas parece bueno. Tal vez hacer de esto una columna calculada persistido:

CREATE TABLE BioEntity 
(
    BioEntityId int 
    ,Name   nvarchar(4000) 
    ,TypeId  int 
    ,NameLookup AS checksum(Name) persisted 
) 

y luego crear un índice igual que (yo uso agrupado, pero aún no agrupado ayudaría):

CREATE clustered INDEX IX_BioEntity__Lookup on BioEntity (NameLookup, TypeId) 

(Compruebe BOL, hay reglas y limitaciones en la creación de índices en columnas calculadas que pueden aplicarse a su entorno.)

Hecho en ambas tablas, esto debería proporcionar un índice muy selectivo para apoyar la consulta si se revisa la siguiente manera:

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX 
FROM EGM INNER JOIN BioEntity 
ON EGM.NameLookup = BioEntity.NameLookup 
    and EGM.name = BioEntity.Name 
    and EGM.TypeId = BioEntity.TypeId 

Dependiendo de muchos factores que seguirá funcionando de largo (entre otras cosas porque eres ¿cómo copiar la cantidad de datos en una nueva tabla?), pero esto debería tomar menos de días.

+0

Sí, solo un centenar de artículos en TypeId. Gracias por la entrada, voy a intentarlo pronto ... mi MBP de 2 años acaba de morir, voy a la tienda:/ –

6

Tal vez un poco offtopic, pero: "Me he dado cuenta de que el equipo se atasca ocasionalmente cada 30 segundos (más o menos) durante un par de segundos."

Este comportamiento es característico para una matriz RAID5 (o tal vez para un solo disco) barata mientras copia (y su consulta en su mayoría copias de datos) gigabytes de información.

Más sobre el problema: ¿no puedes dividir tu consulta en bloques más pequeños? ¿Como nombres que comienzan con A, B, etc. o ID en rangos específicos? Esto podría disminuir sustancialmente los gastos generales de transacción/bloqueo.

+0

+1 para el partición de datos –

+0

, así que esto se votó por publicar el mismo Lo que ya he preguntado? – DForck42

+0

Un disco, yup. –

1

¿Por qué un nvarchar? La mejor práctica es, si no NECESITA (o espera necesitar) el soporte Unicode, simplemente use varchar. Si crees que el nombre más largo tiene menos de 200 caracteres, convertiría esa columna en varchar (255). Puedo ver escenarios en los que el hashing que se le ha recomendado sería costoso (parece que esta base de datos es intensiva en la inserción). Con ese tamaño, sin embargo, y la frecuencia y naturaleza aleatoria de los nombres, sus índices se fragmentarán rápidamente en la mayoría de los escenarios en los que indexa un hash (dependiente del hash) o el nombre.

Alteraría la columna de nombre como se describe anteriormente y crearía el índice agrupado TypeId, EGMId/BioentityId (la clave sustituta para cualquiera de las tablas). Entonces puede unirse muy bien en TypeId, y la combinación "aproximada" en Name tendrá menos que recorrer. Para ver cuánto tiempo se puede ejecutar esta consulta, inténtelo para un subconjunto muy pequeño de TypeIds, y eso debería darle una estimación del tiempo de ejecución (aunque podría ignorar factores como el tamaño de la memoria caché, el tamaño de la memoria, las tasas de transferencia del disco duro).

Editar: si se trata de un proceso en curso, debe aplicar la restricción de clave externa entre sus dos tablas para futuras importaciones/volcados. Si no es continuo, el hash es probablemente lo mejor que puedas.

+0

No puedo estar seguro de eso, aunque probablemente sea suficiente –

5

En primer lugar, las uniones de 100M filas no son del todo irrazonables o poco comunes.

Sin embargo, sospecho que la causa del bajo rendimiento que está viendo puede estar relacionada con la cláusula INTO. Con eso, no solo está haciendo una unión, también está escribiendo los resultados en una nueva tabla. Su observación sobre el archivo de registro cada vez más grande es básicamente la confirmación de esto.

Una cosa para intentar: eliminar el INTO y ver cómo funciona. Si el rendimiento es razonable, para abordar la escritura lenta debe asegurarse de que su archivo de registro de DB esté en un volumen físico separado de los datos. Si no lo es, las cabezas del disco se agitarán (muchas búsquedas) a medida que lean los datos y escriban el registro, y su rendimiento se colapsará (posiblemente hasta tan solo 1/40 a 1/60 de lo que podría ser).)

0

Me pregunto si el tiempo de ejecución lo toma la unión o la transferencia de datos.

Asumido, el tamaño de datos promedio en su columna de nombre es de 150 caracteres, en realidad tendrá 300 bytes más las otras columnas por registro. Multiplique esto por 100 millones de registros y obtendrá aproximadamente 30 GB de datos para transferir a su cliente. ¿Ejecutas el cliente de forma remota o en el servidor? Quizás espere a que se transfieran 30GB de datos a su cliente ...

EDIT: Ok, veo que está insertando en la tabla Aux. ¿Cuál es la configuración del modelo de recuperación de la base de datos?

Para investigar el cuello de botella en el lado del hardware, podría ser interesante si el recurso limitante es leer datos o escribir datos. Puede iniciar una ejecución del monitor de rendimiento de Windows y capturar la longitud de las colas para leer y escribir sus discos, por ejemplo.

Ideal, debe colocar el archivo de registro db, las tablas de entrada y la tabla de salida en volúmenes físicos separados para aumentar la velocidad.

+0

El modelo de recuperación está configurado como simple; aprendí eso de la manera difícil :) Sugerencia simple pero lógica con respecto a los volúmenes físicos separados, solo estoy usando una sola unidad de disco duro. ¡Gracias! Estoy publicando la estimación del plan de ejecución ahora por cierto –

0

Si la coincidencia hash consume demasiados recursos, realice su consulta en lotes de, digamos, 10000 filas a la vez, "caminando" la columna TypeID. No mencionó la selectividad de TypeID, pero presumiblemente es lo suficientemente selectiva como para poder hacer lotes de esta envergadura y cubrir por completo uno o más TypeID a la vez. También está buscando combinaciones de bucles en sus lotes, por lo que si todavía obtiene combinaciones de hash, entonces forzará un bucle o reducirá el tamaño del lote.

El uso de lotes también, en modo de recuperación simple, evitará que su registro de transición crezca demasiado. Incluso en el modo de recuperación simple, una unión grande como la que está haciendo consumirá mucho espacio porque tiene que mantener toda la transacción abierta, mientras que al realizar lotes puede reutilizar el archivo de registro para cada lote, limitando su tamaño al más grande necesario para una operación por lotes

Si realmente necesita unirse a Nombre, entonces podría considerar algunas tablas auxiliares que convierten nombres en ID, básicamente reparando el diseño desnormalizado temporalmente (si no puede repararlo permanentemente).

La idea de suma de comprobación también puede ser buena, pero no he jugado mucho con eso.

En cualquier caso, una coincidencia de hash tan grande no funcionará tan bien como las uniones de bucle por lotes. Si pudieras obtener un join de fusión, sería increíble ...

1

Intentaría resolver el problema fuera de la caja, tal vez haya algún otro algoritmo que pueda hacer el trabajo mucho mejor y más rápido que la base de datos. Por supuesto, todo depende de la naturaleza de los datos, pero hay algunos algoritmos de búsqueda de cadenas que son bastante rápidos (Boyer-Moore, ZBox, etc.) u otro algoritmo de datamining (MapReduce?). Al elaborar cuidadosamente la exportación de datos, podría ser posible dobla el problema para adaptarlo a una solución más elegante y más rápida. Además, podría ser posible paralelizar mejor el problema y con un cliente simple hacer uso de los ciclos inactivos de los sistemas que le rodean, hay un marco que puede ayudar con esto.

el resultado de esto podría ser una lista de refid tuplas que podría utilizar para obtener los datos completos de la base de datos mucho más rápido.

Esto no le impide experimentar con el índice, pero si tiene que esperar 6 días para obtener los resultados, creo que justifica los recursos dedicados a explorar otras posibles opciones.

mi 2 céntimos

+0

Hmmm buena idea, gracias! –

Cuestiones relacionadas