新手问题 用了 Ruby 以后,发现瓶颈已不在数据库,而在语言

tini8 · 2015年04月27日 · 最后由 luikore 回复于 2015年04月30日 · 5110 次阅读

以 N 级菜单为例,在 php 下我是这样的:

`先用 "select * from trees" 查询出所有的菜单项,存入到一个数组,然后用一个递归函数循环这个数组,遇到 tree1.id == tree2.parent_id 的时候,把 tree2 保存到 tree1 的 children 里面。

这样只运行了一行 sql 查询,剩下的用程序算法实现。

在 ruby 下我也试着这样处理,结果速度慢的超乎我的想想,于是,在 ruby 下我只能这样做:

先用 "select * from trees where depth=1" 查询出所有的一级菜单项,然后

trees = trees.map do |tree|
  tree.children = Tree.where(parent_id: tree.id)
  tree.children = tree.children.map do |child|
    child.children = Tree.where(parent_id. child.id)
    child
  end
  tree
end

这样会进行好多次 sql 查询,但这样的速度是最快的,对 ruby 来说

================================= 2015-04-27 20:06 补充 ================================== PHP 递归实现菜单树的代码:

function make_tree($arr){
    if(!function_exists('make_tree1')){
        function make_tree1(&$arr, $parent_id=0){
            $new_arr = [];
            foreach($arr as $k=>$v){
                if($v->parent_id == $parent_id){
                    $new_arr[] = $v;
                    unset($arr[$k]);
                }
            }
            foreach($new_arr as &$a){
                $a->children = make_tree1($arr, $a->id);
            }
            return $new_arr;
        }
    }
    return make_tree1($arr);
}

那一定是你的用法不对,不要随便下结论,尤其是你不了解一个东西的时候。 贴你在 PHP 里面的 SQL 和完整代码,以及同样的方式,你在 Ruby 里面是如何写的

感觉可以类似这类的重新设计下:

class Employee < ActiveRecord::Base
  has_many :subordinates, class_name: "Employee",
                          foreign_key: "manager_id"

  belongs_to :manager, class_name: "Employee"
end

#2 楼 @rudy 对菜单树来说,一个 parent_id 字段就可以理清各个菜单项的从属关系了吧,我都是这样设计数据库的,当然也可以添加一个 children 的字段来保存子菜单的 id(用逗号分开),但这样我觉得就不 clean 了

可能不是语言层面,而是 ORM 层面吧,不知道 LZ 对 ActiveRecord 熟吗?

我猜测慢的原因大致是 遇到 tree1.id == tree2.parent_id 的时候

  1. 在 php 这边,只是数组项的读取和比较
  2. 在 Ruby 这边,如果每次的比较都是直接调用 model 的 getter 方法来读取属性,那么就会为每条记录实例化一个 Tree 的 instance,慢就慢在了这里

当然只是我的猜测

比 PHP 慢一点完全可能,但慢到你能够感觉到似乎不太可能,毕竟这种纯粹内存里的操作都是非常快的。

贴代码、运行时间和数据量吧。

#4 楼 @serco 可能是这个原因,毕竟 php 的数组比 rails 的 ActiveRecord 要简单的多 #5 楼 @billy 非常抱歉,实在是工作赶的紧,没太多的时间把这两种 demo 都做出来。

这个需求应该不是及时的,慢一点不要紧

#6 楼 @tini8 你也可以像 php 一样,把数据完全放在数组再处理。再判断是 activerecord 问题,还是 ruby 递归处理数组比 php 慢

#7 楼 @winnie 不是及时的,可以放进缓存,我当然也不会因为这个原因放弃 ruby 用 php,Ruby 比 php 方便的地方多的太多。

用 has_many through source 一下就可以查出来吧

既然你不熟悉 Ruby 又没时间,为何不直接用 act_as_nested_set... 都不用自己写,还快很多

这个叫瓶颈。。。。。。。?你用数组的方式操作 raw sql 也可以的呀。

针对实际问题而谈的话,无限极分类问题可以参考这个 gems https://github.com/stefankroes/ancestry 感觉既然选择一门语言的话,应该关注它的长处并加以利用,ruby 这个环境下本身就有着各式各样的轮子,既然选择了 ruby 也说明是需要敏捷开发的, 这样的话放着现成的东西不去用,自己纠结怎么能写出效率还可以的代码不是适得其反么。

为什么不在循环外查询数据库呢?

#3 楼 @tini8 就按你的思路

Tree.all.group_by(&:parent_id)

应该也比较快吧

#14 楼 @michael0015 在循环外查询数据库并不比在循环内快,我在主贴已经对比过了

我还是支持 5 楼 @billy 大哥的建议,贴代码,数据量以及对比

能超越 io 瓶颈的系统还真是了不得,通常 ruby 代码并不比 js 和 python 代码慢。

#19 楼 @wuwx PHP 实现的代码我在一楼已经补充了

有基情

#20 楼 @tini8 你在用好多次查询的算法和只有一次查询的算法比,而不是在比较 Ruby 和 PHP。至少要把 ruby 这边循环中的 where 避免,纯粹的内存数组对象间运算,然后再比较才公平。

@tini8 你在 Ruby 下也可以 trees = Tree.all,然后再进行你用 PHP 写的那种操作吧。

顺带提一下,Ruby 里面正确解决 Nested Set 的场景可以用这个东西来完美处理:

https://github.com/collectiveidea/awesome_nested_set

ruby 的一些高级特性,如果用不好,会导致严重的性能问题,但是把这些细节都了解了,也不容易,实际上 ruby 并没有比 java 简单

ruby 有没有用原生 Sql 查询的框架 ?

#19 楼 @wuwx 高手在民间。。。

#19 楼 @wuwx 简单 time 一下

➜ time php test.php php test.php 0.07s user 0.01s system 97% cpu 0.083 total ➜ time ruby test.rb ruby test.rb 5.30s user 0.05s system 99% cpu 5.352 total

果然还是 php 厉害!!!

http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

N+1 问题,你在 rails 里面这样操作和 php 比是没得比的,你的设计和用法有问题

Ruby on Rails 在运行速度或性能上对其他语言少有优势。大家选择它是因为开发速度快。经常看到有人说,你用 Ruby on Rails 做出产品来,当你的用户足够多,以至于 Rails 成为瓶颈时,你肯定有足够的资源找人用性能更好的语言改写。

time 一下这个吧... 其实楼主你的 ruby 代码和 php 代码都写复杂了

trees = trees.to_a
q = trees.group_by &:parent_id
trees.each do |t|
  t.children = q[t.parent_id]
end

#35 楼 @luikore 先 to_a 再 group 对楼主感觉还是太复杂了,直接用 rails 的提供的 group by 一步到位:Tree.group(:parent_id)

#35 楼 @luikore 这样得出来的 tree 是这样的,用 json 表示吧:

[{
    name: 一级分类
    children: [
        {
            name: 二级分类1
          }
    ]
},
{
     name: 二级分类1
     children: [
          {
              name: 三级分类1
           },
           {
               name: 三级分类2
            }
},
{
    name: 三级分类1
    children: []
},
{
    name: 三级分类2
    children: []
}]

我需要的是这样的

[{
    name: 一级分类
    children: [
        {
            name: 二级分类1
            children: [
                {
                      name: 三级分类1
                      children: []
                },
                {
                      name: 三级分类2
                      children: []
                }
          }
    ]
}]

#38 楼 @tini8 不好意思写错了...

trees = trees.to_a
q = trees.group_by &:parent_id
trees.each do |t|
  t.children = q[t.id]
end
# 如果要选取根节点, trees.select!{|t| !t.parent_id}
jmmbite 请问如何写成下面这个 json 的 API? 提及了此话题。 08月07日 13:33
需要 登录 后方可回复, 如果你还没有账号请 注册新账号