Raisons d'éviter les grandes valeurs d'identification


17

Nous travaillons sur une application web, pas encore accessible aux utilisateurs. Mon patron a remarqué que les enregistrements nouvellement créés obtiennent un ID de plus de 10 000, même si nous n'avons que 100 enregistrements dans le tableau. Elle a supposé que l'interface Web, pour une raison quelconque, crée plus de 100 fois plus d'enregistrements temporaires que les enregistrements réels (et les supprime) et que cela peut nous conduire à manquer de portée dans les quelques mois suivant leur publication.

Je ne pense pas qu'elle ait raison sur la cause de l'inflation d'identité (la collègue qui peut répondre à cette question est en vacances, donc nous ne savons pas avec certitude), mais supposons qu'elle l'est. Elle a dit qu'elle détesterait utiliser une colonne bigint et qu'elle aimerait que nous arrêtions l'auto-incrémentation de la colonne ID et écrivions du code côté serveur qui choisit le premier entier "inutilisé" et l'utilise comme ID.

Je suis un étudiant diplômé en informatique avec peu d'expérience pratique, remplissant un rôle de développeur junior. Elle possède des années d'expérience dans la gestion de toutes les bases de données de notre organisation et dans la conception de la plupart d'entre elles. Je pense qu'elle est incorrecte dans ce cas, qu'un ID bigint n'a rien à craindre et que l'imitation de la fonctionnalité SGBD sent un antipattern. Mais je n'ai pas encore confiance en mon jugement.

Quels sont les arguments pour et contre chaque position? Quelles mauvaises choses peuvent se produire si nous utilisons un bigint, et quels sont les dangers de réinventer la fonctionnalité d'auto-incrémentation de la roue ? Existe-t-il une troisième solution meilleure que l'une ou l'autre? Quelles pourraient être ses raisons pour vouloir éviter une inflation des valeurs faciales d'identité? Je suis également intéressé à entendre parler de raisons pragmatiques - peut-être que les identifiants bigint fonctionnent en théorie, mais causent des maux de tête dans la pratique?

L'application ne devrait pas gérer de très grandes quantités de données. Je doute qu'il atteigne 10 000 records réels au cours des prochaines années.

Si cela fait une différence, nous utilisons Microsoft SQL Server. L'application est écrite en C # et utilise Linq to SQL.

Mise à jour

Merci, j'ai trouvé les réponses et commentaires existants intéressants. Mais je crains que vous n'ayez mal compris ma question, ils contiennent donc ce que je voulais savoir.

Je ne suis pas vraiment préoccupé par la vraie raison des ID élevés. Si nous ne pouvons pas le trouver par nous-mêmes, je pourrais poser une autre question. Ce qui m'intéresse, c'est de comprendre le processus de décision dans ce cas. Pour cela, supposez que l'application écrira 1000 enregistrements par jour, puis en supprimera 9999 . Je suis presque sûr que ce n'est pas le cas, mais c'est ce que mon patron pensait quand elle a fait sa demande. Donc, dans ces circonstances hypothétiques, quels seraient les avantages et les inconvénients de l'utilisation de bigint ou de l'écriture de notre propre code qui attribuera les identifiants (de manière à réutiliser les identifiants des enregistrements déjà supprimés, pour s'assurer qu'il n'y a pas de lacunes)?

Quant à la raison réelle, je soupçonne fortement que c'est parce que nous avons écrit du code pour importer des données d'une autre base de données, comme preuve de concept qu'une migration ultérieure peut être effectuée dans une certaine mesure. Je pense que mon collègue a créé plusieurs milliers d'enregistrements lors de l'importation et les a ensuite supprimés. Je dois confirmer si tel était bien le cas, mais si c'est le cas, il n'y a même pas besoin d'agir.


Voir le post de SM Ahasan Habib sur codeproject.com/Tips/668042/…
RLF

Pouvez-vous clarifier? Les nouveaux identifiants obtiennent-ils simplement des valeurs> 10000? Ou est-ce que les nouveaux identifiants ont des lacunes de 10000? Et combien d'ID sont estimés nécessaires dans la future vie de l'application?
user2338816

1
Concernant la recherche du premier ID inutilisé, il y a un chapitre à ce sujet précisément dans le livre de Bill Karwin "SQL Antipatterns". Alors oui, cela peut certainement être vu comme un contre-motif!
Thomas Padron-McCarthy

