在Rails.cache.fetch的代码块内,似乎不应使用return语句
首先
这篇文章是一个使用不到1年的Rails初学者所写的内容。
请注意,由于我使用的是旧版本(Rails 4),最新版本可能不会出现相同的问题。
示例代碼
在下面的示例源代码中,我们按照col1、col2、col3的顺序对tests表进行搜索,并在找到符合搜索条件的数据时不执行后续处理而返回搜索结果。
同时,搜索结果将保持在缓存中(30分钟),如果在指定的时间段内再次执行,则不会访问数据库,而是返回缓存中的数据。
def get_testdata( col1 , col2 , col3 )
Rails.cache.fetch("test-#{col1}-#{col2}-#{col3}", expires_in: 30.minutes ) do
test = Test.where(col1: col1).first
return test if test
test = Test.where(col2: col2).first
return test if test
test = Test.where(col3: col3).first
return test if test
nil
end
end
那么,让我们从Rails的控制台中实际运行这个处理过程吧。
第一次
[2] pry(main)> get_testdata("test1","test2","test3")
Cache read: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Cache generate: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Test Load (0.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test1' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.4ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col2` = 'test2' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.4ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col3` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[3] pry(main)>
哦,数据已获取到。
由于SQL已被执行了三次,似乎只有符合col3条件的数据。
如果再次使用相同条件进行执行,数据应该会从缓存中获取,所以不会再执行SQL。
第二次
[3] pry(main)> get_testdata("test1","test2","test3")
Cache read: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Cache generate: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Test Load (7.1ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test1' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (105.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col2` = 'test2' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (54.5ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col3` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[4] pry(main)>
… 看起来好像执行了与第一次相同的SQL语句。
可能只是我自己的错觉,所以让我们再试一次执行。
第三次
[4] pry(main)> get_testdata("test1","test2","test3")
Cache read: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Cache generate: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Test Load (0.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test1' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.4ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col2` = 'test2' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.4ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col3` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[5] pry(main)>
……似乎不是心理作用。
由于缓存未能正常工作,似乎每次都在访问数据库呢。
我将创建一个简化的源码供检查缓存使用。
样本源代码(用于缓存确认)
def get_testdata2( col1 )
Rails.cache.fetch("test2-#{col1}", expires_in: 30.minutes ) do
Test.where(col1: col1).first
end
end
因此,我创建了一个用于确认缓存的示例源代码。
如果这个代码不起作用,可能是执行环境出了问题。
第一次
[2] pry(main)> get_testdata2("test3")
Cache read: test2-test3 ({:expires_in=>1800 seconds})
Cache generate: test2-test3 ({:expires_in=>1800 seconds})
Test Load (0.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
Cache write: test2-test3 ({:expires_in=>1800 seconds})
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[3] pry(main)>
看起来成功获取到数据了。
这次执行时,之前没有输出的“Cache write”文字已经输出在执行结果中。
第二次
[3] pry(main)> get_testdata2("test3")
Cache read: test2-test3 ({:expires_in=>1800 seconds})
Cache fetch_hit: test2-test3 ({:expires_in=>1800 seconds})
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[4] pry(main)>
哦,能够在没有执行SQL的情况下获取到数据。
由于缓存正常工作,执行环境似乎没有问题。
接下来,我们将逐步添加处理并确认问题在哪里出现。
样本源代码(用于返回确认)
def get_testdata3( col1 )
Rails.cache.fetch("test3-#{col1}", expires_in: 30.minutes ) do
return Test.where(col1: col1).first
end
end
我在样本源代码中添加了一个仅包含return的语句(用于缓存确认)。让我们开始执行吧。
第一次
[2] pry(main)> get_testdata3("test3")
Cache read: test3-test3 ({:expires_in=>1800 seconds})
Cache generate: test3-test3 ({:expires_in=>1800 seconds})
Test Load (0.9ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[3] pry(main)>
好像成功获取到了数据。
在用于缓存确认的示例源代码中,不再输出“Cache write”这个文字。
第二次
[3] pry(main)> get_testdata3("test3")
Cache read: test3-test3 ({:expires_in=>1800 seconds})
Cache generate: test3-test3 ({:expires_in=>1800 seconds})
Test Load (0.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[4] pry(main)>
已经确认第二次执行与第一次相同的 SQL,就像最初的示例源代码一样。
似乎原因是使用了return子句导致离开了缓存目标块。
那么,让我们修改示例源代码,不再使用return子句。
サンプルソース(修正版)
def get_testdata( col1 , col2 , col3 )
Rails.cache.fetch("test-#{col1}-#{col2}-#{col3}", expires_in: 30.minutes ) do
test = Test.where(col1: col1).first
test = Test.where(col2: col2).first if test.blank?
test = Test.where(col3: col3).first if test.blank?
end
end
returnが問題ならば、これで上手く行くはず!
それでは実行して行きましょう。
第一次
[2] pry(main)> get_testdata("test1","test2","test3")
Cache read: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Cache generate: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Test Load (0.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test1' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.5ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col2` = 'test2' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.6ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col3` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
Cache write: test-test1-test2-test3 ({:expires_in=>1800 seconds})
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[3] pry(main)>
首次运行时,SQL会像之前一样被执行并且可以获得数据。
由于输出结果中出现了“缓存写入”字样,这看起来是可以期待的。
第二次
[3] pry(main)> get_testdata("test1","test2","test3")
Cache read: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Cache fetch_hit: test-test1-test2-test3 ({:expires_in=>1800 seconds})
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[4] pry(main)>
太好了!!!
成功获取了数据,而不需要执行SQL语句。
这样一来,在原本的处理中,缓存可以正常工作,并且能够在不访问数据库的情况下获取数据。
总结
在使用Rails.cache.fetch时,在执行块内使用return语句来结束处理,确认数据不会保存在缓存中。
起初我并没有想到只是加上return语句就会使缓存失效。仔细考虑一下,因为在执行return的时候就会退出方法,所以也会产生关于什么被缓存的疑问… 嗯,问题顺利解决了,真是太好了。
如果这能对遇到相同问题的人有所帮助,那就很好了。