mnesia数据库优化

我一直都很想使用mnesia数据库,但mnesia的数据库一直很头疼的就是内存的占用量,我到 现在还没有办法,另外一个担心就是效率,我不知道执行到底需要占用多长时间,正好,从 另外的一个mysql上有一个不大的测试数据,可以让我检查下。

先说明下数据结构,这个数据结构很简单,是一个短信的结构,变成erlang的语法如下:

-record(sms, {
          id,
          time,
          date,
          code,
          content,
          status,
          status2
         }).

mysql中这个数据总共是55272条,我想取出其中一天(比如2014-10-31)的内容,然后根据 总量,status,status2分组,并按照一天的每一个小时进行统计,最后出现下面的三组结果:

  1. 指定一天的每个小时中所发送的短信量
  2. 指定一天的每个小时中发送成功的短信量(status=1)
  3. 指定一天中每个小时中发送失败的短信量(status2=1)

需要注意的是,源数据中,status和status2并非在0,1之间,而是可能有其他状态。我选 定的这一天总共有2327的量。

mysql中的time是按照“YYYY-MM-DD hh-mm-ss”的格式存储的,因此,如果取某一天的话,需 要使用left函数,转换成erlang就是{datetime,{{YYYY,MM,DD},{hh,mm,ss}}}。

先说下测试的环境,16G内存,i8四核,其实这个条件无论对于mysql和mnesia都是完全完美 的。

我先作了一个用于输出的测试代码:

test() ->
    io:format("use z=~p~n", [timer:tc(?MODULE, test, [z])]),
    ok.

后续的其他方法可以通过test(a),test(b),test(c)….等等来输出