Réponses:


24

Sans voir le code, il est assez difficile de dire de façon concluante ce qui se passe. Bien que, très probablement, la IDENTITYvaleur soit mise en cache, provoquant des lacunes dans la valeur après le redémarrage de SQL Server. Voir /programming/17587094/identity-column-value-suddenly-jumps-to-1001-in-sql-server pour de bonnes réponses et des informations à ce sujet.

Un INTchamp simple peut contenir des valeurs allant jusqu'à 2 147 483 647. Vous pouvez réellement démarrer la valeur d'identité à -2.147.483.648, ce qui donne un total de 32 bits de valeurs. 4 milliards de valeurs distinctes. Je doute fort que vous n'ayez plus de valeurs à utiliser. En supposant que votre application est la consommation de 1000 valeurs pour chaque ligne réelle ajoutée, vous aurez besoin d'être près de créer 12 000 lignes par jour tous les jours à manquer d'ID dans 6 mois , en supposant que vous avez commencé laIDENTITY valeur à 0, et utilisaient un INT. Si vous utilisez un BIGINT, vous devrez attendre 21 millions de siècles avant de manquer de valeurs si vous écrivez 12 000 lignes par jour, consommant 1 000 «valeurs» par ligne.

Cela dit, si vous souhaitez utiliser BIGINTle type de données de champ d'identité, il n'y a certainement rien de mal à cela. Cela vous donnera à toutes fins utiles, une offre illimitée de valeurs à utiliser. La différence de performances entre un INT et un BIGINT est pratiquement inexistante sur le matériel 64 bits moderne, et très préférable à l'utilisation par exempleNEWID() pour générer des GUID.

Si vous souhaitez gérer vos propres valeurs pour la colonne ID, vous pouvez créer une table de clés et fournir un moyen assez résistant de le faire en utilisant l'une des méthodes indiquées dans les réponses à cette question: gérer l'accès simultané à une table de clés sans blocages dans SQL Server

L'autre option, en supposant que vous utilisez SQL Server 2012+, serait d'utiliser un SEQUENCEobjet pour obtenir des valeurs d'ID pour la colonne. Cependant, vous devez configurer la séquence pour ne pas mettre en cache les valeurs. Par exemple:

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

En réponse à la perception négative par votre patron des nombres "élevés", je dirais quelle différence cela fait-il? En supposant que vous utilisiez un INTchamp, avec un IDENTITY, vous pourriez en fait démarrer IDENTITYà 2147483647et "incrémenter" la valeur de -1. Cela ne changerait absolument rien à la consommation de mémoire, aux performances ou à l'espace disque utilisé car un nombre 32 bits est de 4 octets, qu'il s'agisse de 0ou 2147483647. 0en binaire est 00000000000000000000000000000000lorsqu'il est stocké dans un INTchamp signé 32 bits . 2147483647est01111111111111111111111111111111- les deux nombres occupent précisément la même quantité d'espace, à la fois en mémoire et sur disque, et tous deux nécessitent précisément la même quantité d'opérations CPU pour être traitées. Il est beaucoup plus important de concevoir correctement votre code d'application que de vous obséder sur le nombre réel stocké dans un champ clé.

