test mnesia.erl
-modu1e(test_mnesia).
-import(lists, [foreach/2]).
-compile(export_all).
-include_lib("stdlib/include/qlc.hrl").
-record(shop, {item, quantity, cost}).
-record(cost, {name, price}).
-record(design, {id, plan}).
do_this_once() ->
mnesia:create_schema([node()]),
mnesia:start(),
mnesia:create_table(shop, [{attributes, record_info(fields, shop)}]),
mnesia:create_table(cost, [{attributes, record_info(fields, cost)}]),
mnesia:create_table(design, [{attributes, record_info(fields, design)}]),
mnesia:stop().
start() ->
mnesia:start(),
mnesia:wait_for_tables([shop, cost, design], 20000).
demo(select_shop) -> do(qlc:q([X || X <- mnesia:table(shop)]));
demo(select_some) -> do(qlc:q([{X#shop.item, X#shop.quantity} || X <- mnesia:table(shop)]));
demo(reorder) -> do(qlc:q([X#shop.item || X <- mnesia:table(shop), X#shop.quantity < 250]));
demo(join) -> do(qlc:q([X#shop.item || X <- mnesia:table(shop),
X#shop.quantity < 250,
Y <- mnesia:table(cost),
X#shop.item =:= Y#cost.name,
Y#cost.price < 2])).
do(Q) ->
F = fun() -> qlc:e(Q) end,
{atomic, Val} = mnesia:transaction(F),
Val.
example_tables() ->
[{shop, apple, 20, 2.3},
{shop, orange, 100, 3.8},
{shop, pear, 200, 3.6},
{shop, banana, 420, 4.5},
{shop, potato, 2456, 1.2},
{cost, apple, 1.5},
{cost, orange, 2.4},
{cost, pear, 2.2},
{cost, banana, 1.5},
{cost, potato, 0.6}].
add_shop_item(Name, Quantity, Cost) ->
Row = #shop{item = Name, quantity = Quantity, cost = Cost},
F = fun() ->
mnesia:write(Row)
end,
mnesia:transaction(F).
remove_shop_item(Item) ->
Oid = {shop, Item},
F = fun() ->
mnesia:delete(Oid)
end,
mnesia:transaction(F).
farmer(Nwant) ->
F = fun() ->
[Apple] = mnesia:read({shop, apple}),
Napples = Apple#shop.quantity,
Apple1 = Apple#shop{quantity = Napples + 2 * Nwant},
mnesia:write(Apple1),
[Orange] = mnesia:read({shop, orange}),
NOranges = Orange#shop.quantity,
if
NOranges >= Nwant ->
N1 = NOranges - Nwant,
Orange1 = Orange#shop{quantity = N1},
mnesia:write(Orange1);
true ->
mnesia:abort(oranges)
end
end,
mnesia:transaction(F).
reset_tables() ->
mnesia:clear_table(shop),
mnesia:clear_table(cost),
F = fun() ->
foreach(fun mnesia:write/1, example_tables())
end,
mnesia:transaction(F).
add_plans() ->
D1 = #design{id = {joe, 1},
plan = {circle, 10}},
D2 = #design{id = fred,
plan = {rectangle, 10, 5}},
D3 = #design{id = {jane, {house, 23}},
plan = {house,
[{floor, 1, [{doors, 3},
{windows, 12},
{rooms, 5}]},
{floor, 2,
[{doors, 2},
{rooms, 4},
{windows, 15}]}]}},
F = fun() ->
mnesia:write(D1),
mnesia:write(D2),
mnesia:write(D3)
end,
mnesia:transaction(F).
get_plan(PlanId) ->
F = fun() -> mnesia:read({design, PlanId}) end,
mnesia:transaction(F).
加载测试数据
test_mnesia:example_tables/0函数用来初始化数据表,填充测试数据。
example_tables() ->
[{shop, apple, 20, 2.3},
{shop, orange, 100, 3.8},
{shop, pear, 200, 3.6},
{shop, banana, 420, 4.5},
{shop, potato, 2456, 1.2},
{cost, apple, 1.5},
{cost, orange, 2.4},
{cost, pear, 2.2},
{cost, banana, 1.5},
{cost, potato, 0.6}].
向Mnesia插入数据的代码
reset_tables() ->
mnesia:clear_table(shop),
mnesia:clear_table(cost),
F = fun() ->
foreach(fun mnesia:write/1, example_tables())
end,
mnesia:transaction(F).
实际上,通过example_tables/1函数返回的列表,只是对其中的每一个元组调用mnesia:write。
do()函数
do(Q) ->
F = fun() -> qlc:e(Q) end,
{atomic, Val} = mnesia:transaction(F),
Val.
它是在Mnesia的事务中调用qlc:e(Q)。Q是一个编译过的QLC查询,qlc:e(Q)执行这个查询,并将查询结果都返回到一个列表之中。返回值{atomic, Val}的意思是事务成功,返回值是val。这里的Val是交易的执行结果。
在表中保存复杂数据
Mnesia是被设计用来保存Erlang数据结构的。更明确地说,你可以把任意类型的Erlang数据结构保存到Mnesia表中。
比如说,我们要把一些建筑的设计方案放到Mnesia的数据库中。首先,我们需要一个记录,用来表示这些设计:
-record(design, {id, plan}).
向数据库中添加一些设计
add_plans() ->
D1 = #design{id = {joe, 1},
plan = {circle, 10}},
D2 = #design{id = fred,
plan = {rectangle, 10, 5}},
D3 = #design{id = {jane, {house, 23}},
plan = {house,
[{floor, 1, [{doors, 3},
{windows, 12},
{rooms, 5}]},
{floor, 2,
[{doors, 2},
{rooms, 4},
{windows, 15}]}]}},
F = fun() ->
mnesia:write(D1),
mnesia:write(D2),
mnesia:write(D3)
end,
mnesia:transaction(F).
向数据库添加一些设计
1>test mnesia:start().
ok
2>test_mnesia:add _plans().
{atomic,ok}
访问函数来获取这些设计
get_plan(PlanId) ->
F = fun() -> mnesia:read({design, PlanId}) end,
mnesia:transaction(F).
3> test_mnesia:get_plan(fred).
{atomic, [{design, fred, {rectangle, 10, 5}}]}
4 >test_mnesia:get_plan(jane, {house, 23}}).
{atomic, [{design, {jane, {house, 23}},
{house, [{floor, 1, [{doors, 3},
{windows, 12},
{rooms,5]]},
{floor, 2,[{doors, 2},
{rooms,4},
{windows, 15}]3]}}]}
数据库中的数据结构与编程语言的数据结构之间并不存在“匹配阻抗”问题。这意味着向数据库增加或者删除复杂的数据结构是非常迅速的。
表的类型和位置
Mnesia数据表可以进行很多种不同的配置。首先我们可以选择将表存储在内存或者磁盘上(或者同时保存在内存和磁盘上),其次,可以选择将数据表保存在一台机器上,或者在多台机器上备份。
在设计阶段,我们就必须要认真思考我们想要存储的数据有什么样的特性,然后根据这些特
性来选择。下面是Mnesia数据表的属性。
内存表
非常快,但其中数据是瞬态的,所以当系统崩溃或者停掉数据库的时候,这些数据都会丢失。
磁盘表
系统崩溃不会导致其中的数据丢失
创建表
创建一个表的时候,我们调用mnesia:create_table(Name,Args)函数。这里的Args是由{Key,Val}这样的元组组成的列表。如果表创建成功,会得到{atomic,ok}返回值,否则会返回{aborted,Reason}。
下面是最常见的create_table参数。
Name
这是表的名字(一个原子)。通常来说它的取值就是Erlang记录的名字,这个记录就是我们之前提到过的表示每行数据的记录。
{type,Type}
这个参数表明表的类型。Type是set、ordered_set或者bag。其意义与我们在15.2节中提到的一样。
{disc_copies,NodeList}
NodeList是Erlang节点的列表,这个表的备份会存储在这些节点上。使用这个选项时,系统会同时创建内存表和磁盘表。
一个节点上有一个disc_copies类型的备份表,同时另一个节点上可能有以不同类型存储的同一个表。这个配置提供了如下梦幻般的特性。
(1)读操作在内存中执行,速度飞快。
(2)写操作时,数据被写到持久介质上。
{ram_copies,NodeList}
NodeList中的每个Erlang节点上都会有这个表的内存备份。
{disc_only_copies,NodeList}
NodeList中的每个Erlang节点上都会有一个这个表的磁盘备份。因为这个表不是在内存中的,所以,访问起来会慢一些。
fattributes,AtomList}
AtomList的列表表明了表中每列的名称。需要留意的是,当我们为此建立了xxx这样的Erlang记录时,我们可以简单的使用这样的语法{attribute,record_info(fields,xxx)},而不需要显式的逐个字段的指定列名。
表属性的常见组合
下面是一些常见的选项配置,覆盖了我们经常会用的各种情况
mnesia:createtable(shop,[Attrs])
只在一个节点的内存中存储。
如果这个节点失效,表中的数据都会丢失。
在所有的方法中,这是最快的组合。
机器的物理内存要能放下整张表。
mnesia:create_table(shop,[Attrs,{disc_copies,[node()]}])
在一个节点上做内存+磁盘备份。
如果节点失效,表中的数据可以从磁盘上恢复。
读操作很快,写操作稍慢。
机器的物理内存要能放下整张表。
mnesia:create_table(shop,[Attrs,{disc_only_copies,[node()]}]
单个节点的磁盘备份。
无须考虑内存大小,可以存储大量数据。
比用内存备份的方式要慢一些。
mnesia:create_table(shop,[Attrs,iram_copies,[node() , someOtherNode()]})
这个表在两个节点上有内存备份。
如果两个节点都失效了,数据会丢失。
机器的物理内存要能放下整张表。
在两个节点上都能访问这个表。
mnesia:create_table(shop,[Attrs,{disc_copies,[node() , someOtherNode()]}])
两个节点上都做磁盘备份。
每个节点上都可以进行恢复。
两个节点都失效,仍然可以恢复。
表的行为
当一个表在多个Erlang节点上备份时,它会尽可能的进行同步。如果某个节点失效,系统会继续保持运作,但是少了一个备份。当失效的节点再次上线时,它会与其他节点再次进行同步,备份数量得以保持。