论坛首页 编程语言技术论坛

Rails sql延迟加载和自带缓存

浏览 9757 次
精华帖 (0) :: 良好帖 (2) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-12-30   最后修改:2010-12-31
color_lot_manuallies = color_lot.color_lot_manuallies
if color_lot_manuallies.size == 1
end

引用
SELECT count(*) AS count_all FROM `color_lot_manuallies` WHERE (`color_lot_manuallies`.color_lot_id = 237)

当有A has many B时,a.bs.size并不是检索出所有a.bs再求出size,而是直接用select count(*)来计算结果。这应该是延迟加载,有点类似named_scope,但是并不是named_scope,named_scope的结果类是ActiveRecord::NamedScope::Scope,但是A.bs.class的结果是Array,这部分看了下源码但是并没有看懂。
所以当我这样写的时候:
    color_lot_manuallies = color_lot.color_lot_manuallies
    if color_lot_manuallies.size == 1
    end
    color_lot.color_lot_manuallies.each do |i|
       puts i.id
    end

引用
SQL (0.6ms)   SELECT count(*) AS count_all FROM `color_lot_manuallies` WHERE (`color_lot_manuallies`.color_lot_id = 200)
ColorLotManually Load (0.1ms)   SELECT * FROM `color_lot_manuallies` WHERE (`color_lot_manuallies`.color_lot_id = 200)

这样的话rails使用了延迟加载造成两个sql不同,所以后面。color_lot.color_lot_manuallies再次使用时,不会使用缓存的sql。
改成这样:
    color_lot_manuallies = color_lot.color_lot_manuallies.all
    if color_lot_manuallies.size == 1
    end
    color_lot.color_lot_manuallies.each do |i|
       puts i.id
    end

引用
ColorLotManually Load (0.1ms)   SELECT * FROM `color_lot_manuallies` WHERE (`color_lot_manuallies`.color_lot_id = 198)
CACHE (0.0ms)   SELECT * FROM `color_lot_manuallies` WHERE (`color_lot_manuallies`.color_lot_id = 198)

加一个all,color_lot.color_lot_manuallies.all,这样的话,后面再次调用时,会使用rails的sql缓存。
#PurchaseOrder 
  has_one :purchase_order_marketing, :dependent => :destroy
  has_many :purchase_invoices, :dependent => :destroy,:through => :purchase_order_marketing
#PurchaseOrderMarketing
  has_many :purchase_invoices,:dependent => :destroy

PurchaseOrder.first.purchase_invoices.all(:select => 'purchase_invoices.id')

引用
PurchaseOrder Load (0.2ms)   SELECT * FROM `purchase_orders` LIMIT 1
  PurchaseInvoice Load (0.8ms)   SELECT purchase_invoices.id FROM `purchase_invoices` INNER JOIN `purchase_order_marketings` ON `purchase_invoices`.purchase_order_marketing_id = `purchase_order_marketings`.id WHERE ((`purchase_order_marketings`.purchase_order_id = 13))

一条sql就完成了,真强大。
  #article
def sizes
    sizes = []
    art = self
    sf = art.article_secondary_feature
    size_group = SizeGroup.find_by_id(sf.size_groupid) if sf
    sizes = size_group.sizes if size_group
    return sizes
  end

ArticleMarketing.first.article.sizes.all(:select => 'id')

 
引用
ArticleMarketing Load (0.3ms)   SELECT * FROM `article_marketings` LIMIT 1
  Article Load (0.8ms)   SELECT * FROM `articles` WHERE (`articles`.`id` = 43)
  ArticleSecondaryFeature Load (0.7ms)   SELECT `article_secondary_features`.* FROM `article_secondary_features` WHERE (`article_secondary_features`.article_id = 43)
  SizeGroup Load (0.6ms)   SELECT * FROM `size_groups` WHERE (`size_groups`.`id` = 2) LIMIT 1
  Size Load (1.0ms)   SELECT id FROM `sizes` WHERE (`sizes`.size_group_id = 2)

