Choix du model Django: IntegerField vs CharField

TL; DR : J'ai une table avec des millions d'instances et je me request comment l'indexer.

J'ai un projet Django qui utilise SQL Server comme backend de database.

Après avoir eu un model avec environ 14 millions d'instances dans l'environnement de production, j'ai réalisé que j'avais des problèmes de performance:

class UserEvent(models.Model) A_EVENT = 'A' B_EVENT = 'B' types = ( (A_EVENT, 'Event A'), (B_EVENT, 'Event B') ) event_type = models.CharField(max_length=1, choices=types) contract = models.ForeignKey(Contract) # field_x = (...) # field_y = (...) 

J'utilise beaucoup de requêtes basées dans ce domaine, et c'est très inefficace, puisque le champ n'est pas indexé. Le filtrage du model à l'aide de ce champ prend presque 7 secondes, alors que l'interrogation par une key étrangère indexée ne pose pas de problèmes de performances:

 UserEvent.objects.filter(event_type=UserEvent.B_EVENT).count() # elapsed time: 0:00:06.921287 UserEvent.objects.filter(contract_id=62).count() # elapsed time: 0:00:00.344261 

Quand j'ai réalisé cela, je me suis aussi posé une question: "Ce champ ne devrait-il pas être un SmallIntegerField? Comme je n'ai qu'un petit choix, les requêtes basées sur des champs entiers sont plus efficaces que les requêtes textuelles / varchar."

Donc, d'après ce que je comprends, j'ai deux options *:

* Je me rends count qu'une troisième option peut exister, puisque l' indexing des champs avec peu de cardinalité ne peut pas causer de sérieuses améliorations , mais comme mes valeurs ont une dissortingbution de [1% -99%] (et je cherche la partie 1%), indexing ce champ semble être une option valide.

  • A) Il suffit d'indexer ce champ et de le laisser en tant que champ CharField.

     A_EVENT = 'A' B_EVENT = 'B' types = ( (A_EVENT, 'Event A'), (B_EVENT, 'Event B') ) event_type = models.CharField(max_length=1, choices=types, db_index=True) 
  • B) Effectuer une migration pour transformer ce champ dans un SmallIntegerField (je ne veux pas qu'il soit un BooleanField, car il peut être possible d'append plus d'options au champ), puis indexer le champ.

     A_EVENT = 1 B_EVENT = 2 types = ( (A_EVENT, 'Event A'), (B_EVENT, 'Event B') ) event_type = models.SmallIntegerField(choices=types, db_index=True) 

Option A

Avantages: Simplicité

Inconvénients: les index basés sur CharField sont less efficaces que les index basés sur Integer

Option B

Avantages: Les index basés sur des nombres entiers sont plus efficaces que les index basés sur CharField

Inconvénients: Je dois effectuer une opération complexe:

  1. Migration de schéma pour créer un nouveau SmallIntegerField
  2. Migration de données copiant (et transformant) les millions d'instances de l'ancien champ vers le nouveau champ.
  3. Mettez à jour le code de projet pour utiliser le nouveau champ ou effectuez une autre migration de schéma pour renommer le nouveau champ comme le précédent.
  4. Supprimer l'ancien champ.

En résumé, la vraie question ici est:

L'amélioration des performances que j'obtiens en migrant le terrain vers un SmallIntegerField vaut le risque?

Je suis penché pour essayer l'option A et vérifier si les améliorations de performance sont adéquates.


J'ai aussi posé cette question à StackOverflow parce qu'une question plus générique s'est posée:

  • Y at-il une situation où l'utilisation de CharFields selon les choix de Django est une meilleure option que l'utilisation de Boolean / Integer / SmallIntegerField?

Cette situation a été créée parce que lors de la définition des templates de projet, j'ai été inspiré par l' extrait de code de la documentation de Django :

 YEAR_IN_SCHOOL_CHOICES = ( ('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ) year_in_school = models.CharField(max_length=2, choices=YEAR_IN_SCHOOL_CHOICES, default=FRESHMAN) 

Pourquoi utilisent-ils des caractères quand ils pourraient utiliser des entiers, puisque c'est juste une représentation de valeur qui ne devrait jamais être montrée?

Vitesse de requêtes de nombre.

 UserEvent.objects.filter(event_type=UserEvent.B_EVENT).count() # elapsed time: 0:00:06.921287 

Les requêtes de cette nature seront malheureusement toujours lentes dans les bases de données lorsque la table contient un grand nombre d'inputs.

Mysql optimise les requêtes de count en regardant l'index à condition que les colonnes indexées soient numériques . C'est donc une bonne raison d'utiliser SmallIntegeField au lieu de Charfield si vous étiez sur mysql mais apparemment vous ne l'êtes pas. Votre kilométrage varie avec d'autres bases de données. Je ne suis pas un expert sur le server SQL, mais je crois comprendre qu'il est particulièrement difficile d'utiliser des index sur COUNT (*).

Partitionnement

Vous pourriez être en mesure d'améliorer les performances globales des requêtes impliquant event_type en partitionnant datatables. Étant donné que la cardinalité de l'index actuel est faible, il est souvent préférable que le planificateur effectue une parsing de table complète. Si datatables ont été partitionnées, seule cette partition particulière doit être analysée.

Char ou Smallint

Qui occupe plus d'espace char (2) ou petit int? La réponse est que cela dépend de votre jeu de caractères. Si le jeu de caractères ne requirejs qu'un seul octet par caractère petit entier et char (2) prendrait la même quantité d'espace. Puisque le champ va avoir une cardinalité très basse, utiliser char ou smallint ne fera pas de différence significative dans ce cas.