Razones para evitar grandes valores de ID

17

Estamos trabajando en una aplicación web, aún no accesible para los usuarios. Mi jefe notó que los registros recién creados obtienen una identificación de más de 10 000, a pesar de que solo tenemos menos de 100 registros en la tabla. Ella asumió que la interfaz web por alguna razón crea más de 100 veces más registros temporales que los reales (y los elimina) y que esto puede llevarnos a quedar fuera de alcance dentro de unos pocos meses de su lanzamiento.

No creo que tenga razón sobre la causa de la inflación de la identificación (el colega que puede responder esto está de vacaciones, por lo que no lo sabemos con certeza), pero supongamos que sí. Ella dijo que odiaría usar una columna bigint, y que le gustaría que dejáramos de aumentar automáticamente la columna de ID y escribir el código del lado del servidor que elige el primer entero "no utilizado" y lo usa como ID.

Soy un estudiante graduado de ciencias de la computación con poca experiencia práctica, desempeñando un rol de desarrollador junior. Tiene años de experiencia en la gestión de todas las bases de datos de nuestra organización y en el diseño de la mayoría de ellas. Yo creo que ella es incorrecta en este caso, que un documento de identidad bigint hay nada que temer, y que imitan la funcionalidad DBMS olores de un anti patrón. Pero todavía no confío en mi juicio.

¿Cuáles son los argumentos a favor y en contra de cada posición? ¿Qué cosas malas pueden pasar si usamos un bigint, y cuáles son los peligros de reinventar la funcionalidad de autoincremento de la rueda ? ¿Hay una tercera solución que sea mejor que cualquiera? ¿Cuáles podrían ser sus razones para querer evitar una inflación de los valores nominales de identificación? También estoy interesado en conocer razones pragmáticas: ¿tal vez las ID de bigint funcionan en teoría, pero causan dolores de cabeza en la práctica?

No se espera que la aplicación maneje grandes cantidades de datos. Dudo que alcance los 10 000 registros reales en los próximos años.

Si hace alguna diferencia, estamos usando el servidor Microsoft SQL. La aplicación está escrita en C # y usa Linq to SQL.

Actualizar

Gracias, encontré interesantes las respuestas y comentarios existentes. Pero me temo que no entendiste mi pregunta, por lo que contienen lo que quería saber.

No estoy realmente preocupado por la verdadera razón de las altas ID. Si no podemos encontrarlo por nuestra cuenta, podría hacer una pregunta diferente. Lo que me interesa es entender el proceso de decisión en este caso. Para esto, suponga que la aplicación escribirá 1000 registros por día y luego eliminará 9999 de ellos . Estoy casi seguro de que este no es el caso, pero esto es lo que mi jefe creía cuando hizo su pedido. Entonces, en estas circunstancias hipotéticas, ¿cuáles serían las ventajas y desventajas de usar bigint o escribir nuestro propio código que asignará ID (de una manera que reutilice las ID de los registros ya eliminados, para garantizar que no haya vacíos)?

En cuanto a la razón real, sospecho fuertemente que esto se debe a que una vez escribimos código para importar datos de otra base de datos, como prueba de concepto de que una migración posterior se puede hacer en cierta medida. Creo que mi colega realmente creó varios miles de registros durante la importación y luego los eliminó. Tengo que confirmar si este fue realmente el caso, pero si es así, ni siquiera hay necesidad de acción.

rumtscho
fuente
Vea la publicación de SM Ahasan Habib en codeproject.com/Tips/668042/…
RLF
¿Puedes aclarar? ¿Los nuevos ID simplemente obtienen valores> 10000? ¿O es que las nuevas ID tienen huecos de 10000? ¿Y cuántos ID se estiman necesarios en la vida futura de la aplicación?
user2338816
1
Con respecto a la búsqueda de la primera ID no utilizada, hay un capítulo sobre eso precisamente en el libro de Bill Karwin "Antipatterns SQL". Entonces, sí, ¡ciertamente puede verse como un antipatrón!
Thomas Padron-McCarthy

Respuestas:

24

Sin ver el código, es bastante difícil decir de manera concluyente lo que está sucediendo. Aunque, lo más probable es que el IDENTITYvalor se esté almacenando en la memoria caché, lo que provoca brechas en el valor después de reiniciar SQL Server. Consulte /programming/17587094/identity-column-value-suddenly-jumps-to-1001-in-sql-server para obtener algunas buenas respuestas e información al respecto.

Un INTcampo simple puede contener valores de hasta 2,147,483,647. En realidad, puede iniciar el valor de identidad en -2,147,483,648, dando un total de 32 bits de valores. 4 mil millones de valores distintos. Dudo mucho que te quedes sin valores para usar. Asumiendo que su aplicación está consumiendo 1.000 valores para cada fila añadido real, que había necesidad de ser la creación de cerca de 12.000 filas por día todos los días de quedarse sin identificaciones en 6 meses, suponiendo que inició el IDENTITYvalor a 0, y estaban usando un INT. Si usara un BIGINT, tendría que esperar 21 millones de siglos antes de quedarse sin valores si escribiera 12,000 filas por día, consumiendo 1,000 "valores" por fila.

Habiendo dicho todo eso, si quisieras usarlo BIGINTcomo el tipo de datos del campo de identidad, ciertamente no hay nada de malo en eso. Eso le dará, a todos los efectos, un suministro ilimitado de valores para usar. La diferencia de rendimiento entre un INT y un BIGINT es prácticamente inexistente en el hardware moderno de 64 bits, y es altamente preferible sobre, por ejemplo, el uso NEWID()para generar GUID.

Si desea administrar sus propios valores para la columna ID, puede crear una tabla de claves y proporcionar una forma bastante segura de hacerlo utilizando uno de los métodos que se muestran en las respuestas a esta pregunta: Manejo del acceso concurrente a una tabla de claves sin puntos muertos en SQL Server

La otra opción, suponiendo que esté usando SQL Server 2012+, sería usar un SEQUENCEobjeto para obtener valores de ID para la columna. Sin embargo, necesitaría configurar la secuencia para que no almacene valores. Por ejemplo:

CREATE SEQUENCE dbo.MySequence AS INT START WITH -2147483648 INCREMENT BY 1 NO CACHE;

En respuesta a la percepción negativa de su jefe de los números "altos", diría ¿qué diferencia hace? Suponiendo que use un INTcampo, con un IDENTITY, de hecho podría comenzar IDENTITYen 2147483647e "incrementar" el valor en -1. Esto no cambiaría en absoluto el consumo de memoria, el rendimiento o el espacio en disco utilizado, ya que un número de 32 bits es de 4 bytes, sin importar si es 0o no 2147483647. 0en binario es 00000000000000000000000000000000cuando se almacena en un INTcampo con signo de 32 bits . 2147483647es01111111111111111111111111111111- ambos números ocupan exactamente la misma cantidad de espacio, tanto en la memoria como en el disco, y ambos requieren exactamente la misma cantidad de operaciones de CPU para procesar. Es mucho más importante obtener el código de su aplicación diseñado correctamente que obsesionarse con el número real almacenado en un campo clave.

Preguntó sobre los pros y los contras de (a) usar una columna de ID de mayor capacidad, como a BIGINT, o (b) implementar su propia solución para evitar lagunas de ID. Para responder a estas preocupaciones:

  1. BIGINTen lugar de INTcomo el tipo de datos para la columna en cuestión. El uso de a BIGINTrequiere el doble de la cantidad de almacenamiento, tanto en disco como en memoria para la columna misma. Si la columna es el índice de clave principal para la tabla involucrada, todos y cada uno de los índices no agrupados adjuntos a la tabla también almacenarán el BIGINTvalor, al doble del tamaño de una INT, nuevamente en la memoria y en el disco. SQL Server almacena datos en el disco en páginas de 8 KB, donde el número de "filas" por "página" depende del "ancho" de cada fila. Entonces, por ejemplo, si tiene una tabla con 10 columnas, cada una INT, podrá almacenar aproximadamente 160 filas por página. Si esas columnas en su lugarBIGINTcolumnas, solo podrá almacenar 80 filas por página. Para una tabla con un número muy grande de filas, esto claramente significa que la E / S requerida para leer y escribir la tabla será doble en este ejemplo para cualquier número dado de filas. Por supuesto, este es un ejemplo bastante extremo: si tuviera una fila que constara de una sola columna INTo BIGINTcolumna y una sola NCHAR(4000)columna, (simplistamente) obtendría una sola fila por página, ya sea que usara una INTo una BIGINT. En este escenario, no habría mucha diferencia apreciable.

  2. Rodando su propio escenario para evitar vacíos en la columna ID. Debería escribir su código de tal manera que determinar el "próximo" valor de ID a utilizar no entre en conflicto con otras acciones que sucedan en la tabla. Algo a lo largo de las líneas de SELECT TOP(1) [ID] FROM [schema].[table]ingenuidad viene a la mente. ¿Qué sucede si hay varios actores que intentan escribir nuevas filas en la tabla simultáneamente? Dos actores podrían obtener fácilmente el mismo valor, resultando en un conflicto de escritura. Para solucionar este problema, se requiere acceso en serie a la tabla, lo que reduce el rendimiento. Se han escrito muchos artículos sobre este problema; Dejaré que el lector realice una búsqueda sobre ese tema.

La conclusión aquí es: debe comprender sus requisitos y estimar adecuadamente tanto el número de filas como el ancho de las filas, junto con los requisitos de concurrencia de su aplicación. Como de costumbre, depende de ™.

Max Vernon
fuente
44
+1 pero no descartaría los requisitos de espacio de BIGINT. No tanto por el espacio en el disco, sino por la E / S y el espacio desperdiciado en la memoria. Puede compensar mucho de esto usando la compresión de datos, por lo que realmente no siente la peor parte del tipo BIGINT hasta que supera los 2 mil millones. Idealmente, solo solucionarían el problema (dudo en llamarlo un error per se), mientras que a las personas no deberían importarles las brechas, y aunque las personas no deberían reiniciar sus servidores 15 veces al día, tenemos ambos escenarios siendo bastante frecuente, y a menudo en tándem.
Aaron Bertrand
3
Puntos muy válidos, Aaron, como siempre. Tendería a usar un INT de todos modos, ya que BIGINT es una exageración total a menos que esperen una gran cantidad de filas.
Max Vernon
Un tipo de datos BIGINT para una columna de ID no tendrá mucho impacto en la memoria a menos que tenga cientos de miles o más en la memoria al mismo tiempo. Incluso entonces, es probable que sea una pequeña fracción del tamaño total de la fila.
user2338816
2
@ user2338816 ese es el punto: si la tabla se agranda, habrá muchas en la memoria. Y dado que la columna de identidad suele ser la clave de agrupación, también son 4 bytes adicionales para cada fila en cada índice. ¿Importará en cada caso? No. ¿Debería ser ignorado? Absolutamente no. Nadie parece preocuparse por la escalabilidad hasta que sea demasiado tarde.
Aaron Bertrand
3
Aunque si hacer tener una expectativa legítima de que es posible que tenga bigintque probablemente se agradecerá para decidir de antemano que en lugar de tener que añadir esto en una mesa con mil millones de filas.
Martin Smith
6

La tarea principal es encontrar la causa raíz por la cual el valor actual es tan alto.

La explicación más razonable para las versiones de SQL Server anteriores a SQL2012, suponiendo que esté hablando de una base de datos de prueba, sería que hubo una prueba de carga seguida de una limpieza.

A partir de SQL2012, la razón más probable se debe a varios reinicios del motor SQL (como se explica en el primer enlace que proporcionó Max).

