Rails 这算不算counter_cache的设计问题?

jasl · 发布于 2013年01月19日 · 最后由 jasl 回复于 2013年01月19日 · 1844 次阅读
1107

见代码说话

[1] pry(main)> Node.last.posts
  Node Load (0.2ms)  SELECT `nodes`.* FROM `nodes` ORDER BY `nodes`.`id` DESC LIMIT 1
  Post Load (0.4ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`node_id` = 5
=> []
[2] pry(main)> Post.first.node = Node.last
  Node Load (0.1ms)  SELECT `nodes`.* FROM `nodes` ORDER BY `nodes`.`id` DESC LIMIT 1
  SQL (0.3ms)  UPDATE `nodes` SET `posts_count` = COALESCE(`posts_count`, 0) + 1 WHERE `nodes`.`id` = 5
  SQL (0.3ms)  UPDATE `nodes` SET `posts_count` = COALESCE(`posts_count`, 0) - 1 WHERE `nodes`.`id` = 1
=> #<Node id: 5, name: "ttt", cover: nil, description: nil, created_at: "2013-01-18 21:34:14", updated_at: "2013-01-18 22:15:41", posts_count: 0, state: "publish">
[3] pry(main)> Post.first.node
=> #<Node id: 1, name: "System", cover: nil, description: "", created_at: "2012-12-07 18:29:00", updated_at: "2012-12-07 18:40:03", posts_count: 4, state: "system">
[4] pry(main)> Node.last.posts_count
  Node Load (0.4ms)  SELECT `nodes`.* FROM `nodes` ORDER BY `nodes`.`id` DESC LIMIT 1
=> 1
[5] pry(main)> Node.last.posts
  Node Load (0.4ms)  SELECT `nodes`.* FROM `nodes` ORDER BY `nodes`.`id` DESC LIMIT 1
  Post Load (0.3ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`node_id` = 5
=> []
[6] pry(main)> Node.last.posts_count
  Node Load (0.4ms)  SELECT `nodes`.* FROM `nodes` ORDER BY `nodes`.`id` DESC LIMIT 1
=> 1

在给post的node赋值时counter便发生update,但此时post不一定会被save,这样就造成了不一致性,此外,更新node_id时不会更新counter 阅读了源码,发现似乎就是这么设计的,并且在单元测试中也是按照这个逻辑去做的。

十分不解这样设计的目的,为什么不在更新之后更新counter?我写了一个patch,正在犹豫要不要pr

共收到 21 条回复
202

看的太累了。以后不如来个mock testing,看的快。你提交个patch看看,犹豫啥。

1

自从发现 counter_cache 的行为有时不符合我理解之后,我就自己写 after_create 了。

2622

会不会是为了save前用到count可以保证逻辑上的count数?

586

Post.first.node = Node.last 这句的行为在 Rails 2 的时候好像就已经是这样了 要做关联的话,记得应该是用 Node.last.build_post() 然后再 save

200

你reload试下,看看counter有没有变化

1107

@Ddl1st 不会 @edokeh 有这么一种场景,比如post属于node1,然后把他移动到node2了,然后counter就乱掉了

1107

@xds2000 开了一个issue看看

200

#6楼 @jasl 我觉得没什么不妥,只是临时的更新了变量而已,如果没有保存自动释放'池'

1107

@Ddl1st 当然不是临时的。。。counter的改动直接就写入数据库了,但是资源却不一定被持久化,这当然是问题

200

#9楼 @jasl 你加了autosave ?

1107

@Ddl1st 没有啊,他就是这个行为,你读读源码就知道了

200

的确😓

270

@jasl ……

1107

@hysios 从源码的逻辑看,确实写入数据库了,而且很有可能造成最终一致性问题 ar的策略是 如果存在counter_cache 那么在求size的时候会直接取counter

15楼 已删除
1107

@hysios 如果设置了counter_cache那么多对一的一端要求有一个字段来缓存多端的size,这个字段是rails来管理的,在对多端进行count/size查询的饿时候rails会直接取这个字段 避免了count(*)操作

586

#16楼 @jasl 好像是这样的,你如果要修改关联,要先删除旧关联(设置为nil),然后再赋值,这样两边才会都对

1107

@edokeh 这个字段是rails管理的,所以他不应该出现我描述的这种情况,除非有什么设计上的考量。 先删除就关联 再赋值 一样会有问题 因为赋值之后 counter立即更新并持久化了 那我因为一些原因不把resource保存了呢?counter还是会错的

377

写个unit test吧,看得真心累

586

#18楼 @jasl 恩,这里确实有些奇怪,不过也可能是我们没有摸清 DHH 大神的设计意图 这会儿手头没环境,痛苦,回头找台机好好看看

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册