• mnesia数据库学习笔记三


    mnesia数据库学习笔记三

    事务及其它访问

    • 事务属性,包括原子性,一致性,隔离性,持久性
    • 脏操作
    • 记录名字与表名字
    • 活动概念与访问上下文
    • 嵌套事务
    • 模式匹配
    • Iteratoin

    1、事务属性

    Mnesia事务就是将一系列数据库操作封装在一个函数块中。函数块作为一个事务进行运行所有叫作函数对象。保作将影响到所有相关节点上。

    Mnesia提供了如下重要属性:

    • 事务函数内部不涉及操作在其它事务中,当它在执行一系列表操作时
    • 事务保证了要么在所有节点上操作成功,要么失败但没有在任何节点上产生负作用
    • 提供了Atomicity(原子性),Consistency(一致性),Isolation(隔离性),Durability(持久性).ACID.

    2、锁

    Mnesia使用5种锁

    • 读锁, 在复制记录读前加读锁
    • 写锁, 在事务写记录前,会在指定记录的所有复件上添加写锁
    • 读表锁,如果一个事务遍历整个表搜索指定条件记录,最低效是设置记录锁,同时也是大量内存消耗。些时可以指定一个读表锁。
    • 写表锁,如果一个事务要大量写入一个表,最好你用写表锁
    • 粘锁,当一个事务操作完成后,锁依然存在。

    Mnesia采用动态策略应对如mnesia:read/1,自动添加和释放锁。程序员无须考虑。

    Mnesia不用担心死锁问题,当系统怀疑某个锁死锁时,它会释放该锁,然后再执行一遍, 多个相同事务不能保证顺序执行,但可以保证都执行。程序员不能设定某个事务的优先级。

    切不可执行代码带有事务副作用。如在receive语句在事务,可能产生系统假死等。

    当一个事务异常终止时,Mnesia会自动的释放其持有的所有锁。

    Mnesia函数:

    mnesia:transaction(Fun) -> {aborted, Reason} | {atomic, Value},该函数执行一个带函数Fun的事务。

    mnesia:read({Tab, Key}) -> transaction abort | RecordList, 返回所有带Key的记录

    mnesia:wread({Tab, Key}),该函数和上一函数相同,除由读锁改为写锁,如果执行一个读记录,修改, 写入记录,那么直接用写锁效率更高。

    mnesia:write(Record).写入一条记录到数据库
    mnesia:delete({Tab, Key}),删除指定表键的所有记录

    mnesia:delete_object(Record)用Record的OID删除对应记录

    粘锁:

    普通情况下mnesia每次写入操作的时候,都会锁住所有复件。如果针对一个大量写入到在一个复件的情况下, 那么粘锁就可以派上用场了。在没有其它复件存在的情况下,粘锁和普通锁差不多,没有什么特别影响。

    粘锁在第一次使用后, 并不立即释放。下次我们使用粘锁在同一节点的同一记录上,那么这个粘锁就已经设置好了。所有效率更高。多用于一主多从库, 对于有用到两复件间交互则消耗较大。

    表锁:

    如果我们在表上进行大量记录的读写操作, 那么设置表锁定会更加高效,但是会阻塞其它并行事务。我用以下函数进行设置:

    mnesia:read_lock_table(Tab) 在表上设置一个读锁

    mnesia:write_lock_table(Tab)在表上设置一个写锁

    或者

    mnesia:lock({table,Tab},read)

    mnesia:lock({table,Tab},write)

    全局锁

    通常情况下,写锁都会锁住所有活动节点的复件。读锁仅需要一个节点。函数mnesia:lock/2可以锁住所有复件表

    mnesia:lock({global, GlobalKey, Node}, LockKind)

    LockKind ::= read | write | ...

    3、脏操作

    大量的事务开锁可能导致低效,可以引入脏操作。可以应用在报文路由类应用,使用mnesia函数不带事务,这就是脏操作,需要权衡失去了mnesia的原子性和隔离性。主要优势就是执行更快。

    脏操作也是保证了记录操作的一致性的。每一个单一读写操作都是原子操作。所有操作失败返回({aborted, Reason})。具体脏操作函数如下:

    mnesia:dirty_read({Tab, Key}),

    mnesia:dirty_write(Record)

    mnesia:dirty_delete({Tab,Key})

    mnesia:dirty_delete_object(Record)

    mnesia:dirty_firest(Tab)

    mnesia:dirty_next(Tab, key)

    mnesia:dirty_last(Tab)

    mnesia:dirty_prev(Tab, Key)

    mnesia:dirty_slot(Tab, Slot)

    mnesia:dirty_update_counter({Tab, Key}, Val)

    mnesia:dirty_match_object(Pat)

    mnesia:dirty_index_match_object(Pat, Pos)

    mnesia:dirty_all_key(Tab)
    4、记录名与表名

    在Mnesia中,在一个表中所有记录名字必须相同。所有记录必须是同一记录类型。但是记录名字可以与表名字不同。

     mnesia:create_table(subscriber, [])

     TabDef = [{record_name, subscriber}],
     mnesia:create_table(my_subscriber, TabDef),
     mnesia:create_table(your_subscriber, TabDef).

    mnesia:write(subscriber, #subscriber{}, write)
    mnesia:write(my_subscriber, #subscriber{}, sticky_write
    mnesia:write(your_subscriber, #subscriber{}, write)

     5、活动概念与访问上下文
    如下函数对象都可以作为事务函数mnesia:tansaction的参数:

    mnesia:write/3 (write/1, s_write/1)
    mnesia:delete/3 (delete/1, s_delete/1)

    mnesia:delete_object/3 (delete_object/1, s_delete_object/1)
    mnesia:read/3 (read/1, wread/1)
    mnesia:match_object/2 (match_object/1)
    mnesia:select/3 (select/2)
    mnesia:foldl/3 (foldl/4, foldr/3, foldr/4)
    mnesia:all_keys/1
    mnesia:index_match_object/4 (index_match_object/2)
    mnesia:index_read/3
    mnesia:lock/2 (read_lock_table/1, write_lock_table/1)
    mnesia:table_info/2

    这些函数能够运行在事务活动中,也可以运行在如下活动中:

    • transaction(事务)
    • sync_transaction(同步事务)
    • async_dirty(脏异步)
    • sync_dirty(脏同步)
    • ets

    sync_transaction会等待所有活动复件上的事务操作完成后才返回。

    sync_dirty会等待所有活动复件上的脏操作完成后返回

    存储类型为RAM_copies , disc_copies的Mnesia表内部是以“ets-tables"实现的。它允许用户直接访问表,mnesia:ets/2将按照非常原始的上下文进行处理,它会假设这些表存储类型为RAM_copies ,没有复件在其它节点 ,没有订阅触发,没有检查点更新。

    6、嵌套事务

    事务可以进行嵌套, 子事务必须和父事务运行在同一进程,当子事务中止时,子事务的调用者会收到{aborted, Reason},任何子事务操作都会回滚。如果子事务提交, 由子事务写入的记录将会产生在父事务。

    所有锁的释放是在最上层事务终止后。

       add_subscriber(S) ->
              mnesia:transaction(fun() ->
                 %% Transaction context
                 mnesia:read({some_tab, some_data}),
                 mnesia:sync_dirty(fun() ->
                     %% Still in a transaction context.
                     case mnesia:read( ..) ..end), end).
          add_subscriber2(S) ->
              mnesia:sync_dirty(fun() ->
                 %% In dirty context
                 mnesia:read({some_tab, some_data}),
                 mnesia:transaction(fun() ->
                     %% In a transaction context.
                     case mnesia:read( ..) ..end), end).

     7、模式匹配

    当使用mnesia:read/3不能满足需求的时候, mnesia提供了以下函数用于匹配记录:

        mnesia:select(Tab, MatchSpecification, LockKind) ->
            transaction abort | [ObjectList]
        mnesia:select(Tab, MatchSpecification, NObjects, Lock) -> 
            transaction abort | {[Object],Continuation} | '$end_of_table'
        mnesia:select(Cont) ->
            transaction abort | {[Object],Continuation} | '$end_of_table'
        mnesia:match_object(Tab, Pattern, LockKind) ->
            transaction abort | RecordList
      表中记录以hash保存,或者ordered_set都是有提升查询效率。在数据匹配中,‘_'表示任意数据结构,匹配元组的第一个元素必须为记录名字。‘$<number>'作为Erlang变量。

     如下:

       Wildpattern = mnesia:table_info(employee, wild_pattern),
      %% Or use
      Wildpattern = #employee{_ = '_'},意义如  {employee, '_', '_', '_', '_', '_',' _'}.

     又如:

     Pat = #employee{sex = female, _ = '_'},
     F = fun() -> mnesia:match_object(Pat) end,
     Females = mnesia:transaction(F).

     假如我们要找房号与职员号是相同的职员:

      Pat = #employee{emp_no = '$1', room_no = '$1', _ = '_'},
      F = fun() -> mnesia:match_object(Pat) end,
      Odd = mnesia:transaction(F).

     假如我们找在二楼的男性职员:

      MatchHead = #employee{name='$1', sex=male, room_no={'$2', '_'}, _='_'},
      Guard = [{'>=', '$2', 220},{'<', '$2', 230}],
      Result = '$1',
      mnesia:select(employee,[{MatchHead, Guard, [Result]}])

     8、Iteration (迭代)

    Mnesia提供了如下几个函数遍历所有记录

         mnesia:foldl(Fun, Acc0, Tab) -> NewAcc | transaction abort
         mnesia:foldr(Fun, Acc0, Tab) -> NewAcc | transaction abort
         mnesia:foldl(Fun, Acc0, Tab, LockType) -> NewAcc | transaction abort
         mnesia:foldr(Fun, Acc0, Tab, LockType) -> NewAcc | transaction abort
     这些函数会将Fun遍历应用到表Tab表上,并把结构放入累计集Acc0中,可以按需要指定锁类型。Fun有两个参数,第一个是从表中取出的记录,第二个是累计集。

     例如要查找薪资级别在10以下的员工:

       find_low_salaries() ->
         Constraint =
              fun(Emp, Acc) when Emp#employee.salary < 10 ->
                     [Emp | Acc];

                   (_, Acc) ->
                        Acc
                 end,
            Find = fun() -> mnesia:foldl(Constraint, [], employee) end,
            mnesia:transaction(Find)

    将薪资上调到10级,返回所有涨薪和:

          increase_low_salaries() ->
             Increase =
                 fun(Emp, Acc) when Emp#employee.salary < 10 ->
                        OldS = Emp#employee.salary,
                        ok = mnesia:write(Emp#employee{salary = 10}),
                        Acc + 10 - OldS;
                    (_, Acc) ->
                        Acc
                 end,
            IncLow = fun() -> mnesia:foldl(Increase, 0, employee, write) end,
            mnesia:transaction(IncLow).
    在遍历中可以做很多事情,但是要特别留意性能和内存消耗。

    在调用这些函数时,如果表在另外一个节点上,会消耗很不必要的网络traffic。

    Mnesia也提供了一些其它函数来遍历表,如果表不是ordered_set,那么遍历结果顺序是未知的。

       mnesia:first(Tab) ->  Key | transaction abort
       mnesia:last(Tab)  ->  Key | transaction abort
       mnesia:next(Tab,Key)  ->  Key | transaction abort
       mnesia:prev(Tab,Key)  ->  Key | transaction abort
       mnesia:snmp_get_next_index(Tab,Index) -> {ok, NextIndex} | endOfTable
    其中函数mnesia:first/1和last/1只对 ordered_set有效。当搜索到'$end_of_table'就退出。

    在用mnesia:fold遍历时,进行删除或者写入操作都会创建一个本地拷贝进行修改。所有会占用大量内存。可能会降低性能,所有尽量避免。

    在脏操作上下文中,修改记录不保存在本地拷贝,每条件记录都是分别更新。如果表复件存在其它节点中,会产生大量网络开销。特别是mnesai:first/1 , mnesia:next/2,同理dirty_first和dirty_next.不要在遍历的时候进行写操作。

  • 相关阅读:
    Linux alias 设置快捷命令,打包
    网站改版了
    团队建设经典故事
    easyui中jquery重复引用问题(tab内存泄露问题)
    EasyUI 兼容 IE6 方法总结
    EasyUI datagrid frozencolumn的bug???
    docker-compose快速部署环境笔记
    MySQL获取距离
    Jenkins Publish FTP远程部署过程
    慎用uniapp开发商业级应用
  • 原文地址:https://www.cnblogs.com/freebird92/p/2296038.html
Copyright © 2020-2023  润新知