【问题标题】:Uniqueness case-sensitive false causes slow query唯一性 case-sensitive false 导致查询慢
【发布时间】:2014-12-18 07:09:32
【问题描述】:

我有以下验证:

validates :username, uniqueness: { case_sensitive: false }

这导致以下查询运行缓慢:

5,510 ms
SELECT ? AS one FROM "users" WHERE (LOWER("users"."username") = LOWER(?) AND "users"."id" != ?) LIMIT ?
Explain plan
1 Query plan    Limit (cost=0.03..4.03 rows=1 width=0)
2 Query plan    -> Index Scan using idx_users_lower_username on users (cost=0.03..4.03 rows=1 width=0)
3 Query plan    Index Cond: ?
4 Query plan    Filter: ?

索引是在我的structure.sql 中使用CREATE INDEX idx_users_lower_username ON users USING btree (lower((username)::text)); 创建的,有关更多信息,请参阅我的问题How to create index on LOWER("users"."username") in Rails (using postgres)

这是使用我设置的索引并且仍然需要 5 秒以上?这里有什么问题?

【问题讨论】:

  • 用户表有多少条记录?
  • 您能说明一下您是如何创建idx_users_lower_username 索引的吗?
  • LOWER("users"."username") 在查询中意味着它正在缩减数据库中的每条记录。索引可以帮助您查找内容,但据我所知,它不能帮助您转换现有记录。
  • @HenrikN,这是可以实现的,至少在 PostgreSQL 中是可以的
  • @blelump 嗯,是的,你是对的:postgresql.org/docs/9.1/static/indexes-expressional.html“索引表达式在索引搜索期间不会重新计算,因为它们已经存储在索引中。”谢谢你。

标签: ruby-on-rails postgresql ruby-on-rails-4 activerecord query-optimization


【解决方案1】:

这里发生了几个不同的、相互关联的事情。具体如何执行更改取决于您如何管理对数据库结构的更改。最常见的方法是使用 Rails 迁移,但您的链接问题表明您没有这样做。所以我主要讲 SQL,你可以根据自己的方法调整它。

使用 sargable WHERE 子句

您的 WHERE 子句不是 sargable。这意味着它的编写方式会阻止 dbms 使用索引。 PostgreSQL 可以在这里使用创建索引。 . .

create index on "users" (lower("username") varchar_pattern_ops);

现在对小写用户名的查询可以使用该索引。

explain analyze
select * 
from users
where lower(username) = lower('9LCDgRHk7kIXehk6LESDqHBJCt9wmA');

看起来好像 PostgreSQL 必须将表中的每个用户名都小写,但它的查询计划器足够聪明,可以看到表达式 lower(username) 本身已被索引。 PostgreSQL 使用索引扫描。

“使用 users_lower_idx 对用户进行索引扫描(成本=0.43..8.45 行=1 宽度=35)(实际时间=0.034..0.035 行=1 循环=1)” " 索引条件: (lower((用户名)::text) = 'b0sa9malg7yt1shssajrynqhiddm5d'::text)" “总运行时间:0.058 毫秒”

这个表有一百万行随机数据;查询返回非常非常快。它与“id”的附加条件几乎同样快,但是 LIMIT 子句减慢了很多。 “减慢很多”并不意味着它很慢。它仍然会在不到 0.1 毫秒内返回。

此外,varchar_pattern_ops 允许使用 LIKE 运算符的查询使用索引。

explain analyze
select *
from users
where lower(username) like 'b%'


"Bitmap Heap Scan on users  (cost=1075.12..9875.78 rows=30303 width=35) (actual time=10.217..91.030 rows=31785 loops=1)"
"  Filter: (lower((username)::text) ~~ 'b%'::text)"
"  ->  Bitmap Index Scan on users_lower_idx  (cost=0.00..1067.54 rows=31111 width=0) (actual time=8.648..8.648 rows=31785 loops=1)"
"        Index Cond: ((lower((username)::text) ~>=~ 'b'::text) AND (lower((username)::text) ~<~ 'c'::text))"
"Total runtime: 93.541 ms"

从一百万行中选择并返回 30k 行仅需 94 毫秒。

即使有可用的索引,对非常小的表的查询也可能会使用顺序扫描。如果我是你,我就不会担心这个。

在数据库中强制执行唯一性

如果您预计会出现任何流量突发,您应该在数据库中强制执行唯一性。我一直都这样做,不管对流量有什么期望(猜测)。

RailsGuides Active Record Validations 包含这个关于“唯一性”帮助器的略微误导或令人困惑的段落。

这个助手验证属性的值是唯一的 在对象被保存之前。它不会创造独特性 数据库中的约束,因此可能会发生两个不同的 数据库连接为 a 创建两个具有相同值的记录 您打算唯一的列。为避免这种情况,您必须创建一个 数据库中两列的唯一索引。请参阅 MySQL 手册 有关多列索引的更多详细信息。

它清楚地表明,事实上,它保证唯一性。误导部分是关于在“两列”上创建唯一索引。如果您希望“用户名”是唯一的,则需要在“用户名”列上声明一个唯一约束。

alter table "users"
add constraint constraint_name unique (username);

区分大小写

在 SQL 数据库中,区分大小写由排序规则确定。排序规则是 SQL 标准的一部分。

在 PostgreSQL 中,您可以在数据库级别、列级别、索引级别和查询级别设置排序规则。值来自操作系统在您使用 initdb 创建新数据库集群时公开的语言环境。

在 Linux 系统上,您可能有 no 不区分大小写的排序规则。这就是我们必须比那些以 SQL Server 和 Oracle 为目标的人跳过更多障碍的原因之一。

【讨论】:

    【解决方案2】:

    尝试使用解释分析在 psql 中运行查询,以确保 postgres 运行良好,因为显然索引和查询是正确的。

    如果在 psql 中速度很快,那么你的 rails 代码就有问题。

    这个针对 3k 记录表的查询给出了这个结果(在我的本地开发机器上):

    app=# explain analyze SELECT id AS one FROM "users" WHERE (LOWER(email) = LOWER('marcus@marcus.marcus') AND "users"."id" != 2000);
                                                           QUERY PLAN
    -------------------------------------------------------------------------------------------------------------------------
     Bitmap Heap Scan on users  (cost=4.43..58.06 rows=19 width=4) (actual time=0.101..0.101 rows=0 loops=1)
       Recheck Cond: (lower((email)::text) = 'marcus@marcus.marcus'::text)
       Filter: (id <> 2000)
       ->  Bitmap Index Scan on users_lower_idx  (cost=0.00..4.43 rows=19 width=0) (actual time=0.097..0.097 rows=0 loops=1)
             Index Cond: (lower((email)::text) = 'marcus@marcus.marcus'::text)
     Total runtime: 0.144 ms
    (6 rows)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多