find和find_by_id
params[:root].classify.constantize.find_by_id(params[:id])
params[:root].classify.constantize.find(params[:id])

引用
  Opportunity Load (0.1ms)   SELECT * FROM `opportunities` WHERE (`opportunities`.`id` = '126') LIMIT 1
  Opportunity Load (0.1ms)   SELECT * FROM `opportunities` WHERE (`opportunities`.`id` = 126)

find_by_id的SQL多了一行limit 1,对于sql语句而言,有一点性能上的提高。不过有时候我们需要find来捕捉异常。如果能用find_by_id最好了。
都改成find_by_id
引用
Opportunity Load (0.1ms)   SELECT * FROM `opportunities` WHERE (`opportunities`.`id` = '126') LIMIT 1
  CACHE (0.0ms)   SELECT * FROM `opportunities` WHERE (`opportunities`.`id` = '126') LIMIT 1

这样都有缓存了。rails的自带的缓存是很脆弱的,B.find_by_id(a.id)方法变成a.b时,这个缓存就不会用上。同样前面的例子里改成
    color_lot_manuallies = color_lot.color_lot_manuallies.all
    ColorLotManually.find_all_by_color_lot_id(color_lot.id)

rails自带的缓存也不会用上。
关于||=缓存,参考
http://fuliang.iteye.com/blog/827321
http://www.iteye.com/topic/810957
但是有一点,||=不会自动清除或者更新,所以使用的时候还是要注意点,可能会引起取值错误,而且不会报错。
class PortOfDischage < ActiveRecord::Base
  def _name
    @_name ||= self.city
  end
end


Reloading...
=> true
>> p=PortOfDischage.first
  SQL (0.2ms)   SET SQL_AUTO_IS_NULL=0
  PortOfDischage Load (21.2ms)   SELECT * FROM `port_of_dischages` LIMIT 1
  PortOfDischage Columns (1.7ms)   SHOW FIELDS FROM `port_of_dischages`
+----+------------+-----------------+---------+---------+--------------------------------+--------------------------------+
| id | ap_list_id | ap_marketing_id | city    | country | created_at                     | updated_at                     |
+----+------------+-----------------+---------+---------+--------------------------------+--------------------------------+
| 1  | 2          | 2               | Piraeus | Greece  | Thu Oct 07 07:10:21 +0800 2010 | Thu Oct 07 07:10:21 +0800 2010 |
+----+------------+-----------------+---------+---------+--------------------------------+--------------------------------+
1 row in set
>> p._name
=> "Piraeus"
>> p.update_attribute(:city,'p')
  SQL (0.2ms)   BEGIN
  ApMarketing Columns (43.0ms)   SHOW FIELDS FROM `ap_marketings`
  ApMarketing Load (18.6ms)   SELECT * FROM `ap_marketings` WHERE (`ap_marketings`.`id` = 2) 
  PortOfDischage Update (45.5ms)   UPDATE `port_of_dischages` SET `updated_at` = '2010-12-30 13:08:47', `city` = 'p' WHERE `id` = 1
  SQL (55.5ms)   COMMIT
=> true
>> p._name
=> "Piraeus"
>> p.city
=> "p"
>> p.reload
  PortOfDischage Load (0.7ms)   SELECT * FROM `port_of_dischages` WHERE (`port_of_dischages`.`id` = 1) 
+----+------------+-----------------+------+---------+--------------------------------+--------------------------------+
| id | ap_list_id | ap_marketing_id | city | country | created_at                     | updated_at                     |
+----+------------+-----------------+------+---------+--------------------------------+--------------------------------+
| 1  | 2          | 2               | p    | Greece  | Thu Oct 07 07:10:21 +0800 2010 | Thu Dec 30 13:08:47 +0800 2010 |
+----+------------+-----------------+------+---------+--------------------------------+--------------------------------+
1 row in set
>> p._name
=> "Piraeus"

   发表时间:2011-01-01  
