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.不要在遍歷的時候進行寫操作。