web-dev-qa-db-fra.com

SQL Server peut-il créer des collisions dans les noms de contraintes générés par le système?

J'ai une application qui crée des millions de tables dans une base de données SQL Server 2008 (non en cluster). Je cherche à mettre à niveau vers SQL Server 2014 (en cluster), mais je frappe un message d'erreur lorsque sous charge:

"Il existe déjà un objet nommé" PK__tablenameprefix__179E2ED8F259C33B "dans la base de données"

Il s'agit d'un nom de contrainte généré par le système. Il ressemble à un nombre 64 bits généré de manière aléatoire. Est-il possible que je constate des collisions en raison du grand nombre de tables? En supposant que j'ai 100 millions de tables, je calcule moins d'une chance de collision de 1 sur 1 trillion lors de l'ajout de la table suivante, mais cela suppose une distribution uniforme. Est-il possible que SQL Server ait changé son algorithme de génération de nom entre la version 2008 et 2014 pour augmenter les chances de collision?

L'autre différence significative est que mon instance de 2014 est une paire en cluster, mais j'ai du mal à formuler une hypothèse pour laquelle cela générerait l'erreur ci-dessus.

P.S. Oui, je sais que créer des millions de tables est fou. Il s'agit d'un code tiers de boîte noire sur lequel je n'ai aucun contrôle. Malgré la folie, cela a fonctionné dans la version 2008 et maintenant pas dans la version 2014.

Edit: en y regardant de plus près, le suffixe généré semble toujours commencer par 179E2ED8 - ce qui signifie que la partie aléatoire n'est en fait qu'un nombre de 32 bits et que les chances de collision ne sont que de 1 sur 50 chaque fois qu'un nouveau tableau est ajouté, ce qui correspond beaucoup plus au taux d'erreur que je constate!

14
jl6

SQL Server peut-il créer des collisions dans les noms de contraintes générés par le système?

Cela dépend du type de contrainte et de la version de SQL Server.

CREATE TABLE T1
(
A INT PRIMARY KEY CHECK (A > 0),
B INT DEFAULT -1 REFERENCES T1,
C INT UNIQUE,
CHECK (C > A)
)

SELECT name, 
       object_id, 
       CAST(object_id AS binary(4)) as object_id_hex,
       CAST(CASE WHEN object_id >= 16000057  THEN object_id -16000057 ELSE object_id +2131483591 END AS BINARY(4)) AS object_id_offset_hex
FROM sys.objects
WHERE parent_object_id = OBJECT_ID('T1')
ORDER BY name;

drop table T1

Exemple de résultats 2008

+--------------------------+-----------+---------------+----------------------+
|           name           | object_id | object_id_hex | object_id_offset_hex |
+--------------------------+-----------+---------------+----------------------+
| CK__T1__1D498357         | 491357015 | 0x1D498357    | 0x1C555F1E           |
| CK__T1__A__1A6D16AC      | 443356844 | 0x1A6D16AC    | 0x1978F273           |
| DF__T1__B__1B613AE5      | 459356901 | 0x1B613AE5    | 0x1A6D16AC           |
| FK__T1__B__1C555F1E      | 475356958 | 0x1C555F1E    | 0x1B613AE5           |
| PK__T1__3BD019AE15A8618F | 379356616 | 0x169C85C8    | 0x15A8618F           |
| UQ__T1__3BD019A91884CE3A | 427356787 | 0x1978F273    | 0x1884CE3A           |
+--------------------------+-----------+---------------+----------------------+

Exemple de résultats 2017

+--------------------------+------------+---------------+----------------------+
|           name           | object_id  | object_id_hex | object_id_offset_hex |
+--------------------------+------------+---------------+----------------------+
| CK__T1__59FA5E80         | 1509580416 | 0x59FA5E80    | 0x59063A47           |
| CK__T1__A__571DF1D5      | 1461580245 | 0x571DF1D5    | 0x5629CD9C           |
| DF__T1__B__5812160E      | 1477580302 | 0x5812160E    | 0x571DF1D5           |
| FK__T1__B__59063A47      | 1493580359 | 0x59063A47    | 0x5812160E           |
| PK__T1__3BD019AE0A4A6932 | 1429580131 | 0x5535A963    | 0x5441852A           |
| UQ__T1__3BD019A981F522E0 | 1445580188 | 0x5629CD9C    | 0x5535A963           |
+--------------------------+------------+---------------+----------------------+