Vous avez demandé les avantages et les inconvénients de (a) l'utilisation d'une colonne d'identification de plus grande capacité, telle que a BIGINT, ou (b) de rouler votre propre solution pour éviter les lacunes d'identification. Pour répondre à ces préoccupations:

  1. BIGINTau lieu de INTcomme type de données pour la colonne en question. L'utilisation d'un BIGINTrequiert deux fois plus de stockage, sur disque et en mémoire pour la colonne elle-même. Si la colonne est l'index de clé primaire de la table concernée, chaque index non cluster attaché à la table stockera également la BIGINTvaleur, à deux fois la taille d'uneINT , à la fois en mémoire et sur disque. SQL Server stocke les données sur disque dans des pages de 8 Ko, où le nombre de "lignes" par "page" dépend de la "largeur" ​​de chaque ligne. Ainsi, par exemple, si vous avez un tableau avec 10 colonnes, chacune une INT, vous pourrez approximativement stocker 160 lignes par page. Si ces colonnes où à la placeBIGINTcolonnes, vous ne pourrez stocker que 80 lignes par page. Pour une table avec un très grand nombre de lignes, cela signifie clairement que les E / S nécessaires pour lire et écrire la table seront doublées dans cet exemple pour un nombre donné de lignes. Certes, c'est un exemple assez extrême - si vous aviez une ligne composée d'une seule INTou BIGINTcolonne et d'une seule NCHAR(4000)colonne, vous obtiendriez (de manière simplifiée) une seule ligne par page, que vous utilisiez un INTou un BIGINT. Dans ce scénario, cela ne ferait pas beaucoup de différence appréciable.

  2. Rouler votre propre scénario pour éviter les lacunes dans la colonne ID. Vous devez écrire votre code de manière à ce que la détermination de la "prochaine" valeur d'ID à utiliser n'entre pas en conflit avec les autres actions qui se produisent dans la table. Quelque chose dans le sens de la SELECT TOP(1) [ID] FROM [schema].[table]naïveté me vient à l'esprit. Que faire si plusieurs acteurs tentent d'écrire simultanément de nouvelles lignes dans la table? Deux acteurs pourraient facilement obtenir la même valeur, entraînant un conflit d'écriture. Pour contourner ce problème, vous devez sérialiser l'accès à la table, ce qui réduit les performances. De nombreux articles ont été écrits sur ce problème; Je laisse au lecteur le soin d'effectuer une recherche sur ce sujet.

La conclusion est la suivante: vous devez comprendre vos besoins et estimer correctement à la fois le nombre de lignes et la largeur des lignes, ainsi que les exigences de concurrence de votre application. Comme d'habitude, It Depends ™.


4
+1 mais je n'écarterais pas les besoins d'espace de BIGINT. Pas tant pour l'espace disque que pour les E / S et l'espace gaspillé en mémoire. Vous pouvez compenser une grande partie de cela en utilisant la compression de données, de sorte que vous ne ressentez pas vraiment le poids du type BIGINT jusqu'à ce que vous dépassiez les 2 milliards. Idéalement, ils devraient simplement résoudre le problème (j'hésite à appeler cela un bug en soi) - alors que les gens ne devraient pas se soucier des lacunes et que les gens ne devraient pas redémarrer leurs serveurs 15 fois par jour, nous avons ces deux scénarios étant assez répandue, et souvent en tandem.
Aaron Bertrand

3
Des points très valables, Aaron, comme d'habitude. J'aurais tendance à utiliser un INT de toute façon, car BIGINT est à peu près exagéré, à moins qu'ils n'attendent un grand nombre de lignes.
Max Vernon

Un type de données BIGINT pour une colonne ID n'aura pas beaucoup d'impact sur la mémoire à moins que vous n'en ayez des centaines de milliers ou plus en même temps. Même dans ce cas, il est probable qu'il s'agisse d'une petite fraction de la taille totale des lignes.
user2338816

2
@ user2338816 c'est le point - si la table devient grande, il y en aura beaucoup en mémoire. Et puisque la colonne d'identité est généralement la clé de clustering, cela représente également 4 octets supplémentaires pour chaque ligne de chaque index. Sera-ce important dans chaque cas? Non. Faut-il l'ignorer? Absolument pas. Personne ne semble donner une idée de l'évolutivité jusqu'à ce qu'il soit trop tard.
Aaron Bertrand

3
Bien que si vous n'avez une attente légitime que vous devrez peut - être vous remercier probablement vous pour décider que l' avance plutôt que de devoir ajouter ceci à une table avec des milliards de lignes. bigint
Martin Smith

6

La tâche principale à faire est de trouver la cause première pour laquelle la valeur actuelle est si élevée.

L'explication la plus raisonnable pour les versions de SQL Server avant SQL2012 - en supposant que vous parlez d'une base de données de test - serait qu'il y avait un test de charge suivi d'un nettoyage.

À partir de SQL2012, la raison la plus probable est due à plusieurs redémarrages du moteur SQL (comme expliqué dans le premier lien fourni par Max).

Si l'écart est provoqué par un scénario de test, il n'y a aucune raison de s'inquiéter de mon point de vue. Mais pour être prudent, je vérifierais les valeurs d'identité pendant l'utilisation normale de l'application ainsi qu'avant et après un redémarrage du moteur.

