使用对象-关系映射器(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 ,对性能的影响是可以忽略不计的。