使用对象-关系映射器(ORM)时where.first与find_by的性能对比

61 阅读2分钟

使用对象-关系映射器(ORM),如Active Record,来生成你的SQL有很多好处。你可以从清晰的可重复的抽象中获益,节省时间并提高可读性。

不幸的是,使ORM有用的抽象也会产生意想不到的后果。

它很容易引入性能问题,因为在你的数据库服务器上运行的实际SQL是由框架生成的,有时这意味着SQL并不像它可能的那样有效。

我们最近在CoverageBook遇到了一个问题,强调了这一点。

而不是...

...使用一个where 条件,然后first

User.where(email: "andy@goodscary.com").first

使用...

...find_by

User.find_by(email: "andy@goodscary.com")
User.find_by_email("andy@goodscary.com")

为什么?

这是ORM(以及围绕它的工具)的一种情况,它妨碍并引入了无法预见的性能问题。

.where 范围在主键上有一个隐含的ORDER 范围,乍一看并不明显。

User.where(email: "andy@goodscary.com")
# SELECT "users".*
# FROM "users"
# WHERE "users"."email" = "andy@goodscary.com"

User.where(email: "andy@goodscary.com").first
# SELECT "users".*
# FROM "users"
# WHERE "users"."email" = "andy@goodscary.com"
# ORDER BY "users"."id" ASC
# LIMIT 1

User.find_by(email: "andy@goodscary.com")
# SELECT "users".*
# FROM "users"
# WHERE "users"."email" = "andy@goodscary.com"
# LIMIT 1

在我们的数据库中,直接的索引并没有像我们的--更复杂的--案例那样帮助我们。我们正在使用索引进行查询,但是由于我们使用了.where().first ,我们无意中做了一次非索引的扫描来建立顺序,这造成了巨大的性能问题。

此外,我们每秒钟要写几千条记录,即使有一个强大数据库,我们也看到了问题,因为整个表被排序,然后只挑选一条记录。

调试这个问题很棘手,因为在查询执行过程中,不可能在.find_by.where().first 的结果上调用.to_sql ,你必须使用日志来找出正在生成的确切SQL。

知道Active Record从表面上看起来相同的方法中生成的确切SQL可能非常重要。

为什么不呢?

在小表中,在轻度负载下,使用where().first ,对性能的影响是可以忽略不计的。