Il est "drôle" que MS déclare que les deux alternatives (soit l'indicateur de trace 272 ou le nouvel objet SEQUENCE) peuvent avoir un impact sur les performances.

Ce pourrait être la meilleure solution pour utiliser BIGINT au lieu de INT juste pour être du bon côté pour couvrir les prochaines "améliorations" de MS ...


J'ai probablement formulé ma question dans le mauvais sens, mais je ne suis pas vraiment intéressé à trouver la cause. Il y a une forte probabilité que ce soit quelque chose qui n'apparaîtra plus (résultats d'un test), ou une mauvaise décision de conception dans l'application, qui peut être résolue en dehors de la base de données. Le but était de comprendre pourquoi un DBA expérimenté considérerait les ID élevés comme mauvais ou pire que de rouler notre propre gestion des ID.
rumtscho

2

Rumtscho, Si vous ne créez que 1 000 lignes par jour, il n'y a pas grand-chose à décider - utilisez le type de données INT avec un champ Identité et finissez-en. Des calculs simples indiquent que si vous donnez à votre application un cycle de vie de 30 ans (peu probable), vous pourriez avoir 200 000 lignes par jour et rester dans la plage de nombres positifs d'un type de données INT.

L'utilisation de BigInt est exagérée dans votre cas, cela peut également entraîner des problèmes si votre application ou vos données seront accessibles via ODBC (telles que celles importées dans Excel ou MS Access, etc.), Bigint ne se traduit pas bien sur la plupart des pilotes ODBC en applications de bureau.

Quant aux GUIDS, à part l'espace disque supplémentaire et les E / S supplémentaires, il y a l'énorme problème qu'ils ne sont pas séquentiels par conception, donc s'ils font partie d'un index trié, eh bien vous pouvez deviner que chaque insertion va exiger que l'index soit utilisé. - Jim


Bon point sur les GUID, sauf si vous utilisez NEWSEQUENTIALID () - Je suis toujours d'accord, il n'y a pas de grande raison de les utiliser apparentes dans cette question.
Max Vernon

1

Il y a un écart entre les valeurs utilisées? Ou les valeurs de départ sont 10 000 et à partir de là, tous ajoutent 1? Parfois, si le numéro doit être donné aux clients, le nombre initial est supérieur à zéro, disons 1500 par exemple, de sorte que le client ne se rend pas compte que le système est "nouveau".

L'inconvénient de l'utilisation de bigint au lieu de smallint est que bigint utilise "plus d'espace disque", lorsque vous lisez un disque, vous lisez moins de blocs de disque pour chaque disque. Si votre espace de rangée est petit, cela peut être un inconvénient, sinon, cela n'a pas beaucoup d'importance. De plus, cela n'a pas beaucoup d'importance si vous ne recherchez pas beaucoup de ressources à la fois et si vous disposez des index appropriés.

Et comme dit dans d'autres réponses, si vous craignez de manquer d'index, alors ne vous inquiétez pas, smallint peut gérer à moins que vous n'ayez une entreprise millionnaire. Inventer un mécanisme pour «récupérer les identifiants» coûte cher et ajoute des points de défaillance et de la complexité au logiciel.

Cordialement


2
L'OP constate des lacunes lors du redémarrage du service. C'est à cause de ce problème . Je ne pense pas non plus qu'un petit point soit un bon compromis à court terme pour le travail qu'il faudra pour le réparer plus tard.
Aaron Bertrand

@AaronBertrand en fait, je crains que d'autres n'aient mal compris cela lorsqu'ils ont suggéré cette possibilité. Je suis sûr que ce n'est pas la cause des chiffres élevés, mais même si c'était le cas, je n'essayais pas de trouver la cause, mais d'apprendre quels arguments il peut y avoir pour et contre les solutions proposées. Voir ma mise à jour pour plus de détails.
rumtscho

@rumtscho cette réponse met en évidence un bon point même si elle ne répond pas directement à votre question: "Inventer un mécanisme pour" récupérer les identifiants "coûte cher et ajoute des points de défaillance et de la complexité au logiciel."
Doktor J

@DoktorJ Je suis d'accord avec vous. J'étais la personne qui a voté pour la réponse :) Je voulais juste clarifier le malentendu, c'est pourquoi j'ai laissé mon premier commentaire.
rumtscho

1

Si j'étais votre patron, je serais plus intéressé par les raisons des valeurs d'ID étonnamment élevées ... la façon dont je le vois, pour chacun des deux scénarios que vous avez décrits:

  1. SI les tests antérieurs ont augmenté les valeurs d'identité - alors vos autres commentaires sur le nombre prévu d'enregistrements me pousseraient également à suggérer un type de clé plus petit. Franchement, je considérerais également s'il était possible de réinitialiser la séquence et de renuméroter les enregistrements existants si le test était hors caractère pour l'utilisation prévue actuelle de la table (la plupart considéreraient cette exagération - `` cela dépend '').

  2. SI la majorité des enregistrements écrits dans la table sont supprimés peu de temps après, je serais enclin à envisager d'utiliser deux tables à la place; une table temporaire où les enregistrements ne sont pas conservés à long terme et une autre où seuls les enregistrements que nous créerons en permanence sont conservés. Encore une fois, vos attentes en matière de nombre d'enregistrements à long terme me suggèrent l'utilisation d'un type plus petit pour votre colonne clé, et quelques enregistrements par jour ne vous causeront guère de problème de performances pour «déplacer» un enregistrement d'une table à une autre similaire un. Je soupçonne que ce n'est pas votre scénario, mais imaginez qu'un site Web d'achat préfère maintenir un panier / BasketItem et lorsqu'une commande est réellement passée, les données sont déplacées dans l'ensemble Order / OrderItem.

Pour résumer; à mon avis, les BIGINT ne sont pas nécessairement à craindre, mais sont franchement inutilement grands pour de nombreux scénarios. Si la table ne devient jamais grande, vous ne vous rendrez jamais compte que votre choix de type était excessif ... mais lorsque vous avez des tables avec des millions de lignes et de nombreuses colonnes FK qui sont BIGINT alors qu'elles auraient pu être plus petites - alors vous pouvez souhaiter que le les types avaient été sélectionnés de manière plus conservatrice (considérez non seulement les colonnes de clés, mais toutes les colonnes de clés avant, et toutes les sauvegardes que vous conservez, et ainsi de suite!). L'espace disque n'est pas toujours bon marché (pensez au disque SAN aux emplacements gérés - c'est-à-dire que l'espace disque est loué).

En substance, je plaide pour un examen attentif de votre sélection de type de données toujours plutôt que parfois . Vous ne prédirez pas toujours correctement les modèles d'utilisation, mais je pense que vous prendrez de meilleures décisions en règle générale, puis en supposant toujours que «plus c'est gros, mieux c'est». En général, je sélectionne le plus petit type pouvant contenir la plage de valeurs requise et raisonnable et je considérerai avec plaisir INT, SMALLINT et même TINYINT si je pense que la valeur est susceptible de correspondre à ce type dans un avenir prévisible. Il est peu probable que les types plus petits soient utilisés avec des colonnes IDENTITY, mais peuvent être utilisés avec bonheur avec des tables de recherche où les valeurs de clé sont définies manuellement.

Enfin, les technologies que les gens utilisent peuvent influencer considérablement leurs attentes et leurs réponses. Certains outils sont plus susceptibles de provoquer des lacunes dans les plages, par exemple en pré-réservant des plages d'identités par processus. En revanche, @DocSalvager suggère une séquence vérifiable approfondie qui semble refléter le point de vue de votre patron; Personnellement, je n'ai jamais exigé un tel niveau d'autorité - bien que la règle générale selon laquelle les identités sont séquentielles et généralement sans lacunes m'a souvent été extrêmement utile dans les situations de soutien et l'analyse des problèmes.


1

quels seraient les avantages et les inconvénients de l'utilisation de bigint ou de l'écriture de notre propre code qui attribuera les identifiants (de manière à réutiliser les identifiants des enregistrements déjà supprimés, pour garantir qu'il n'y a pas de lacunes)?

Utiliser bigintcomme identité et vivre avec les lacunes:

  • c'est toutes les fonctionnalités intégrées
  • vous pouvez être sûr que cela fonctionnera prêt à l'emploi
  • cela gaspillera de l'espace car vous intfournirait encore environ 2 millions de jours de données; plus de pages devront être lues et écrites; les index peuvent devenir plus profonds. (Cependant, à ces volumes, ce n'est pas une préoccupation importante).
  • une colonne clé de substitution est censée être dénuée de sens, les lacunes sont donc correctes. Si cela est montré aux utilisateurs et que les lacunes sont interprétées comme importantes, vous vous trompez.

