1. 程式人生 > >erlang學習筆記--------mnesia資料庫

erlang學習筆記--------mnesia資料庫

作者:陳峰
來源:部落格園
原文:https://www.cnblogs.com/freebird92/archive/2011/12/22/2296038.html

事務及其它訪問

  • 事務屬性,包括原子性,一致性,隔離性,永續性
  • 髒操作
  • 記錄名字與表名字
  • 活動概念與訪問上下文
  • 巢狀事務
  • 模式匹配
  • 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} | 'endoftable′mnesia:select(Cont)−>transactionabort|[Object],Continuation|′endoftable′mnesia:select(Cont)−>transactionabort|[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′,roomno=′1′,roomno=′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', 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.不要在遍歷的時候進行寫操作。