ActiveRecord的has_many关联和scope很相似,都用了delegation,rails 2.3.x中返回的是namedscopes或associations。
虽然返回的是array,但是在对返回的associations或namedscopes调用方法时候,会根据不同的方法,delegate到不同的对象。
调用:scopes, :with_scope, :scoped_methods等时继续返回scope
调用除了[nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?]中的方法时,先调用.all方法进行db操作。
即:
user.posts.each{...} == user.posts.all.each{...}
Rails3就更变态了,到处是lazy loading.
where
having
select
group
order
limit
offset
joins
includes (:include)
lock
readonly
from
这些方法都仍然返回一个Relation对象。直到调用map/each等方法才真正的进行数据库操作。


引用
CACHE (0.0ms)   SELECT * FROM `opportunities` WHERE (`opportunities`.`id` = '126') LIMIT 1

这样的日志是mysql的缓存吧,和rails一点关系没有。
无论find还是find by id都进行了一次db操作。而这样的主键查询有没有limit 1貌似没有差别吧。
find和find by id的最大区别就是抛不抛异常的区别。还有就是find by 会调用method_missing,很多人说这东西慢...
像这样简单的查询,用cache money或其他缓存插件都可以避免hit db的。

0 请登录后投票
   发表时间:2011-01-01   最后修改:2011-01-01
Hooopo 写道

引用
CACHE (0.0ms)   SELECT * FROM `opportunities` WHERE (`opportunities`.`id` = '126') LIMIT 1

这样的日志是mysql的缓存吧,和rails一点关系没有。
无论find还是find by id都进行了一次db操作。而这样的主键查询有没有limit 1貌似没有差别吧。
find和find by id的最大区别就是抛不抛异常的区别。还有就是find by 会调用method_missing,很多人说这东西慢...
像这样简单的查询,用cache money或其他缓存插件都可以避免hit db的。

这个是rails的缓存。mysql的缓存是另外一回事,你搞混了。
加limit 1还是有一点点效果的,你可以在mysql下做下测试。
我标题里已经写了,rails自带缓存。当然,如果用memcache或者其他的插件,这个应该是可以避免hit db的。不知道有没有办法把rails的自带缓存禁止掉。
引用

ActiveRecord的has_many关联和scope很相似,都用了delegation,rails 2.3.x中返回的是namedscopes或associations。
虽然返回的是array,但是在对返回的associations或namedscopes调用方法时候,会根据不同的方法,delegate到不同的对象。
调用:scopes, :with_scope, :scoped_methods等时继续返回scope
调用除了[nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?]中的方法时,先调用.all方法进行db操作。
即:
user.posts.each{...} == user.posts.all.each{...}
Rails3就更变态了,到处是lazy loading.
where
having
select
group
order
limit
offset
joins
includes (:include)
lock
readonly
from
这些方法都仍然返回一个Relation对象。直到调用map/each等方法才真正的进行数据库操作。

rails3还没怎么玩。恩,delegate,恩,有时间在看看。
0 请登录后投票
   发表时间:2011-01-05  
引用
这个是rails的缓存。mysql的缓存是另外一回事,你搞混了。

确实搞混了,这个是rails的QueryCache打的日志~~
这个缓存是action级别的,一个action之内的相同查询不会再去数据库取新的(如果当前action没有更新这条记录)。但弱的很,不过不需要手动设置什么,很方便的。也有一些隐藏的危险,比如:
p User.first(:order => "RAND()")
p User.first(:order => "RAND()")

这样两次结果是相同的,如果开启了QueryCache。
引用
加limit 1还是有一点点效果的,你可以在mysql下做下测试。

这个效果还真没看出来,可能数据太少了。
引用
不知道有没有办法把rails的自带缓存禁止掉。