Roulez le vôtre:

  • votre équipe de développement fera pour toujours tout le développement et la correction des bogues.
  • voulez-vous simplement combler les lacunes à la queue ou au milieu, aussi? Concevoir des décisions à discuter.
  • chaque écriture devra émettre des verrous solides pour empêcher les processus simultanés d'acquérir le même nouvel ID ou de résoudre les conflits a posteriori .
  • dans le pire des cas, vous devrez mettre à jour chaque ligne du tableau pour combler les lacunes si rowid = 1 est supprimé. Cela martèlera la concurrence et les performances, avec toutes les mises à jour de clés étrangères en cascade, etc.
  • paresseux ou désireux de combler les lacunes? Qu'arrive-t-il à la concurrence pendant que cela se produit?
  • vous devrez lire le nouvel ID avant toute écriture = charge supplémentaire.
  • un index sera nécessaire sur la colonne id pour trouver efficacement l'écart.

0

Si vous êtes vraiment inquiet d'atteindre le seuil supérieur d'INT pour vos PK, pensez à utiliser des GUID. Oui, je sais que c'est 16 octets contre 4 octets, mais le disque est bon marché.

Voici une bonne description des avantages et des inconvénients.


4
+1 parce que c'est une solution, mais voir le commentaire d'Aaron sur la réponse de Max pour une raison pour laquelle "le disque est bon marché" n'est pas une raison pour utiliser des GUID sans peser soigneusement les options.
Jack Douglas

