Indexación de un GUID de PK en SQL Server 2012

13

Mis desarrolladores han configurado su aplicación para usar GUID como PK para casi todas sus tablas y, de forma predeterminada, SQL Server ha configurado el índice agrupado en estas PK.

El sistema es relativamente joven y nuestras tablas más grandes tienen poco más de un millón de filas, pero estamos analizando nuestra indexación y queremos poder escalar rápidamente, ya que puede ser necesario en el futuro cercano.

Por lo tanto, mi primera inclinación fue mover el índice agrupado al campo creado, que es una gran representación de DateTime. Sin embargo, la única forma en que puedo hacer que el CX sea único sería incluir la columna GUID en este CX, pero ordenar por creado primero.

¿Esto haría que la clave de agrupamiento fuera demasiado amplia y aumentaría el rendimiento de las escrituras? Las lecturas también son importantes, pero las escrituras son probablemente una preocupación mayor en este momento.

njkroes
fuente
1
¿Cómo se generan los GUID? NEWID o NEWSEQUENTIALID?
swasheck
66
Agrupado rendimiento GUID y el inserto debe ser sólo en una frase si la palabra inmediatamente anterior "rendimiento" es minimizar
billinkc
2
Lleve a esos desarrolladores a almorzar y explíqueles que si usan NEWID () nuevamente como clave principal, les echarán la culpa del bajo rendimiento. Le preguntarán rápidamente qué hacer para evitarlo. En ese punto, dices usar IDENTITY (1,1) en su lugar. (quizás una ligera simplificación excesiva pero 9 de cada 10 veces funcionará).
Max Vernon
3
La razón de nuestro odio a guid es que son anchos (16 bytes) y cuando no se crean newsequentialidson aleatorios. Las claves agrupadas son mejores cuando son estrechas y crecientes. Un GUID es lo contrario: gordo y aleatorio. Imagine una estantería casi llena de libros. Entra el OED y debido a la aleatoriedad de las guías, se inserta en el medio del estante. Para mantener las cosas ordenadas, la mitad correcta de los libros tiene que ser introducida en una nueva ubicación, que es una tarea que requiere mucho tiempo. Eso es lo que el GUID está haciendo a su base de datos y está matando el rendimiento.
billinkc
77
La forma de solucionar el problema del uso de identificadores únicos es volver al tablero de dibujo y no usar identificadores únicos . No son terribles si el sistema es pequeño, pero si tiene al menos más de un millón de tablas de filas (o cualquier tabla más grande que eso), se aplastará utilizando identificadores únicos para las claves.
Jon Seigel

Respuestas:

20

Los principales problemas con los GUID, especialmente los no secuenciales, son:

  • Tamaño de la clave (16 bytes frente a 4 bytes para un INT): Esto significa que está almacenando 4 veces la cantidad de datos en su clave junto con ese espacio adicional para cualquier índice si este es su índice agrupado.
  • Fragmentación del índice: es prácticamente imposible mantener una columna GUID no secuencial desfragmentada debido a la naturaleza completamente aleatoria de los valores clave.

Entonces, ¿qué significa esto para su situación? Todo se reduce a tu diseño. Si su sistema se trata simplemente de escrituras y no le preocupa la recuperación de datos, entonces el enfoque descrito por Thomas K es preciso. Sin embargo, debe tener en cuenta que al seguir esta estrategia, está creando muchos problemas potenciales para leer esos datos y almacenarlos. Como señala Jon Seigel , también estarás ocupando más espacio y esencialmente teniendo memoria hinchada.

La pregunta principal sobre los GUID es qué tan necesarios son. A los desarrolladores les gustan porque aseguran la singularidad global, pero es una rara ocasión que este tipo de singularidad sea necesaria. Pero considere que si su número máximo de valores es menor a 2,147,483,647 (el valor máximo de un entero con signo de 4 bytes), entonces probablemente no esté usando el tipo de datos apropiado para su clave. Incluso utilizando BIGINT (8 bytes), su valor máximo es 9,223,372,036,854,775,807. Esto suele ser suficiente para cualquier base de datos no global (y muchas globales) si necesita algún valor de incremento automático para una clave única.

Finalmente, en lo que respecta al uso de un montón versus un índice agrupado, si solo está escribiendo datos, un montón sería más eficiente porque está minimizando la sobrecarga para las inserciones. Sin embargo, los montones en SQL Server son extremadamente ineficientes para la recuperación de datos. Mi experiencia ha sido que un índice agrupado siempre es deseable si tiene la oportunidad de declarar uno. He visto que la adición de un índice agrupado a una tabla (más de 4 mil millones de registros) mejora el rendimiento general de selección en un factor de 6.

Información Adicional:

Mike Fal
fuente
13

No hay nada malo con GUID como claves y clústeres en un sistema OLTP (a menos que tenga MUCHOS índices en la tabla que sufran el aumento del tamaño del clúster). De hecho, son mucho más escalables que las columnas IDENTITY.

Existe una creencia generalizada de que los GUID son un gran problema en SQL Server; en gran medida, esto es simplemente incorrecto. De hecho, el GUID puede ser significativamente más escalable en cajas con más de aproximadamente 8 núcleos:

Lo siento, pero sus desarrolladores tienen razón. Preocúpese por otras cosas antes de preocuparse por GUID.

Ah, y finalmente: ¿por qué quieres un índice de clúster en primer lugar? Si su preocupación es un sistema OLTP con muchos índices pequeños, probablemente sea mejor con un montón.

Consideremos ahora qué fragmentación (que introducirá el GUID) hace a sus lecturas. Hay tres problemas principales con la fragmentación:

  1. Las divisiones de página cuestan E / S de disco
  2. Las páginas a la mitad no son tan eficientes en memoria como las páginas completas
  3. Hace que las páginas se almacenen fuera de servicio, lo que hace menos probable la E / S secuencial

Dado que su preocupación en la pregunta es sobre la escalabilidad, que podemos definir como "Agregar más hardware hace que el sistema vaya más rápido", estos son sus problemas menores. Para abordar cada uno a su vez

Anuncio 1) Si desea escalar, puede comprar E / S. Incluso un SSD Samsung / Intel 512GB barato (a unos pocos USD / GB) te dará más de 100K IOPS. No lo consumirá pronto en un sistema de 2 sockets. Y si te encuentras con eso, compra uno más y listo

Anuncio 2) Si elimina en su tabla, tendrá páginas semillenas de todos modos. E incluso si no lo hace, la memoria es barata y para todos menos los sistemas OLTP más grandes: los datos activos deberían encajar allí. Buscar empacar más datos en páginas es una suboptimización cuando busca escala.

Anuncio 3) Una tabla construida a partir de datos divididos en páginas con frecuencia altamente fragmentados realiza E / S aleatorias exactamente a la misma velocidad que las tablas llenas secuencialmente

Con respecto a la unión, hay dos tipos principales de unión que probablemente verá en una OLTP como la carga de trabajo: Hash y loop. Veamos cada uno a su vez:

Hash join: una combinación hash supone que la tabla pequeña se escanea y la más grande normalmente se busca. Es muy probable que las tablas pequeñas estén en la memoria, por lo que la E / S no es su preocupación aquí. Ya hemos mencionado el hecho de que las búsquedas tienen el mismo costo en un índice fragmentado que en un índice no fragmentado.

Unión de bucle: se buscará la tabla externa. Mismo costo

Es posible que también tenga muchos escaneos de tablas incorrectos, pero luego GUID nuevamente no es su preocupación, la indexación adecuada sí lo es.

Ahora, puede tener algunos escaneos de rango legítimos (especialmente cuando se une a claves externas) y en este caso, los datos fragmentados están menos "empaquetados" en comparación con los datos no fragmentados. Pero consideremos qué combinaciones es probable que vea en datos bien indexados de 3NF:

  1. Una combinación de una tabla que tiene una referencia de clave externa a la clave principal de la tabla a la que hace referencia

  2. Al revés

Anuncio 1) En este caso, irá por una sola búsqueda a la clave principal: unir n a 1. Fragmentación o no, el mismo costo (una búsqueda)

Anuncio 2) En este caso, se está uniendo a la misma clave, pero puede recuperar más de una fila (búsqueda de rango). La unión en este caso es de 1 a n. Sin embargo, en la tabla extranjera que busca, está buscando la misma clave, que es probable que esté en la misma página en un índice fragmentado que en una no fragmentada.

Considere esas claves foráneas por un momento. Incluso si hubiera colocado "perfectamente" secuencialmente nuestras claves principales, cualquier cosa que apunte a esa clave seguirá siendo no secuencial.

Por supuesto, es posible que se esté ejecutando en una máquina virtual en alguna SAN en algún banco que tenga poco dinero y mucho proceso. Entonces todos estos consejos se perderán. Pero si ese es su mundo, la escalabilidad probablemente no sea lo que está buscando: busca rendimiento y alta velocidad / costo, que son dos cosas diferentes.

Thomas Kejser
fuente
1
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Paul White 9
5

Thomas: algunos de tus puntos tienen mucho sentido y estoy de acuerdo con todos ellos. Si usa SSD, el saldo de lo que optimiza cambia. Aleatorio versus secuencial no es la misma discusión que el disco giratorio.

Estoy especialmente de acuerdo en que adoptar una vista de DB pura es terriblemente incorrecto. Hacer que su aplicación sea lenta y no escalable para mejorar solo el rendimiento de la base de datos puede ser bastante errónea.

El gran problema con IDENTITY (o secuencia, o cualquier cosa generada en el DB) es que es terriblemente lento, ya que requiere un viaje de ida y vuelta al DB para crear una clave, y esto automáticamente crea un cuello de botella en su DB, obliga a que las aplicaciones deben hacer una llamada a DB para comenzar a usar una clave. La creación de un GUID resuelve esto mediante el uso de la aplicación para crear la clave, se garantiza que sea globalmente único (por definición) y, por lo tanto, las capas de la aplicación pueden usarlo para pasar el registro ANTES de incurrir en un DB ida y vuelta.

Pero tiendo a usar una alternativa a los GUID. Mi preferencia personal para un tipo de datos aquí es un BIGINT globalmente único generado por la aplicación. ¿Cómo se hace para hacer esto? En el ejemplo más trivial, agrega una función pequeña, MUY liviana a su aplicación para hash un GUID. Asumiendo que su función hash es rápida y relativamente rápida (consulte CityHash de Google para ver un ejemplo: http://google-opensource.blogspot.in/2011/04/introducing-cityhash.html - asegúrese de obtener todos los pasos de compilación correctamente, o la variante FNV1a de http://tools.ietf.org/html/draft-eastlake-fnv-03 para código simple) esto le brinda el beneficio de identificadores únicos generados por la aplicación y un valor clave de 64 bits con el que las CPU funcionan mejor .

Hay otras formas de generar BIGINT, y en estos dos algos existe la posibilidad de colisiones hash: leer y tomar decisiones conscientes.

Mark Stacey
fuente
2
Le sugiero que edite su respuesta como una respuesta a la pregunta del OP y no (como lo es ahora) como una respuesta a la respuesta de Thomas. Todavía puede resaltar las diferencias entre Thomas (, MikeFal's) y su sugerencia.
ypercubeᵀᴹ
2
Por favor dirija su respuesta a la pregunta. Si no lo hace, lo eliminaremos por usted.
JNK
2
Gracias por los comentarios Mark. Cuando edite su respuesta (que creo que proporciona un contexto muy bueno), cambiaría una cosa: IDENTITY no requiere un viaje de ida y vuelta adicional al servidor si tiene cuidado con INSERT. Siempre puede devolver SCOPE_IDENTITY () en el lote que llama al INSERT ..
Thomas Kejser
1
Con respecto a "es horriblemente lento, ya que requiere un viaje de ida y vuelta a la base de datos para crear una clave": puede obtener tantos como necesite en un viaje de ida y vuelta.
AK
Con respecto a "puede tomar tantos como necesite en un solo viaje de ida y vuelta": no puede hacer esto con columnas IDENTITY o cualquier otro método en el que básicamente esté usando DEFAULT en el nivel de la base de datos.
Avi Cherry