Index sur plusieurs colonnes dans Ruby on Rails

97

J'implémente des fonctionnalités pour suivre les articles qu'un utilisateur a lus.

  create_table "article", :force => true do |t|
    t.string   "title"
    t.text     "content"
  end

C'est ma migration jusqu'à présent:

create_table :user_views do |t|
  t.integer :user_id
  t.integer :article_id
end

La table user_views sera toujours interrogée pour rechercher les deux colonnes, jamais une seule. Ma question est de savoir à quoi devrait ressembler mon index. Y a-t-il une différence dans l'ordre de ces tableaux, devrait-il y avoir plus d'options ou quoi que ce soit? Ma base de données cible est Postgres.

add_index(:user_views, [:article_id, :user_id])

Merci.

MISE À JOUR:
Parce qu'une seule ligne contenant les mêmes valeurs dans les deux colonnes peut exister (car pour savoir si user_id HAS a lu article_id), dois-je considérer l'option: unique? Si je ne me trompe pas, cela signifie que je n'ai pas à faire de vérification par moi-même et à simplement faire une insertion chaque fois qu'un utilisateur visite un article.

Emil Ahlbäck
la source
"La table user_views sera toujours interrogée pour rechercher les deux colonnes, jamais une seule." - il n'y aura jamais de requête «trouver tous les articles que cet utilisateur a consultés» ou «trouver tous les utilisateurs qui ont consulté cet article»? Je trouve cela surprenant.
David Aldridge

Réponses:

215

L'ordre importe dans l'indexation.

  1. Mettez en premier le champ le plus sélectif, c'est-à-dire le champ qui réduit le plus rapidement le nombre de lignes.
  2. L'index ne sera utilisé que dans la mesure où vous utiliserez ses colonnes dans l'ordre en commençant par le début . ie si vous indexez sur [:user_id, :article_id], vous pouvez effectuer une requête rapide sur user_idou user_id AND article_id, mais PAS sur article_id.

Votre add_indexligne de migration doit ressembler à ceci:

add_index :user_views, [:user_id, :article_id]

Question concernant l'option 'unique'

Un moyen simple de le faire dans Rails consiste à utiliser validatesdans votre modèle avec la portée uniquenesssuivante ( documentation ):

validates :user, uniqueness: { scope: :article }
sscirrus
la source
7
L'ordre compte énormément dans l'indexation. Placez les clauses where à gauche et complétez l'index avec les colonnes de classement à droite. stackoverflow.com/questions/6098616/dos-and-donts-for-indexes
Denis de Bernardy
1
Notez que validates_uniqueness_of(et son cousin, validates uniqueness:) sont sujets aux conditions de course
Ben Aubin
1
Comme mentionné dans les commentaires ci-dessus et stackoverflow.com/a/1449466/5157706 et stackoverflow.com/a/22816105/5157706 , envisagez également d'ajouter un index unique sur la base de données.
Akash Agarwal
25

Juste un avertissement sur la vérification de l'unicité au moment de la validation par rapport à l'index: ce dernier est fait par base de données tandis que l'amorce est faite par le modèle. Puisqu'il peut y avoir plusieurs instances simultanées d'un modèle en cours d'exécution en même temps, la validation est soumise à des conditions de concurrence, ce qui signifie qu'elle peut ne pas détecter les doublons dans certains cas (par exemple, soumettre deux fois le même formulaire exactement au même moment).

Olivier
la source
Alors quel est le meilleur? Côté base de données ou validates_uniqueness_of?
WM
9
Tous les deux. validates_uniqueness_of peut être utilisé pour afficher un message d'erreur de manière gracieuse dans l'application par exemple lorsqu'un formulaire est enregistré. La contrainte de base de données garantirait que vous ne vous retrouviez pas avec des enregistrements dup, même si la validation était spécifiée dans le modèle. De plus, vous pouvez récupérer l'exception ActiveRecord et également afficher un message agréable à l'utilisateur.
Uģis Ozols
5
@WM Si vous devez en choisir un, utilisez la contrainte de base de données. Cela fonctionnera même si différentes applications non RoR interagissent avec vos données et garantissent la cohérence à long terme.
amarré