惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
GbyAI
GbyAI
WordPress大学
WordPress大学
月光博客
月光博客
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Google DeepMind News
Google DeepMind News
H
Help Net Security
MongoDB | Blog
MongoDB | Blog
P
Proofpoint News Feed
博客园 - 司徒正美
S
SegmentFault 最新的问题
Apple Machine Learning Research
Apple Machine Learning Research
Blog — PlanetScale
Blog — PlanetScale
B
Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Microsoft Azure Blog
Microsoft Azure Blog
V
V2EX
L
LangChain Blog
腾讯CDC
T
The Blog of Author Tim Ferriss
量子位

Nic Lin's Blog

謝明真 - 高效領導力的課後筆記 NFT 開發實戰!基礎智能合約入門 (3) NFT 開發實戰!基礎智能合約入門 (2) NFT 開發實戰!基礎智能合約入門 (1) 如何自我檢測 log4j CVE 漏洞 Rails 如何在資料寫入時記錄來源 IP 位置 如何經營工程師 Youtube 頻道 - Part 8 營收篇 如何經營工程師 Youtube 頻道 - Part 7 酸民文化篇 如何經營工程師 Youtube 頻道 - Part 5 設備器材篇 如何經營工程師 Youtube 頻道 - Part 4 後製剪輯篇 如何經營工程師 Youtube 頻道 - Part 3 文案企劃篇 如何經營工程師 Youtube 頻道 - Part 2 設備器材篇 如何經營工程師 Youtube 頻道 - Part 1 制訂頻道方向篇 如何經營工程師 Youtube 頻道 - Part 0 Rails 中避免 race condition 的最佳實踐(二) Rails 中避免 race condition 的最佳實踐(一) 10 分鐘整合 google sheet 做自動化開發功能週報 經營 Side Project 300 天所帶來的收穫及挑戰 我的 Youtube 影片製作流程 API 設計時必須注意的 HTTP header 底線問題 如何提升你的程式可讀性之實務技巧(三) 如何提升你的程式可讀性之實務技巧(二) 如何提升你的程式可讀性之實務技巧(一) Ruby 中使用 freeze 優化效能的時機 避免 React 中的 useEffect 無限 render 在 Rails 內輕量使用 Vue Component 的最佳實踐 如何在區域網路用 Docker 架設有 SSL 的 Gitlab 從被問到問人,那些我常問的面試問題 [Rails] 如何漂亮寫出可維護的 query (Maintainable Rails Query) 在已知長度情況下優化 slice 的性能
[Rails] 如何高效的確定資料是否存在?
Nic Lin · 2018-06-08 · via Nic Lin's Blog

一個很常的議題是討論 Ruby on Rails 很慢,但其實追根究底起來,一般網站慢的問題痛點都在於讀取 Database 的 response time 太久,讓人有很慢的錯覺,其實不管用哪套框架,如果在讀資料的時候慢,是會對使用體驗非常差的。

你的 application 慢在哪?

因為 Rails 有良好的 ORM 可以使用,所以我們大可不必自己手寫 SQL Query,但也因為這樣衍生出表面上看起來 code 很好讀懂,卻在讀取較大資料時明顯的發現有效能並不好的問題。

造成讀取數據慢的幾項常見原因:

  • N+1 queries
  • 一次拉出太多不必要的資料,塞爆 Database memory
  • 沒有建立 index, 或是用了錯誤的 index
  • 沒有適當的 cache 資料,凡事伸手向 Database 拿

這些問題在一開始資料還小的時候(小於百萬),都可以靠 database cpu 硬幹過去,可能不會有太明顯的感受,但 client 端口變多、資料增長速度快的情況下,可能光 api 就被 call 掛了,這樣就會收到滿滿的客訴抱怨了

從 Rails 的語句下手

確認資料是否存在的四種常見語句

  • present?
  • empty?
  • any?
  • exists?

這裡先講結論,建議從 ActiveRecord 中拉資料出來確認是否存在時,偏好使用 exists?

為什麼?我們舉例來看

Order.where(created_at: 7.days.ago..1.day.ago).done.present?

# SELECT "orders".* FROM "orders" WHERE ("orders"."created_at" BETWEEN
# '2017-02-22 21:22:27.133402' AND '2017-02-28 21:22:27.133529') AND
# "orders"."aasm_state" = $1  [["aasm_state", "done"]]

Order.where(:created_at => 7.days.ago..1.day.ago).done.any?

# SELECT COUNT(*) FROM "orders" WHERE ("orders"."created_at" BETWEEN
# '2017-02-22 21:22:16.885942' AND '2017-02-28 21:22:16.886077') AND
# "orders"."aasm_state" = $1  [["aasm_state", "done"]]

Order.where(:created_at => 7.days.ago..1.day.ago).done.empty?

# SELECT COUNT(*) FROM "orders" WHERE ("orders"."created_at" BETWEEN
# '2017-02-22 21:22:16.885942' AND '2017-02-28 21:22:16.886077') AND
# "orders"."aasm_state" = $1  [["aasm_state", "done"]]

Order.where(:created_at => 7.days.ago..1.day.ago).done.exists?

# SELECT 1 AS one FROM "builds" WHERE ("builds"."created_at" BETWEEN
# '2017-02-22 21:23:04.066301' AND '2017-02-28 21:23:04.066443') AND
# "orders"."aasm_state" = $1 LIMIT 1  [["aasm_state", "done"]]

從第一個例子來看,我們較常使用的 .present? 效率是非常低的,他等於是讀取了所有相關的資料,只為了確定是否存在?

如果在這個時間區間內的資料量更大,會更明顯感受到這是非常低效的作法,更具體的說明是,我為了要確認這個圖書館有沒有書,我把所有的書都拿出來,然後跟你說,有書。

那麼第二個和第三個的作法,用了 .any?.empty?,從 SQL query 來看,用了 COUNT(*),在 Rails 中,有針對 COUNT 進行優化,所以會將這個資料放進內存中,整體上來說,並沒有什麼副作用或是效能太慢的問題。

則最後一種方法 .exists?,則是最佳解,這應該是你要檢查資料是否存在的首選項,因為從 SQL query 來看,他用了 SELECT 1 LIMIT 1 的方法,這是非常快的,就算你的資料表數據量非常龐大。

具體來說,我要確認圖書館有沒有書,只要找到一本,我就確定有書。

速度分析參考

present? =>  2892.7 ms
any?     =>   400.9 ms
empty?   =>   403.9 ms
exists   =>     1.1 ms

如果說 200ms 的 response time 是可以接受的程度,那麼這個好習慣可以讓你盡可能的避免寫出有效能問題的 code

所以我該總是使用 exists? 嗎

基本上這是一個很好的 default 寫法,在確認資料存在的情況下。

但有些例外並不適用,例如前幾行 code 已經將相關數據緩存進來的時候,那麼你再次調用 .exists?,其實就會再多一條 SQL query

舉例來說

user = User.find_by(name: "NicLin")

user.orders.load    # eager loads all the builds into the association cache

user.orders.any?    # no database hit
user.orders.exists? # hits the database

# 如果你改變了 cache, 將會再次 hits the database
user.orders(true).any?    # hits the database
user.orders(true).exists? # hits the database

結論

建議使用 .exists?, 依據不同情況改善

參考來源

Faster Rails: How to Check if a Record Exists