test(z) ->
    Test={date, {2014, 
                 10, 
                 30}},
    F = fun() ->
                All = qlc:e(qlc:q([D||D<-mnesia:table(sms),
                              D#sms.orderdate=:= Test])),
                [All, All, All]
        end,
    {atomic, [All_sms, Sms_Succ, Sp_Succ]} = mnesia:transaction(F),
    ok;

这个是最简单的一个,只输出某一天的量,不做过滤,使用qlc的方式,据说select要比qlc 更高效些,因此也做了各使用select的等同代码:

test(y) ->
    Test={date, {2014, 
                 10, 
                 30}},
    F = fun() ->
                A = #sms{orderdate=Test, _='_'},
                All = mnesia:select(sms, [{A, [], ['$_']}], read),
                [All, All, All]
        end,
    {atomic, [All_sms, Sms_Succ, Sp_Succ]} = mnesia:transaction(F),
    ok;

简单运行一下测试,select果然要比qlc高效些, qlc关键在于表现不稳定,在我的测试中, 在8ms和26ms之间晃荡,而select一般都在8~11ms之间,以10ms为多数。

接下来是真实的测试内容了,首先第一个方案是将选择出来的sms按照一定的顺序排列,然 后根据小时筛选统计:

test(a) ->
    Test={date, {2014, 
                 10, 
                 30}},

    F = fun() ->
                A = qlc:q([D||D<-mnesia:table(sms),
                              D#sms.orderdate=:= Test]),
                B = qlc:keysort(2, A, [{order, descending}]),
                Qc = qlc:cursor(B),
                All = qlc:next_answers(Qc, all_remaining),
                qlc:delete_cursor(Qc),
                All_succ = [D||D<-All,  D#sms.status=:=1],
                ALL_SP_succ = [D||D<-All,  D#sms.sp_status=:=1],
                F1 = fun(T1) ->
                             case T1 of
                                 [] ->
                                     [];
                                 [Head|Still] -> 
                                     order_sort_hour(Still, Head#sms.ordertime, [])
                             end
                     end,
  
                All_sms = F1(All),
                Sms_succ = F1(All_succ),
                Sp_List= F1(ALL_SP_succ),
                [All_sms, Sms_succ, Sp_List]
        end,
    {atomic, [All_sms, Sms_Succ, Sp_Succ]} = mnesia:transaction(F),
    ok.
order_sort_hour([], _, Result) ->
    lists:reverse(Result);
order_sort_hour([Order|All], {datetime, {{Y, M, D}, {_HH, _MM, _SS}}}, []=Result) ->
    Time2 = Order#sms.ordertime,
    {datetime, {{Y, M, D}, {HH2, _, _}}} = Time2,
    order_sort_hour(All, Time2, [[HH2, 1]|Result]);
order_sort_hour([Order|All], {datetime, {{Y, M, D}, {HH, _MM, _SS}}}= Time, [[HH, Num]|Rest]=Result) ->
    Time2 = Order#sms.ordertime,
    {datetime, {{Y, M, D}, {HH2, _, _}}} = Time2,
    case HH2 of
        HH ->
            order_sort_hour(All, Time, [[HH, Num+1]|Rest]);
        _ ->
            order_sort_hour(All, Time2, [[HH2, 1]|Result])
    end.

这种方式测试下来,平均竟然需要50ms左右!!

改进方法,我首先觉得这个这个让qlc将结果排序不靠谱,觉得会占用时间,乱许处理会比 较快些,于是,有了下面的改进:

test(b) ->
    Test={date, {2014, 
                 10, 
                 30}},

    F = fun() ->
                A = qlc:q([D||D<-mnesia:table(sms),
                              D#sms.orderdate=:= Test]),
                Qc = qlc:cursor(A),
                All = qlc:next_answers(Qc, all_remaining),
                qlc:delete_cursor(Qc),
                All_succ = [D||D<-All,  D#sms.status=:=1],
                ALL_SP_succ = [D||D<-All,  D#sms.sp_status=:=1],
                T = [{Num, 0} || Num<-lists:seq(0, 23)],
                All_sms = sort2(All, T),
                Sms_succ = sort2(All_succ, T),
                Sp_List= sort2(ALL_SP_succ, T),
                [All_sms, Sms_succ, Sp_List]
        end,
    {atomic, [All_sms, Sms_Succ, Sp_Succ]} = mnesia:transaction(F),
    ok.

sort2([], Result) ->
    [[H, N]||{H,N}<-Result];
sort2([Order|All], Result) ->
    Time2 = Order#sms.ordertime,
    {datetime, {{Y, M, D}, {HH, _, _}}} = Time2,
    HH2 = HH+1,
    R2 = lists:nth(HH2, Result),
    {HH, Num} = R2,
    sort2(All, lists:keyreplace(HH, 1, Result, {HH, Num+1})).

测试一下,时间大幅缩短到20ms左右,果然排序比较坑阿,当然,时间的另外一个排序的排 进也会对时间有很大的影响。

于是,有了第三个版本:

test(c) ->
    Test={date, {2014, 
                 10, 
                 30}},
    F = fun() ->
                All = qlc:e(qlc:q([D||D<-mnesia:table(sms),
                              D#sms.orderdate=:= Test])),
                All_succ = [D||D<-All,  D#sms.status=:=1],
                ALL_SP_succ = [D||D<-All,  D#sms.sp_status=:=1],
                T = [{Num, 0} || Num<-lists:seq(0, 23)],
                All_sms = sort2(All, T),
                Sms_succ = sort2(All_succ, T),
                Sp_List= sort2(ALL_SP_succ, T),
                [All_sms, Sms_succ, Sp_List]
        end,
    {atomic, [All_sms, Sms_Succ, Sp_Succ]} = mnesia:transaction(F),
    ok;

乍一看,c的版本和b的版本没太大区别,但c的版本普遍比b的版本要快平均7ms,也就是 13ms左右,看来,使用数据库的游标还是有代价的。

最起始的时候,我们说select要比qlc高效,于是,就有了d的版本:

test(d) ->
    Test={date, {2014, 
                 10, 
                 30}},
    F = fun() ->
                A = #sms{orderdate=Test, _='_'},
                All = mnesia:select(sms, [{A, [], ['$_']}]),
                All_succ = [D||D<-All,  D#sms.status=:=1],
                ALL_SP_succ = [D||D<-All,  D#sms.sp_status=:=1],
                T = [{Num, 0} || Num<-lists:seq(0, 23)],
                All_sms = sort2(All, T),
                Sms_succ = sort2(All_succ, T),
                Sp_List= sort2(ALL_SP_succ, T),
                [All_sms, Sms_succ, Sp_List]
        end,
    {atomic, [All_sms, Sms_Succ, Sp_Succ]} = mnesia:transaction(F),
    ok;

但结果,有些出乎我的意料,d的版本在大部分时间都比c的版本要长,虽然不多,就不到 1ms,但终究是长了,总之,没有意料中的快。但是否是最快的,这个就不敢保证了,反正 拼着不折腾不舒服斯基的小强精神,我做了下面的版本:

test(e) ->
    Test={date, {2014, 
                 10, 
                 30}},
    F = fun() ->
                All = qlc:e(qlc:q([D||D<-mnesia:table(sms),
                              D#sms.orderdate=:= Test])),
                All_succ = [D||D<-All,  D#sms.status=:=1],
                ALL_SP_succ = [D||D<-All,  D#sms.sp_status=:=1],
                T = [{Num, 0} || Num<-lists:seq(0, 23)],
                sort3(All, T, T, T)
        end,
    {atomic, [All_sms, Sms_Succ, Sp_Succ]} = mnesia:transaction(F),
    ok.
sort3([], Result, Result2, Result3) ->
    [[[H, N]||{H,N}<-Result], [[H, N]||{H,N}<-Result2], [[H, N]||{H,N}<-Result3]];
sort3([Order|All], Result, Result2, Result3) ->
    Time2 = Order#sms.ordertime,
    {datetime, {{Y, M, D}, {HH, _, _}}} = Time2,
    HH2 = HH+1,
    R2 = lists:nth(HH2, Result),
    {HH, Num} = R2,
    S1 = case Order#sms.status of
             1 ->
                 1;
             _ ->
                 0
         end,
    S2 = case Order#sms.sp_status of
             1 ->
                 1;
             _ ->
                 0
         end,
    sort3(All, lists:keyreplace(HH, 1, Result, {HH, Num+1}), lists:keyreplace(HH, 1, Result2, {HH, Num+S1}), lists:keyreplace(HH, 1, Result3, {HH, Num+S2})).

e的版本将三个的整理放到一起进行,直接说结果吧,比d,c要慢,比b要快,因此,目前最 快就是c和d了,但c和d使用的记录都是整条记录,那么如果记录减少会不会影响呢。于是我 再次改进了下:

test(f) ->
    Test={date, {2014, 
                 10, 
                 30}},
    F = fun() ->
                A = #sms{orderdate=Test, ordertime='$1', status='$2', sp_status='$3', _='_'},
                All = mnesia:select(sms, [{A, [], ['$$']}], read),
                All_succ = [[{D, S1, S2}]||[{D, S1, S2}]<-All, S1=:=1],
                ALL_SP_succ = [[{D, S1, S2}]||[{D, S1, S2}]<-All, S2=:=1],
                T = [{Num, 0} || Num<-lists:seq(0, 23)],
                All_sms = sort4(All, T),
                Sms_succ = sort4(All_succ, T),
                Sp_List= sort4(ALL_SP_succ, T),
                [All_sms, Sms_succ, Sp_List]
        end,
    {atomic, [All_sms, Sms_Succ, Sp_Succ]} = mnesia:transaction(F),
    ok.
sort4([], Result) ->
    [[H, N]||{H,N}<-Result];
sort4([[Time2, _, _]|All], Result) ->
    {datetime, {{Y, M, D}, {HH, _, _}}} = Time2,
    HH2 = HH+1,
    R2 = lists:nth(HH2, Result),
    {HH, Num} = R2,
    sort4(All, lists:keyreplace(HH, 1, Result, {HH, Num+1})).

然后,奇迹来了,这个操作在大部分时候,仅比y的操作慢不到1ms,也就是说,大部分时候, 3个操作的过滤使用了不到1ms的时间。

是否还有更快的操作,应该还有吧,但我已经没有太大兴趣测试了。

mnesia使用中文创建数据库并支持REST操作

这个不是什么高深的内容,记录下来,给自己提个醒。

起源于我想给我的所有数据库添加一个统一的操作接口,当然,使用英
文会更方便,更批量化,代码更少等等,我在弄的时候突发奇想,使用中
文吧,这样网页中看着连想都不用想,而且还可以给别人看,连教育部都
不支持中文了,杂家也随下大流。

其实erlang的record完全支持中文的声明,比如下面的例子:

      -record('公告',
        {'ID', %ID
         '标题' = "", % 标题
         '内容' = "", % 描述
         '显示' = true% 禁用
        }).
      

只所以可以成功是因为单引号的字符串,在erlang中就是作为一个atom
类型的,如果你想给里面加个空格什么的,也没问题。当然,使用的时候也
要加上单引号。

其实,erlang的record使用中文的话,也就上面的这些。但如果在shell
中使用,就比较烦人了,在命令行下使用下面的语句:

     mnesia:transaction(fun() -> mnesia:match_object('公告') end)).
     

会出现:illegal atom 的错误,或许修改环境变量的编码可以修改,我
没有测试。

接下来是http的REST接口,一般都使用的是表格显示,我喜欢使用JSON数
据格式,这个就更简单些了。还是上面的record:

继续阅读mnesia使用中文创建数据库并支持REST操作

mnesia数据库index的选择

之前刚说了mnesia的一些经验,不想回头就犯错了。这里给自己提醒下。

对于下列的数据结构:

       -record(worker, {id,
                       name,
                       desc,
                       state}).
       

其中,id是唯一的,name可能会重复,但几率较小,state只有4种,
wait/doing/done/error,表示状态。

我在创建的时候,name作为第二个索引,id自然的成了第一索引,一切
都没有问题。最后数据量大约超过了N百万条。

然后,其他的逻辑流程出现了问题,state变为doing状态,没有正确切
换到其他的状态,这样,这个worker就一直处于doing状态,为了重置这些
状态,需要遍历所有的doing状态的worker,并且根据一个过滤列表,检查
后重置为其他的状态,比如done还是wait

我一次检查几百到几万条不等,为了加快速度,就手动的给worker的数
据库再添加一个索引:state,然后悲剧就发生了。

数据库变的非常非常的占用内存,超过几十GB了,虽然我虚拟内存有上
百GB,但也不能这么用啊,关键是,其他的进程对这一数据库的操作就会
变的非常的慢,而且,由于系统内存耗光,整个系统也非常慢。

其实,原因也很简单,问题就在于索引的选择上。对于mnesia而言,为
了加快读取,会对数据库遍历,根据索引生成对应的列表,这样,当每操
作一条数据的时候,就会更新索引,对于上述例子而言,根据state生成的
每个状态的列表元素的个数都是恐怖的,达到了上百万量,这样,当操作
数据的时候,每次都要遍历这些上百万个元素,效率就会非常的低,换言
之,state不适合做索引,而适合做分表

在索引选择上,每个属性对应的数据个数越少,越适合做索引,反之,
适合做分表。

mnesia数据库使用体验

这只是我自己在过去一周中对mnesia使用的经验总结,并不针对所有范

为什么使用mnesia?

呃,不为什么,好玩一把,相对于其他的数据库,mnesia还算是最为熟
悉的,因为我就是数据库白痴

过去一直不曾关注这块,数据库在我眼里一直当文件使用,最近突发奇
想,看看mnesia如果应用于真实的可商用的情况下有何好处,当然我比较
看重的是mnesia分布这块,也想试试。于是就开始折腾一通了。

继续阅读mnesia数据库使用体验

语言陷阱:mnesia中关于index的选择

erlang使用mnesia进行数据库操作的时候出现的bad_type错误。
对于下面的数据结构:

       -record(info, {name, id}).
       

使用下面的语句来创建表:

       case mnesia:create_table(info, [
                                    {disc_copies, Nodes},
                                     {index, [name]},
                                    {type, set},
                                    {attributes, record_info(fields, info)}
                                     ]) of
        {atomic, ok} ->
            ok;
        Any ->
            error_logger:error_report([
                                       {message, "Cannot install table"},
                                       {table, info},
                                       {error, Any},
                                       {nodes, Nodes}])
    end.
       

编译的时候不会有任何问题,但运行的时候,会出现{aborted,{bad_type,info,{index,[2]}}}的错误,原因在于使用record的第一个元素来做index,如果不是第一个的话则没有任何问题。