Si la brecha es causada por un escenario de prueba, no hay razón para preocuparse desde mi punto de vista. Pero para estar seguro, verificaría los valores de identidad durante el uso normal de la aplicación, así como antes y después de reiniciar el motor.

Es "gracioso" que MS afirme que ambas alternativas (ya sea el indicador de rastreo 272 o el nuevo objeto SEQUENCE) podrían afectar el rendimiento.

Podría ser la mejor solución para usar BIGINT en lugar de INT solo para estar seguro para cubrir las próximas "mejoras" de MS ...

Lmu92
fuente
Probablemente formulé mi pregunta de manera incorrecta, pero no estoy realmente interesado en encontrar la causa. Existe una alta probabilidad de que sea algo que no volverá a aparecer (resultados de una ejecución de prueba) o una mala decisión de diseño en la aplicación, que se puede resolver fuera de la base de datos. El punto era entender por qué un DBA experimentado consideraría que las ID altas son malas o peores que lanzar nuestra propia administración de ID.
rumtscho
2

Rumtscho, si solo está creando 1000 filas por día, hay poco que decidir: use el tipo de datos INT con un campo Identidad y termine con él. Las matemáticas simples dicen que si le da a su aplicación un ciclo de vida de 30 años (poco probable), podría tener 200,000 filas por día y aún estar dentro del rango de número positivo de un tipo de datos INT.

El uso de BigInt es excesivo en su caso, también puede causar problemas si se accede a su aplicación o sus datos a través de ODBC (como traerlos a Excel o MS Access, etc.), Bigint no se traduce bien en la mayoría de los controladores ODBC a las aplicaciones de escritorio.

En cuanto a los GUID, aparte del espacio de disco adicional y la E / S adicional, existe el gran problema de que, por diseño, no son secuenciales, por lo que si forman parte de un índice ordenado, puede adivinar que cada inserción va a requieren que se recurra al índice. --Jim

jimo3
fuente
Buen punto sobre los GUID, a menos que use NEWSEQUENTIALID () - Todavía estoy de acuerdo, no hay una gran razón para usarlos en esta pregunta.
Max Vernon
1

Hay una brecha entre los valores utilizados? ¿O los valores iniciales son 10.000 y a partir de entonces todos suman 1? A veces, si el número se va a dar a los clientes, el número inicial es mayor que cero, digamos 1500 por ejemplo, por lo que el cliente no se da cuenta de que el sistema es "nuevo".

El inconveniente de usar bigint en lugar de smallint es que como bigint usa "más espacio en disco", cuando lee el disco, lee menos bloques de disco para cada disco. Si su espacio de fila es pequeño, esto puede ser un inconveniente, si no es así, no importa mucho. Además, no importa mucho si no está consultando muchos recursos a la vez y si tiene los índices adecuados.

Y como se dijo en otra respuesta, si le preocupa quedarse sin índices, entonces no debe preocuparse, smallint puede manejar a menos que tenga un negocio millonario. Inventar un mecanismo para "recuperar identificadores" es costoso y agrega puntos de falla y complejidad al software.

Saludos

ctutte
fuente
2
El OP está viendo brechas en el reinicio del servicio. Esto se debe a este problema . Además, no creo que una minúscula sea una buena compensación a corto plazo por el trabajo que llevará arreglarla más adelante.
Aaron Bertrand
@AaronBertrand en realidad, me temo que otros malinterpretaron esto cuando sugirieron esta posibilidad. Estoy bastante seguro de que esta no es la causa de los altos números, pero incluso si lo fuera, no estaba tratando de encontrar la causa, sino de saber qué argumentos puede haber a favor y en contra de las soluciones propuestas. Vea mi actualización para más detalles.
rumtscho
@rumtscho en realidad esta respuesta destaca un buen punto, incluso si no aborda directamente su pregunta: "Inventar un mecanismo para 'recuperar identificadores' es costoso y agrega puntos de falla y complejidad al software".
Doktor J
@DoktorJ Estoy de acuerdo contigo. Yo fui la persona que votó por la respuesta :) Solo quería aclarar el malentendido, por eso dejé mi primer comentario.
rumtscho
1

