Mnesia用法介紹
簡單介紹Mnesia
1. Mnesia表的存儲位置:
可以把數據存儲在內存中, 可以存儲在磁盤上, 也可以同時存儲在內存和磁盤上,
可以在不同機器上建立同一份數據的多個副本.
對於存儲在內存表中的數據,如果系統崩潰,則數據會丟失.如果物理內存不夠大,操作系統會使用虛擬內存,這意味着內存-磁盤的數據換頁操作(swap或者page操作),會導致性能急劇惡化.
基於日誌的磁盤表:
對於磁盤表來說,每次"成功提交"一次Mnesia事務,其底層的實現方式是這樣的,它會先將數據寫到一個日誌中,這個日誌會保持增長,每隔一段時間,會把日誌中的數據同步到表中,並清除掉日誌中的條目。
如果系統崩潰了,會先檢查這個日誌,先將尚未寫入的數據同步到數據庫,然後纔開放數據庫服務.
2. 初始和創建數據庫, 刪除數據庫(基於一個節點)
<1> 啓動Erlang的時候指定一個mnesia的數據庫路徑.
erl -sname node1 -mnesia dir '"/home/woomsgadmin/mnesia.node1"'
<2> 創建數據庫, 在當前節點創建一個數據庫
mnesia:create_schema([node()]).
mnesia:start()
<3> 刪除數據庫, 刪除當前節點的數據庫, 必須先停止
mnesia:stop()
mnesia:delete_schema([node()]).
3. 創建表和刪除表:
(創建表格以後可以調用mnesia:info()來查看數據庫當前狀態)
mnesia:create_table(Name, Args)
mnesia:delete_table(Name)
mnesia:clear_table(Name)
Name:atom()
Args:
{type, Type} -> set, ordered_set, bag
{disc_copies, NodeList} - 磁盤 + 內存
{ram_copies, NodeList} - 內存
{disc_only_copies} - 磁盤
{attributes, AtomList}
{attributes, record_info(fields, myrecord)}
mnesia:create_table(shop, [{type, set},
{disc_copies, [node()]},
{attributes, [name, price]}]).
或者:
-record(shop, {name, price}).
mnesia:create_table(shop, [{type, set},
{disc_copies, [node()]},
{attributes, record_info(fields, shop)}]).
delete_table/1會刪除這個表格的所有備份.
clear_table/1刪除這個表格所有備份中的所有數據.
補充:
<1> 創建表的時候使用AtomList和record_info(fields, Record)在返回查詢結果的區別:
-record(employee, {id, name, salary, phone})
創建兩張表:
mensia:create_table(employee1, [{attributes, record_info(fields, employee)}]).
mensia:create_table(employee2, [{attributes, [id, name, salary, phone]}]).
在兩張表中分別插入一條記錄:
mnesia:dirty_write({employee1, 1, "liqiang", 100, 1234567}).
mnesia:dirty_write({employee2, 1, "liqiang", 100, 1234567}).
測試結果:
mnesia:dirty_read({employee1, 1})
[#employee{id = 1,name = "liqiang",salary = 100,phone = 1234567}] %% 返回記錄
mnesia:dirty_read({employee2, 1})
[{employee2, 1, "liqiang", 100, 1234567}] %% 返回AtomList
4. mnesia:wait_for_table(TableList, Timeout)
等待所有的表格都確認可以訪問.
5. 寫入數據的兩種方式:
dirty和transaction, 可以直接寫入,也可以寫入一條記錄,直接寫入的時候,tuple的項是表名.
表結構如下:
-record(user, {id, username, age})
<1> 直接寫入:
Huangwei = #user{id=2, username="huangwei", age=27},
mnesia:dirty_write({user, 1, "liqiang", 29}),
mnesia:dirty_write(Huangwei). %% 可以寫入一條記錄
<2> 通過事務寫入:
Huangwei = #user{id=2, username="huangwei", age=27},
F = fun() ->
mnesia:write({user, 1, "liqiang", 20}), %% 不能直接調用, 必須在事務中執行,否則出錯.
mnesia:write(Huangwei)
end,
mnesia:transaction(F).
6. 刪除數據的兩種方式:
dirty和transaction, 可以按照key(表格tuple結構中的項)刪除, 也可以按照record來刪除.
<1> 直接刪除
mnesia:dirty_delete({user, 1})
mnesia:dirty_delete_object({user, 2, "huangwei", 27})
<2> 通過事務刪除
F = fun() ->
mnesia:delete({user, 1}),
mnesia:delete_object({user, 2, "huangwei", 27})
end,
mnesia:transaction(F).
7. 讀取整條數據:
dirty和transaction, 通過key(表格tuple結構中的項)來讀取.
返回的值是ValueList:
[{user, 1, "liqiang", 20}]
<1> 直接讀取
mnesia:dirty_read({user, 1})
<2> 通過事務讀取
F = fun() ->
mnesia:read({user, 1}).
end,
mnesia:transaction(F).
8. 使用QLC - 查詢列表解析來選取數據
這個工具非常類似於SQL查詢語句的方式查詢數據庫中的內容,
使用的時候必須包含這個hrl文件:
/usr/local/lib/erlang/lib/stdlib-1.16.2/include/qlc.hrl
執行流程分三步:
Query = qlc:q(XXXX),
F = fun() ->
qlc:e(Query)
end,
mnesia:transaction(F).
<1> 選取數據:
SELECT * FROM user
Query = qlc:q([X || X <- mnesia:table(user)]).
<2> 條件查詢:
SELECT id, username FROM user
Query = qlc:q([{X#user.id, X#user.username} || X <- mnesia:table(user)]).
SELECT id, username FROM user WHERE id < 30
Query = qlc:q([{X#user.id, X#user.username} || X <- mnesia:table(user),
X#user.id < 30]).
<3> 關聯查詢:
-record(shop, {item, quantity, cost})
-record(cost, {name, price})
SELECT shop.item, shop.quantity, cost.name, cost.price
FROM shop, cost
WHERE shop.item = cost.name
AND cost.price < 2
AND shop.quantity < 250
Query = qlc:q([{X#shop.item, X#shop.quantity, Y#cost.name, Y#cost.price} || X <- mnesia:table(shop),
Y <- mnesia:table(cost),
X#shop.item =:= Y#cost.name,
Y#cost.price < 2,
X#shop.quantity < 250 ]).
9. 使用mnesia:select來代替QLC的查詢.
這個通常帶來比QLC更快的響應速度, 但是查詢語法沒有QLC簡潔, 詳細的語法參考: ERTS User's Guide
http://www.erlang.org/doc/apps/erts/part_frame.html
(mnesia:select必須在mnesia:transaction中執行, 可以使用mnesia:dirty_select/2直接執行.)
mneisa:select(Tab, [MatchSpec], [, Lock]). (後一項Lock是可選的)
MatchSpec = [MatchFunction]
Function = {MatchHead, [Guard], [Result]}
MatchHead = tuple() | record()
如果成功返回結果列表.
例如:
<1>SELECT id, username FROM user
F = fun() ->
MatchHead = #user{id = '$1', username = '$2', _ = '_'},
Guard = [],
Result = ['$1','$2'],
mnesia:select(user, [{MetchHead, Guard, Result}])
end,
mnesia:transaction(F).
<2>SELECT id, username FROM user WHERE id < 30
F = fun() ->
MatchHead = #user{id = '$1', username = '$2', _ = '_'},
Guard = [{'<', '$1', 30}],
Result = ['$1','$2'],
mnesia:select(user, [{MetchHead, Guard, Result}])
end,
mnesia:transaction(F).
10. 表格中保存複雜的數據:
表格中可以是任意的複雜數據,類型不限,同一張表中的數據格式也沒有嚴格限制. 例如:
-record(design, {id, plan}).
D1 = design#{id = {liqiang, 1},
plan = {circle, 10}},
D2 = design#{id = huangwei,
plan = [{doors, 3}, {windows, 4}, {rooms, 2}]},
F = fun() ->
mnesia:write(D1),
mnesia:write(D2)
end,
mnesia:transaction(F).
11. 補充:
<1> 關於schema
schema是一個特殊的表,它包含了表名、每個表的存儲類型(表應該存儲爲RAM、硬盤或兩者)以及表的位置等信息
不像數據表,schema表裏包含的信息只能通過schema相關的方法來訪問和修改.
我們使用mnesia:create_schema(NodeList)來創建一個新的空的schema, Mnesia是一個完全分佈的DBMS,而schema是一個系統表,
它備份到Mnesia系統的所有節點上如果NodeList中某一個節點已經有schema,則該方法會失敗. 也就是應用程序只需調用該方法一次,
因爲通常只需要初始化數據庫schema一次.
mnesia:delete_schema(DiscNodeList)該方法在DiscNodeList節點上擦除舊的schema,它也刪除所有的舊table和數據
該方法需要所有節點上的Mnesia都停止後才執行.
<2> 如果我們不調用mnesia:cretae_schema(NodeList)會怎樣?
如果我們不調用,可以使用內存表, 但是不能使用磁盤表, 而且內存表在mnesia:stop()之後所有的數據包括表結構會丟失.
如果我們創建了schema, 使用內存表的時候,數據會丟失,但表結構不會仍然保存,也就是我們下次在啓動的時候,內存表的
表結構也會自動加載進來.
例子a - 不創建schema使用內存表:
mnesia:start().
ok
嘗試創建磁盤表,出錯, 因爲我們沒有創建schema.
mnesia:create_table(user, [{disc_copies, [node()]}, {attributes, [id, username, age]}]).
{aborted,{bad_type,user,disc_copies,'node1@liqiang-tfs'}}
創建內存表,成功
mnesia:create_table(user, [{ram_copies, [node()]}, {attributes, [id, username, age]}]).
{atomic,ok}
寫入和讀取數據, 成功
mnesia:dirty_write({user, 1, liqiang, 24}).
ok
mnesia:dirty_read({user, 1}).
[{user,1,liqiang,24}]
停止mnesia, 再次啓動,嘗試寫入數據到user表和從user表中讀取數據,都失敗了, 因爲沒有schema記錄表的信息,所以調用
mnesia:stop/0之後,所有的數據,包括表結構都丟失了.
mnesia:stop().
=INFO REPORT==== 13-Nov-2009::14:10:43 ===
application: mnesia
exited: stopped
type: temporary
stopped
mnesia:start().
ok
mnesia:dirty_read({user, 1}).
** exception exit: {aborted,{no_exists,[user,1]}}
in function mnesia:abort/1
mnesia:dirty_write({user, 1, liqiang, 24}).
** exception exit: {aborted,{no_exists,user}}
in function mnesia:abort/1
例子b - 創建schema使用內存表:
創建內存表, 寫入和讀取數據
mnesia:create_schema([node()]).
ok
mnesia:start().
ok
mnesia:create_table(user, [{ram_copies, [node()]}, {attributes, [id, username, age]}]).
{atomic,ok}
mnesia:dirty_write({user, 1, liqiang, 23}).
ok
mnesia:dirty_read({user, 1}).
[{user,1,liqiang,23}]
停止mnesia, 再次啓動,然後讀取user中的數據,丟失,但是嘗試再次寫入數據到user表中,成功,讀取數據,成功,
說明內存表結構在mnesia重啓後自動加載.
mnesia:stop().
=INFO REPORT==== 13-Nov-2009::14:14:44 ===
application: mnesia
exited: stopped
type: temporary
stopped
mnesia:start().
ok
mnesia:dirty_read({user, 1}).
[]
mnesia:dirty_write({user, 1, liqiang, 23})
ok
mnesia:dirty_read({user, 1}).
[{user,1,liqiang,23}]
<3> 數據模型
Mnesia的數據庫數據由record組成,record由tuple表示
record的個元素是record名,第二個元素是表的key,前兩個元素組成的tuple稱爲oid
所以,在下面的記錄生成的表格中, {user, id}稱爲oid.
-record(user, {id, username, age})
mnesia:dirty_write({user, 1, liqiang, 24}).
mnesia:dirty_read({user, 1}).
12. 多個節點的表數據同步, 每個節點必須使用不同的mnesia-dir:
(在我的測試下,如果兩個節點使用相同的mnesia-dir, 在調用mnesia:start/0的時候會出錯)
erl -sname node1 -setcookie testcookie -mnesia dir '"/home/woomsgadmin/mnesia/node1"'
erl -sname node2 -setcookie testcookie -mnesia dir '"/home/woomsgadmin/mnesia/node2"'
erl -sname node3 -setcookie testcookie -mnesia dir '"/home/woomsgadmin/mnesia/node3"'
在其中一個節點上調用, 會在三個節點對應的mnesia目錄下面創建schema表.
mnesia:create_schema([node() | nodes()]).
在三個節點上分別調用mnesia:start(), mnesia:start/0, mnesia:stop/0是對當前節點起作用,不涉及其它節點.
ok
在其中一個節點上調用, 會在三個節點上創建user表.
mnesia:create_table(user, [{disc_copies, [node() | nodes()]}, {attributes, [id, username, age]}]).
{atomic, ok}
在其中一個節點上寫入數據, 數據會自動同步到其它節點上.
mensia:dirty_write({user, 1, liqiang, 29})
ok
在任意一個節點上讀取數據:
mnesia:dirty_read({user, 1}).
[{user, 1, liqiang, 29}]
注意:
在創建表格前,每個節點必須首先調用mnesia:start/0,否則create_table失敗, 錯誤如下:
mnesia:create_table(user, [{disc_copies, [node() | nodes()]}, {attributes, [id, username, age]}]).
{aborted,{not_active,user,'node3@liqiang-tfs'}}
13.
<1> 事務的屬性:原子性,隔離性,
事務:保證事務中的操作要麼在所有節點上完全原子的成功執行,要麼失敗並且對所有節點沒有任何影響.
同時保證數據的隔離行, 所有通過事務系統訪問數據庫的程序都可以當作自己是對數據有訪問權限的,
也就是多個進程同時更新一個數據的時候,不用擔心數據同步的問題.
<2> 五種鎖:
不同的事務管理器使用不同的策略來滿足隔離屬性Mnesia使用兩階段鎖(two-phase locking)標準技術
這意味着記錄在讀寫之前被加鎖,Mnesia使用5種不同的鎖
a. 讀鎖
在record能被讀取之前設置讀鎖
b. 寫鎖
當事務寫一條record時,首先在這條record的所有備份上設置寫鎖
c. 讀表鎖
如果一個事務掃描整張表來搜索一條record,那麼對錶裏的記錄一條一條的加鎖效率很低也很耗內存(如果表很大,讀鎖本身會消耗很多空間)
因此,Mnesia支持對整張表加讀鎖
d. 寫表鎖
如果事務對錶寫入大量數據,則可以對整張表設置寫鎖
e. Sticky鎖
即使設置鎖的事務終止,鎖也會一直保留在節點上。
當事務執行時,Mnesia使用一個策略來動態獲得必要的鎖
Mnesia自動加鎖和解鎖,程序員不用在代碼裏考慮這些操作
當併發進程對同樣的數據操作時會出現死鎖的情況
Mnesia使用“等待死亡(wait-die)”策略來解決這種問題
當某個事務嘗試加鎖時,如果Mnesia懷疑它可能出現死鎖,那麼該事務就會被強制釋放所有鎖並休眠一會,然後事務將被再次執行.
所以執行事務的F函數可能被嘗試求值一次或者多次.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.