Índice em várias colunas em Ruby on Rails

97

Estou implementando uma funcionalidade para rastrear quais artigos um usuário leu.

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

Esta é minha migração até agora:

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

A tabela user_views sempre será consultada para procurar ambas as colunas, nunca apenas uma. Minha pergunta é como meu índice deve ser. Existe uma diferença na ordem dessas tabelas, deve haver mais algumas opções para isso ou algo assim. Meu banco de dados de destino é o Postgres.

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

Obrigado.

ATUALIZAÇÃO:
Como apenas uma linha contendo os mesmos valores em ambas as colunas pode existir (visto que, ao saber se user_id LEU article_id), devo considerar a opção: unique? Se não me engano, isso significa que não tenho que fazer nenhuma verificação por conta própria e simplesmente inserir uma inserção sempre que um usuário visitar um artigo.

Emil Ahlbäck
fonte
"A tabela user_views sempre será consultada para procurar ambas as colunas, nunca apenas uma." - nunca haverá uma consulta "encontre todos os artigos que este usuário visualizou" ou "encontre todos os usuários que visualizaram este artigo"? Acho isso surpreendente.
David Aldridge

Respostas:

212

A ordem importa na indexação.

  1. Coloque o campo mais seletivo primeiro, ou seja, o campo que reduz o número de linhas mais rapidamente.
  2. O índice só será usado na medida em que você usar suas colunas em sequência, começando no início . ou seja, se você indexar em [:user_id, :article_id], poderá realizar uma consulta rápida em user_idou user_id AND article_id, mas NÃO em article_id.

Sua add_indexlinha de migração deve ser semelhante a esta:

add_index :user_views, [:user_id, :article_id]

Pergunta sobre a opção 'única'

Uma maneira fácil de fazer isso no Rails é usar validatesem seu modelo com o uniquenessseguinte escopo ( documentação ):

validates :user, uniqueness: { scope: :article }
sscirrus
fonte
7
A ordem é muito importante na indexação. Coloque as cláusulas where à esquerda e complete o índice com as colunas de ordenação à direita. stackoverflow.com/questions/6098616/dos-and-donts-for-indexes
Denis de Bernardy
1
Observe que validates_uniqueness_of(e seu primo validates uniqueness:) são propensos a condições de corrida
Ben Aubin
1
Conforme mencionado nos comentários acima e stackoverflow.com/a/1449466/5157706 e stackoverflow.com/a/22816105/5157706 , considere adicionar um índice exclusivo no banco de dados também.
Akash Agarwal
25

Apenas um aviso sobre a verificação da unicidade no momento da validação vs. no índice: o último é feito pelo banco de dados enquanto o primer é feito pelo modelo. Como pode haver várias instâncias simultâneas de um modelo em execução ao mesmo tempo, a validação está sujeita a condições de corrida, o que significa que pode falhar em detectar duplicatas em alguns casos (por exemplo, enviar duas vezes o mesmo formulário ao mesmo tempo).

Oliver
fonte
Então qual é o melhor? Lado do banco de dados ou validates_uniqueness_of?
WM
9
Ambos. validates_uniqueness_of pode ser usado para exibir uma mensagem de erro normalmente no aplicativo, por exemplo, quando um formulário é salvo. A restrição do banco de dados garantiria que você não acabasse com registros dup mesmo sabendo que a validação foi especificada no modelo. Além disso, você pode resgatar a exceção ActiveRecord e também mostrar uma mensagem agradável ao usuário.
Uģis Ozols
5
@WM Se você tiver que escolher um, siga a restrição do banco de dados. Isso funcionará mesmo se aplicativos diferentes, não RoR, interagirem com seus dados e garantir a consistência em longo prazo.
atracado em