User.uncached do
  p User.first(:order => "RAND()").id
  p User.first(:order => "RAND()").id
end



0 请登录后投票
   发表时间:2011-01-05   最后修改:2011-01-05
(占位)
引用
order => "RAND(

很邪恶。。这个order by rand貌似mysql都不会缓存吧。。
0 请登录后投票
   发表时间:2011-01-05  
这些方法都仍然返回一个Relation对象。直到调用map/each等方法才真正的进行数据库操作。 

是对数据进行操作系吧。不然执行的不会那么慢,不知道你说的是3还是2+
0 请登录后投票
   发表时间:2011-01-05  
orcl_zhang 写道
(占位)
引用
order => "RAND(

很邪恶。。这个order by rand貌似mysql都不会缓存吧。。

我理解的mysql缓存是在select和where中间的那个字段或值,不知道正不正确,求解
0 请登录后投票
   发表时间:2011-01-06   最后修改:2011-01-06
ddl1st 写道
orcl_zhang 写道
(占位)
引用
order => "RAND(

很邪恶。。这个order by rand貌似mysql都不会缓存吧。。

我理解的mysql缓存是在select和where中间的那个字段或值,不知道正不正确,求解


引用
1. 为查询缓存优化你的查询

大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。

这里最主要的问题是,对于程序员来说,这个事情是很容易被忽略的。因为,我们某些查询语句会让MySQL不使用缓存。请看下面的示例:

// 查询缓存不开启
$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");

// 开启查询缓存
$today = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
// 查询缓存不开启
$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");

// 开启查询缓存
$today = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
上面两条SQL语句的差别就是 CURDATE() ,MySQL的查询缓存对这个函数不起作用。所以,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所需要的就是用一个变量来代替MySQL的函数,从而开启缓存。

引用
6. 千万不要 ORDER BY RAND()

想打乱返回的数据行?随机挑一个数据?真不知道谁发明了这种用法,但很多新手很喜欢这样用。但你确不了解这样做有多么可怕的性能问题。

如果你真的想把返回的数据行打乱了,你有N种方法可以达到这个目的。这样使用只让你的数据库的性能呈指数级的下降。这里的问题是:MySQL会不得不去执行RAND()函数(很耗CPU时间),而且这是为了每一行记录去记行,然后再对其排序。就算是你用了Limit 1也无济于事(因为要排序)

下面的示例是随机挑一条记录
// 千万不要这样做:
$r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1");

// 这要会更好:
$r = mysql_query("SELECT count(*) FROM user");
$d = mysql_fetch_row($r);
$rand = mt_rand(0,$d[0] - 1);

$r = mysql_query("SELECT username FROM user LIMIT $rand, 1");

http://coolshell.cn/articles/1846.html#more-1846
0 请登录后投票
   发表时间:2011-01-07  
都是需要驱动的:
返回的是namedscopes或associations,因为有时候我们的目的并非是查询,还可能会创建或其他操作,比如current_user.posts.new,这个时候current_user.posts就不能直接查询返回数组了。

find 和 find_by也是根据需要场景来决定用谁。个人觉得比较他们的性能并没有意义。

find_by == find(:first),是find的特别版,find是不是抛异常和它返回是数组还是别的,要看find的第一个参数是什么。

如果你需要判断一个记录是否存在并返回它,就用find_by,如果仅仅是批量查询就用find
0 请登录后投票
   发表时间:2011-01-07   最后修改:2011-01-07
K-PAX 写道

find 和 find_by也是根据需要场景来决定用谁。个人觉得比较他们的性能并没有意义。

http://coolshell.cn/articles/1846.html#more-1846
比较性能,多数情况下确实没太大意义。。
不过因为项目里面有类似于exist?(id),然后find(id),如此多此一举。
而且也只有这个表记录在20多万,数据量稍微多点,查询相当频繁,基本上每个action会查几十次。就这种情况下做了下修改,都改成了find_by.
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics