Ruby 02 Jul 2008 02:56 am

推到 Twitter!
推到 Plurk!


[Rails] The Better Way To Do Random



VeryXD 在週末升級完成了 Rails 2.1 之後,很高興的就在 PTT2 的個人版先發佈了這個消息。大神一時手癢就幫忙「測試」起敝小站,他測完之後在 blog 上寫了一篇關於 MySQL 調整的 tips,大家可以參考看看。幫補了幾刀 index 調整以後大致上快了大概 5-6 倍吧 (Y)。

大神看完 MySQL LOG 以後覺得我在實作 random 的方式並不是很好,扔了這篇文章叫我參考改寫。(P.S. 這篇文章的作者雖是 lighttpd 的作者,但他是 MySQL AB 的員工)

原先我在 Rails 1.2.6 的版本是用比較普遍的寫法。

用 Model.find("random") 的方式作。

RUBY:
  1. def self.find(*args)
  2.     if args.first.to_s == "random"
  3.       ids = connection.select_all("SELECT id FROM gphotos where status = 'published'")
  4.       super(ids[rand(ids.length)]["id"].to_i)
  5.     else
  6.       super
  7.     end
  8.   end

換到 Rails 2.1.6 時,我改用了 named_scope + rand。

RUBY:
  1. named_scope :latest,:conditions => "status ='published'", :order => "id DESC"
  2.  
  3.   def self.pick
  4.      self.latest.rand
  5.   end

兩種寫法都是先去 SQL 拉回來,在 app 這邊算,但是這樣效率也是不太好。

目前最新的版本照了文章建議的方式改寫,把 rand 又扔回 database 方去做。

RUBY:
  1. def self.pick
  2.    return Gphoto.find_by_sql("SELECT * FROM gphotos as r1 JOIN (SELECT CEIL(RAND() * (SELECT MAX(id)     FROM gphotos)) AS gid) AS r2 WHERE r1.id>= r2.gid and r1.status = 'published' ORDER BY r1.id ASC LIMIT 1;").first;
  3. end

[ 先用找出一個值 CEIL(RAND*MAX(id)) 找出一個亂數值,但是這個亂數 id 可能不落在你希望的條件的範圍內,因此要取 id 大於此值但符合條件的下一筆資料)]

為何用 temp table 作 JOIN 而不用 id >= status 可以參照 文章內的警告 或 大神(這篇寫的中文版解釋)。至於 r2 為何要取名叫 gid,而不是 id ,是因為 join 出來的結果會有 2 個 id,然後 rails 就會精神錯亂 ....

再用 httperf 下去打,速度快了 30 倍(P.S. gphotos 大概是六萬多筆的資料量)。

提供這方面的實務數據讓大家參考一下。

Creative Commons License

One Response to “[Rails] The Better Way To Do Random”


  1. on 09 Jul 2008 at 10:08 am 1.hina said …

    歐,太實用了 (大心)
    看來有不少 mysql 的資訊可以鑽研了

Trackback This Post | Subscribe to the comments through RSS Feed

Leave a Reply