• [Erlang0008][OTP] 高效指南 表和数据库(ets mnesia)


    原文链接:http://www.erlang.org/doc/efficiency_guide/tablesDatabases.html

    错误之处欢迎指正

    7 表和数据库

     

    7.1 ets,dets和mnesia

     

    每一个Ets的例子都适用于Mnesia。通常所有Ets的例子都适用于Dets表。

    Select/Match 操作

     

    Ets和Mnesia的Select/Match操作代价很高。通常需要检索整张表。你应该尽可能优化你的数据结构,以便最少的使用select/match。但是,如果你确实需要select/match的话,它还是比tab2list高效很多的。接下来的章节会有这方面的例子,包括如何避免使用select/match。函数ets:select/2和mnesia:select/3会优于ets:match/2,ets:match_object/2,mnesia:match_object/3。

    注意: 也有例外的情况可以不检索整张表,例如当检索ordered_set表时一个关键字不足以精确查找到结果,或者是Mnesia有第二索引,用这个字段去select/match。如果关键字能够精确匹配出结果,当然select/match是没有意义的,除非你有一个bag表,并且只对检索结果的一个子集感兴趣。
    (NOTE:There are exceptions when the complete table is not scanned, for instance if part of the key is bound when searching an ordered_set table, or if it is a Mnesia table and there is a secondary index on the field that is selected/matched. If the key is fully bound there will, of course, be no point in doing a select/match, unless you have a bag table and you are only interested in a sub-set of the elements with the specific key.)

    当创建一个被用作select/match操作的记录时,想绝大部分字段的值是'_'。最简单快捷的方法是下面这样

    #person{age = 42, _ = '_'}.

    删除一个元素

    删除操作被当做是成功的,如果一个元素不在表里。因此,删除之前,所有尝试去检测元素是否存在于ets/mnesia表的操作都是非必要的。这里有个ets表的操作。

    DO
    
    ...
    ets:delete(Tab, Key),
    ...
    
    DO NOT
    
    ...
    case ets:lookup(Tab, Key) of
        [] ->
            ok;
        [_|_] ->
            ets:delete(Tab, Key)
    end,
    ...

    获取数据

    不要重复获取已有的数据!假设你有一个模块处理抽象数据类型Person。你导出了一个接口函数print_person/1,它调用了三个内部函数print_name/1, print_age/1, print_occupation/1。

    注意:如果函数print_name/1等是接口函数,那完全是另一回事了,因为你不想让接口使用者知道内部数据结构。

    DO
    
    %%% Interface function
    print_person(PersonId) ->
        %% Look up the person in the named table person,
        case ets:lookup(person, PersonId) of
            [Person] ->
                print_name(Person),
                print_age(Person),
                print_occupation(Person);
            [] ->
                io:format("No person with ID = ~p~n", [PersonID])
        end.
    
    %%% Internal functions
    print_name(Person) -> 
        io:format("No person ~p~n", [Person#person.name]).
                          
    print_age(Person) -> 
        io:format("No person ~p~n", [Person#person.age]).
    
    print_occupation(Person) -> 
        io:format("No person ~p~n", [Person#person.occupation]).
    
    DO NOT
    
    %%% Interface function
    print_person(PersonId) ->
        %% Look up the person in the named table person,
        case ets:lookup(person, PersonId) of
            [Person] ->
                print_name(PersonID),
                print_age(PersonID),
                print_occupation(PersonID);
            [] ->
                io:format("No person with ID = ~p~n", [PersonID])
        end.
    
    %%% Internal functionss
    print_name(PersonID) -> 
        [Person] = ets:lookup(person, PersonId),
        io:format("No person ~p~n", [Person#person.name]).
    
    print_age(PersonID) -> 
        [Person] = ets:lookup(person, PersonId),
        io:format("No person ~p~n", [Person#person.age]).
    
    print_occupation(PersonID) -> 
        [Person] = ets:lookup(person, PersonId),
        io:format("No person ~p~n", [Person#person.occupation]).

    非持久性数据存储

    对于非持久性数据库存储,Ets表要优于本地Mnesia表。即使Mnesia的dirty_write操作只比ets写操作代价高那么一点点。Mnesia还必须检测这个表是否有别的拷贝,或者是否有索引,所以每次dirty_write至少包含一次ets lookup操作。因此ets写永远比Mnesia写快。

    tab2list

    假设我们有一个ets表,用idno作为key,内容如下:

    [#person{idno = 1, name = "Adam",  age = 31, occupation = "mailman"},
     #person{idno = 2, name = "Bryan", age = 31, occupation = "cashier"},
     #person{idno = 3, name = "Bryan", age = 35, occupation = "banker"},
     #person{idno = 4, name = "Carl",  age = 25, occupation = "mailman"}]

    如果我们必须得到ets表里所有的数据,可以用ets:tab2list/1。但是通常我们只对部分数据感兴趣,这种情况下ets:tab2list/1的代价就太高了。如果我们只想要每个记录的一个字段,例如年龄,应该这样做:

    DO
    
    ...
    ets:select(Tab,[{ #person{idno='_', 
                              name='_', 
                              age='$1', 
                              occupation = '_'},
                    [],
                    ['$1']}]),
    ...
    
    DO NOT
    
    ...
    TabList = ets:tab2list(Tab),
    lists:map(fun(X) -> X#person.age end, TabList),
    ...

    如果我们只对名叫Bryan的人的年龄感兴趣,应该:

    DO
    
    ...
    ets:select(Tab,[{ #person{idno='_', 
                              name="Bryan", 
                              age='$1', 
                              occupation = '_'},
                    [],
                    ['$1']}]),
    ...
    
    DO NOT
    
    ...
    TabList = ets:tab2list(Tab),
    lists:foldl(fun(X, Acc) -> case X#person.name of
                                    "Bryan" ->
                                        [X#person.age|Acc];
                                     _ ->
                                         Acc
                               end
                 end, [], TabList),
    ...
    
    REALLY DO NOT
    
    ...
    TabList = ets:tab2list(Tab),
    BryanList = lists:filter(fun(X) -> X#person.name == "Bryan" end,
                             TabList),
    lists:map(fun(X) -> X#person.age end, BryanList),
    ...

    如果我们需要表中名叫Bryan的人的所有信息:

    DO
    
    ...
    ets:select(Tab, [{#person{idno='_', 
                              name="Bryan", 
                              age='_', 
                              occupation = '_'}, [], ['$_']}]),
    ...
    
    DO NOT
    
    ...
    TabList = ets:tab2list(Tab),
    lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList),
    ...

    Ordered_set表

    如果表中的数据要经常被访问,那么有序的KEYS是很有意义的。ordered_set类型的表可以用来代替大部分常见的set类型表。ordered_set表的key总是按照Erlang term顺序排序,所以select,match_object,foldl的返回值也是根据Key排序的。ordered_set的first和next操作也是按照key的排序返回的。

    注意:ordered_set表保证每条记录都按照key的顺序处理。ets:select/2的结果也是按照这个顺序,即使结果中不包含key

    7.2 ets特性

     

    利用好ets的Key

    ets表时一个单key的表(不论哈希表还是树结构)而且应该只用一个key。换句话说,任何可能的情况下都用key去lookup。一个lookup查询对set ets表来说代价是一个常数,对于ordered_set ets表来说是O(logN)。用Key去lookup永远好于需要整表遍历。上面的例子中,字段idno是表的key,所有用姓名字段来查询的,都需要遍历整表来得到匹配结果。

    一个简单的解决办法就是用name字段代替idno字段作为Key,但是如果名字不唯一就会有问题。更常用的解决方法是创建第二张表,名字为key,idno为数据,把这个表索引到主表的name字段。第二张表必须要和原表保持一致。mnesia可以为你做这些,但是一个自制的索引表会比用mnesia高效的多。

    前面例子的索引表必须是个bag表(因为有重复key),内容如下:

    [#index_entry{name="Adam", idno=1},
     #index_entry{name="Bryan", idno=2},
     #index_entry{name="Bryan", idno=3},
     #index_entry{name="Carl", idno=4}]

    查询名为Bryan的人的年龄,应该这样做:

    ...
    MatchingIDs = ets:lookup(IndexTable,"Bryan"),
    lists:map(fun(#index_entry{idno = ID}) ->
                     [#person{age = Age}] = ets:lookup(PersonTable, ID),
                     Age
              end,
              MatchingIDs),
    ...


    注意上面的代码永远不要用match/2代替lookup/2。lists:map/2只用来遍历了名为Bryan的数据,所以主表的查询操作已经最少了。

    使用索引表会产生一些开销,当向表中插入记录时,因此插入的记录越多越效率越低。但是记住能用key来查询元素,意义是很大的。

    7.3 mnesia特性

     

    第二索引

    如果你经常以非Key字段来查询表操作,你将会因使用mnesia:select/match_object而损失性能,因为这些函数会遍历整表。。你可以创建一个第二索引来代替,用mnesia:index_read来快速访问,但是这也会消耗更多的内存,例如:

    -record(person, {idno, name, age, occupation}).
            ...
    {atomic, ok} = 
    mnesia:create_table(person, [{index,[#person.age]},
                                  {attributes,
                                        record_info(fields, person)}]),
    {atomic, ok} = mnesia:add_table_index(person, age), 
    ...
    
    PersonsAge42 =
         mnesia:dirty_index_read(person, 42, #person.age),

    事务

    事务用来确保分布式mnesia数据库保持一致,即使许多不同的进程并行更新。但是如果你对实时性要求很高,推荐使用脏操作代替事务。当使用脏操作时会损失一致性保证,通常的解决方法是让一个进程来更新表。别的进程都发送更新请求给这个进程。

    ...
    % Using transaction
    
    Fun = fun() ->
              [mnesia:read({Table, Key}),
               mnesia:read({Table2, Key2})]
          end, 
    
    {atomic, [Result1, Result2]}  = mnesia:transaction(Fun),
    ...
    
    % Same thing using dirty operations
    ...
    
    Result1 = mnesia:dirty_read({Table, Key}),
    Result2 = mnesia:dirty_read({Table2, Key2}),
    ...
  • 相关阅读:
    Python循环-break和continue
    Python-SocketServer
    Python模块-datetime模块
    Python模块-time模块
    dataframe转化(一)之python中的apply(),applymap(),map() 的用法和区别
    python面试题--连续出现最大次数
    消金ABS
    《风控策略笔记》(二)政策与定价--量化风险管理应用
    hadoop fs –stat 命令
    《风控策略笔记》(一)政策与定价--风控体系及政策设计
  • 原文地址:https://www.cnblogs.com/liangjingyang/p/2705451.html
Copyright © 2020-2023  润新知