Si yo fuera tu jefe, estaría más interesado en las razones de los valores de Id inesperadamente altos ... tal como lo veo, para cada uno de los dos escenarios que describiste:

  1. SI las pruebas anteriores han aumentado los valores de identidad, entonces sus otros comentarios sobre el número esperado de registros también me obligarían a sugerir un tipo de clave más pequeño. Francamente, también consideraría si fuera posible restablecer la secuencia y volver a numerar los registros existentes si la prueba no fuera de carácter para el uso previsto actual de la tabla (la mayoría consideraría esta exageración: 'depende').

  2. SI la mayoría de los registros escritos en la tabla se eliminan poco después, me inclinaría a considerar usar dos tablas; una tabla temporal donde los registros no se mantienen a largo plazo, y otra donde solo se mantienen registros que crearemos permanentemente. Una vez más, sus expectativas sobre la cantidad de registros a largo plazo me sugieren el uso de un tipo más pequeño para su columna clave, y unos pocos registros por día difícilmente le causarán un problema de rendimiento para 'mover' un registro de una tabla a otra similar. uno. Sospecho que no es su escenario, pero imagine que un sitio web de compras puede preferir mantener un Basket / BasketItem y cuando se realiza un pedido, los datos se mueven al conjunto Order / OrderItem.

Resumir; en mi opinión, los BIGINT no son necesariamente temibles, pero son francamente innecesarios para muchos escenarios. Si la tabla nunca se agranda, nunca se dará cuenta de que hubo una exageración en su elección de tipo ... pero cuando tiene tablas con millones de filas y muchas columnas FK que son GRANDES cuando podrían haber sido más pequeñas, entonces puede desear que los tipos se seleccionaron de forma más conservadora (tenga en cuenta no solo las columnas de teclas, sino todas las columnas de teclas principales y todas las copias de seguridad que guarda, etc.). El espacio en disco no siempre es barato (considere el disco SAN en ubicaciones administradas, es decir, el espacio en disco se alquila).

En esencia, estoy abogando por una cuidadosa consideración de su selección de tipo de datos siempre en lugar de a veces . No siempre predecirá los patrones de uso correctamente, pero creo que tomará mejores decisiones como regla general y luego supondrá que "más grande es mejor". En general, selecciono el tipo más pequeño que puede contener el rango de valores requerido y razonable y felizmente consideraré INT, SMALLINT e incluso TINYINT si creo que es probable que el valor se ajuste a ese tipo en el futuro previsible. Sin embargo, es poco probable que los tipos más pequeños se usen con columnas IDENTITY, pero pueden usarse felizmente con tablas de búsqueda donde los valores clave se configuran manualmente.

Finalmente, las tecnologías que las personas usan pueden influir considerablemente en sus expectativas y respuestas. Es más probable que algunas herramientas causen lagunas en los rangos, por ejemplo, al reservar previamente rangos de identidades por proceso. En contraste, @DocSalvager sugiere una secuencia auditable exhaustiva que parece reflejar el punto de vista de su jefe; Personalmente, nunca he requerido ese nivel de autoridad, aunque la regla general de que las identidades son secuenciales y generalmente sin brechas a menudo me ha sido increíblemente útil en situaciones de apoyo y análisis de problemas.

Nij
fuente
1

¿Cuáles serían los pros y los contras de usar bigint o escribir nuestro propio código que asignará ID (de una manera que reutilice las ID de los registros ya eliminados, para garantizar que no haya vacíos)?

Utilizando bigintcomo identidad y viviendo con las brechas:

  • todo es funcionalidad incorporada
  • puede estar seguro de que funcionará de inmediato
  • desperdiciará espacio ya intque todavía le daría datos de aproximadamente 2 millones de días; se deberán leer y escribir más páginas; los índices pueden hacerse más profundos. (Sin embargo, en estos volúmenes esto no es una preocupación importante).
  • una columna de clave sustituta no tiene sentido, por lo que las brechas están bien. Si se muestra a los usuarios y las brechas se interpretan como significativas, entonces lo estás haciendo mal.

