如何在Rails中创建索引("users"."username")(使用postgres)

Jos*_*ith 7 postgresql indexing ruby-on-rails ruby-on-rails-4 rails-activerecord

我的UsersController#create动作中发生了顺序扫描.

SELECT ? AS one FROM "users" WHERE (LOWER("users"."username") = LOWER(?) AND "users"."id" != ?) LIMIT ?

Explain plan
1 Query planLimit (cost=0.00..3.35 rows=1 width=0)
2 Query plan-> Seq Scan on users (cost=0.00..3.35 rows=1 width=0)
3 Query planFilter: ?
Run Code Online (Sandbox Code Playgroud)

我相当积极,这来自以下模型验证:

validates :username, uniqueness: { case_sensitive: false }

我应该创建一个针对此快递的索引吗?如果是这样,在Rails 4中执行此操作的正确方法是什么?

mu *_*ort 13

是的,验证会进行那种查询,而那种查询将进行表扫描.

你实际上有几个问题:

  • 验证受竞争条件的影响,因为逻辑不在其所属的数据库中.无论通常的Rails意识形态如何,数据库都应该负责所有数据完整性问题.
  • 您的验证会触发表扫描,没有人喜欢表扫描.

您可以使用一个索引解决这两个问题.第一个问题是通过在数据库中使用唯一索引来解决的.第二个问题是通过索引结果来解决的,lower(username)而不是username.

AFAIK Rails仍然不理解表达式的索引,所以你必须做两件事:

  1. 从切换schema.rbstructure.sql保持Rails的忘记你的索引.在你的config/application.rb想要设置:

    config.active_record.schema_format = :sql
    
    Run Code Online (Sandbox Code Playgroud)

    您还必须开始使用db:structure:*rake任务而不是db:schema:*任务.切换到之后structure.sql,您可以删除,db/schema.rb因为它不会再被更新或使用; 你还想db/structure.sql在版本控制中开始跟踪.

  2. 通过在迁移中编写一些SQL来手动创建索引.这很简单:

    def up
      connection.execute(%q{
        create index idx_users_lower_username on users(lower(username))
      })
    end
    
    def down
      connection.execute(%q{
        drop index idx_users_lower_username
      })
    end
    
    Run Code Online (Sandbox Code Playgroud)

当然这会让你得到PostgreSQL特有的东西,但是没什么可担心的,因为ActiveRecord无论如何都不会给你带来任何有用的可移植性.

  • 你的“users”表有多大?仅仅因为您有索引并不意味着它会被使用。查询优化器会猜测索引是否值得,而猜测取决于表中的数据是什么样子、有多大……如果只有 100 个条目,使用索引可能比检查更昂贵商场。查询优化有点像魔术。 (2认同)