Pour les contraintes par défaut, les contraintes de vérification et les contraintes de clé étrangère, les 4 derniers octets du nom généré automatiquement sont une version hexadécimale de l'ID d'objet de la contrainte. Comme objectid est garanti unique, le nom doit également être unique. Dans Sybase aussi ceux-ci utilisent tabname_colname_objectid

Pour les contraintes uniques et les contraintes de clé primaire que Sybase utilise

tabname_colname_tabindid, où tabindid est une concaténation de chaîne de l'ID de table et de l'ID d'index

Cela aussi garantirait l'unicité.

SQL Server n'utilise pas ce schéma.

Dans SQL Server 2008 et 2017, il utilise une chaîne de 8 octets à la fin du nom généré par le système, mais l'algorithme a changé la façon dont les 4 derniers octets sont générés.

En 2008, les 4 derniers octets représentent un compteur d'entier signé qui est décalé de object_id Par -16000057 Avec une valeur négative enveloppant autour de max signé int. (La signification de 16000057 Est qu'il s'agit de l'incrément appliqué entre object_id Créé successivement ). Cela garantit toujours l'unicité.

À partir de 2012, je ne vois aucun modèle entre l'id_objet de la contrainte et l'entier obtenu en traitant les 8 derniers caractères du nom comme la représentation hexadécimale d'un int signé.

Les noms de fonction dans la pile d'appels en 2017 montrent qu'elle crée maintenant un GUID dans le cadre du processus de génération de nom (en 2008, je ne vois aucune mention de MDConstraintNameGenerator). Je suppose c'est pour fournir une certaine source d'aléatoire. Clairement, il n'utilise pas les 16 octets entiers du GUID dans ces 4 octets qui changent entre les contraintes cependant.

enter link description here

Je suppose que le nouvel algorithme a été fait pour une raison d'efficacité au détriment de la possibilité accrue de collisions dans des cas extrêmes tels que le vôtre.

Il s'agit d'un cas assez pathologique car il nécessite que le préfixe du nom de table et le nom de colonne du PK (dans la mesure où cela affecte les 8 caractères précédant le 8 final) soient identiques pour des dizaines de milliers de tableaux avant qu'il ne devienne probable mais peuvent être reproduits assez facilement avec ci-dessous.

CREATE OR ALTER PROC #P
AS
    SET NOCOUNT ON;

    DECLARE @I INT = 0;


    WHILE 1 = 1
      BEGIN
          EXEC ('CREATE TABLE abcdefghijklmnopqrstuvwxyz' + @I + '(C INT PRIMARY KEY)');
          SET @I +=1;
      END 

GO

EXEC #P

Un exemple exécuté sur SQL Server 2017 sur une base de données nouvellement créée a échoué en un peu plus d'une minute (après la création de 50 931 tables)

Msg 2714, niveau 16, état 30, ligne 15 Il existe déjà un objet nommé "PK__abcdefgh__3BD019A8175067CE" dans la base de données. Msg 1750, niveau 16, état 1, ligne 15 Impossible de créer une contrainte ou un index. Voir les erreurs précédentes.

16
Martin Smith

En supposant que j'ai 100 millions de tables, je calcule moins d'une chance sur 1 billion de collision

N'oubliez pas qu'il s'agit du " problème d'anniversaire ". Vous n'essayez pas de générer une collision pour un seul hachage donné, mais plutôt de mesurer la probabilité qu'aucune des nombreuses paires de valeurs n'entre en collision.

Donc avec N tables, il y a N * (N-1)/2 paires, donc ici environ 1016 paires. Si la probabilité d'une collision est de 2-64, la probabilité qu'une seule paire ne se heurte pas est de 1 à 2-64, mais avec autant de paires, la probabilité de ne pas avoir de collisions ici est d'environ (1-2-64)dix16, ou plus comme 1/10 000. Voir par exemple https://preshing.com/20110504/hash-collision-probabilities/

Et si ce n'est qu'un hachage 32 bits, la probabilité d'une collision croise 1/2 à seulement 77k valeurs.

11