1
Voici une meilleure rédaction d'un expert en architecture et index SQL Server plutôt qu'un développeur: sqlskills.com/blogs/kimberly/disk-space-is-cheap
Aaron Bertrand

Oh, et bien sûr, méfiez-vous des sauts de page de NEWID ()
Max Vernon

1
Mon patron ne semble s'opposer aux valeurs élevées qu'en raison de leur apparence élevée. J'espère que cette question me montrera plus d'objections possibles, mais si c'est l'un de ses principaux arguments, elle réagirait probablement encore plus négativement aux GUID.
rumtscho

1
@rumtscho Dites à votre patron qu'un numéro de substitution est juste un nombre sans signification (la "taille" du nombre n'est pas pertinent) et que les lacunes dans une séquence sont naturelles et largement inévitables.
Aaron Bertrand

0

Clés primaires du SGBDR (colonne généralement nommée 'ID') Les
lacunes ne peuvent pas être évitées dans les colonnes (champs) d'auto-incrémentation du SGBDR. Ils sont principalement destinés à créer des PK uniques. Pour les performances, les principaux produits les répartissent par lots, de sorte que les mécanismes de récupération automatique pour divers problèmes de fonctionnement normal peuvent entraîner la non-utilisation de nombres. C'est normal.

Séquences ininterrompues
Lorsque vous avez besoin d'un numéro de séquence ininterrompu, comme cela est souvent attendu par les utilisateurs, il doit s'agir d'une colonne distincte affectée par programme et non du PK. Ainsi, ces 1000 enregistrements peuvent tous avoir le même numéro dans cette colonne.

Pourquoi les utilisateurs veulent-ils des séquences ininterrompues?
Les numéros de séquence manquants sont le signe d'erreur le plus élémentaire découvert dans tout type d'audit. Ce principe de "comptabilité-101" est omniprésent. Cependant, ce qui fonctionne pour un petit nombre d'enregistrements conservés à la main, pose un sérieux problème lorsqu'il est appliqué à un très grand nombre d'enregistrements dans des bases de données ...

La réutilisation des valeurs clés pour les enregistrements non liés invalide la base de données. L'
utilisation du "premier entier non utilisé" introduit la probabilité qu'à un certain moment dans le futur, un nombre soit réutilisé pour des enregistrements sans rapport avec l'original. Cela rend la base de données peu fiable en tant que représentation exacte des faits. C'est la raison principale pour laquelle les mécanismes d'auto-incrémentation sont délibérément conçus pour ne jamais réutiliser une valeur.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.