Ruede el suyo:

  • su equipo de desarrollo hará todo el trabajo de desarrollo y corrección de errores para siempre.
  • ¿solo quieres llenar los huecos en la cola o en el medio también? Diseñar decisiones para discutir.
  • cada escritura tendrá que emitir bloqueos fuertes para evitar que procesos concurrentes adquieran la misma nueva ID o resolver conflictos post facto .
  • En el peor de los casos, deberá actualizar cada fila de la tabla para cerrar las brechas si se elimina rowid = 1. Esto afectará la concurrencia y el rendimiento, con todas las actualizaciones de claves externas en cascada, etc.
  • vago o ansioso por llenar huecos? ¿Qué le sucede a la concurrencia mientras esto sucede?
  • Tendrá que leer la nueva ID antes de cualquier escritura = carga adicional.
  • Se necesitará un índice en la columna de identificación para encontrar una brecha eficiente.
Michael Green
fuente
0

Si realmente le preocupa alcanzar el umbral superior de INT para sus PK, considere usar GUID. Sí, sé que son 16 bytes frente a 4 bytes, pero el disco es barato.

Aquí hay una buena reseña de pros y contras.

Tim Goyer
fuente
44
+1 porque esta es una solución, pero vea el comentario de Aaron sobre la respuesta de Max por una razón por la cual "el disco es barato" no es una razón para usar GUID sin sopesar cuidadosamente las opciones.
Jack Douglas
1
Aquí hay una mejor redacción de un experto en arquitectura e índice de SQL Server en lugar de un desarrollador: sqlskills.com/blogs/kimberly/disk-space-is-cheap
Aaron Bertrand
Ah, y por supuesto, cuidado con las divisiones de página de NEWID ()
Max Vernon
1
Mi jefe parece oponerse a los valores altos solo porque se ven altos. Espero que esta pregunta me muestre más posibles objeciones, pero si este es uno de sus principales argumentos, probablemente reaccionaría aún más negativamente a los GUID.
rumtscho
1
@rumtscho Dígale a su jefe que un número sustituto es solo un número sin sentido (el "tamaño" del número es irrelevante) y que las brechas en una secuencia son naturales y en gran medida inevitables.
Aaron Bertrand
0

RDBMS Claves primarias (columna generalmente llamada 'ID')
No se pueden evitar espacios en columnas (campos) de autoincremento RDBMS. Están destinados principalmente a crear PK únicas. Para el rendimiento, los principales productos los asignan en lotes, por lo que los mecanismos de recuperación automática para varios fallos de funcionamiento normales pueden provocar que los números no se usen. Esto es normal.

Secuencias ininterrumpidas
Cuando necesita un número de secuencia ininterrumpida, como suele ser esperado por los usuarios, debe ser una columna separada que se asigne mediante programación y no debe ser la PK. Por lo tanto, esos 1000 registros pueden tener el mismo número en esa columna.

¿Por qué los usuarios quieren secuencias ininterrumpidas?
Los números de secuencia faltantes son el signo más básico de error descubierto en cualquier tipo de auditoría. Este principio de "Contabilidad-101" es omnipresente. Sin embargo, lo que funciona para un pequeño número de registros mantenidos a mano, tiene un grave problema cuando se aplica a un gran número de registros en bases de datos ...

La reutilización de valores clave para registros no relacionados invalida la base de datos El
uso del "primer entero no utilizado" introduce la probabilidad de que en algún momento en el futuro, un número sea reutilizado para registros no relacionados con el original. Eso hace que la base de datos no sea confiable como una representación precisa de los hechos. Esta es la razón principal por la que los mecanismos de autoincremento están diseñados a propósito para nunca reutilizar un valor.

DocSalvager
fuente