一:创建初始数据库
在做任何事之前,必须先创建一个Mnesia数据库。这件事只需要做一次。
$ erl
1> mnesia:create_schema([node()]).
ok
2> init:stop().
ok
$ ls
Mnesia.nonode@nohost
mnesia:create_schema(NodeList)会在NodeList(它必须是一个包含有效Erlang节点的列表)里的所有节点上都初始化一个新的Mnesia数据库。这个案例给出的节点列表是[node()],也就是当前节点。Mnesia完成初始化并创建了一个名为Mnesia.nonode@nohost的目录结构来保存数据库。然后退出Erlang shell,用操作系统的ls命令进行验证。如果在一个名为joe的分布式节点上重复这个练习,就会得到以下输出:
$ erl -name joe
(joe@doris.myerl.example.com)1> mnesia:create_schema([node()]).
ok
(joe@doris.myerl.example.com)2> init:stop().
ok
$ls
Mnesia.joeddoris.myerl.example.com
也可以在启动Erlang时指向一个特定的数据库:
$ erl -mnesia dir "/home/joe/some/path/to/Mnesia.company"'
1>mnesia:create schema([node()]).
ok
2>init:stop().
ok
/home/joe/some/path/to/Mnesia.company是将要保存这个数据库的目录。
二:数据库查询
创建完数据库之后,我们就可以拿它练练手了。首先来看Mnesia的查询。浏览它们之后,你也许会惊讶地发现Mnesia的查询非常像SQL和列表推导,事实上,列表推导和SQL之间的高度相似性并不是什么奇怪的事情,因为它们都是基于数学里的集合论。假如有如下所示数据列表:
shop表
商 品 | 数 量 | 费 用 |
---|---|---|
apple | 20 | 2.3 |
orange | 100 | 3.8 |
pear | 200 | 3.6 |
banana | 420 | 4.5 |
potato | 2456 | 1.2 |
cost表
名 称 | 价 格 |
---|---|
apple | 1.5 |
orange | 2.4 |
pear | 2.2 |
banana | 1.5 |
potato | 0.6 |
Mnesia里的表是一个包含若干行的异键或同键表,其中每一行都是一个Erlang记录。要在Mnesia里表示这些表,需要一些记录定义来对表里的行进行定义,代码如下:
%% test_mnesia.erl
-record(shop,{item,quantity,cost}).
-record(cost,{name,price}).
在操作数据库之前,需要创建一个数据库架构(schema),启动数据库,添加一些表定义并停止数据库,然后再重启它。这些事只需要做一遍。下面是代码:
%% test_mnesia.erl
do_this_once() ->
mnesia:create_schema([node()]),
mnesia:start(),
mncsia:crcatc_tablc(shop, [[attributes, record_info(ficlds, shop)}]),
mnesia:create_table(cost, [{attributes, record_info(fields, cost)}]),
mnesia:create_table(design, [fattributes, record_info(fields, design)}]),
mnesia:stop().
运行如下:
1> test_mnesia:do_this_once().
stopped
=INFO REPORT====24-May-2013::15:27:56===
application: mnesia
exited: stopped
type: temporary
1.选择表里所有数据
下面是选择shop表里所有数据的代码。(对那些懂SQL的读者,我们在代码里用注释符号开头的片段展示了能执行相应操作的等效SQL命令。)
%% test_mnesia.erl
%% 等效SQL命令
%% SELECT * FROM shop;
demo(select_shop) ->
do(qlc:q([X || X <- mnesia:table(shop)]));
这段代码的重点是qlc:q调用,它会把查询(也就是它的参数)编译成一种用于查询数据库的内部格式。把编译后的查询传递给一个名为do()的函数,它会在接近test_mnesia底部的位置进行定义,负责运行查询并返回结果。为了让这一切能够轻易从erl里调用,我们把它映射到函数demo(select_shop)上。在使用这个数据库之前,需要做一些例行的启动和载入表定义工作。它们必须在使用数据库之前运行,但在每个Erlang会话里只需运行一次。
%% test_mnesia.erl
%% 等级SQL命令
%% SELECT FROM shop;
demo(select_shop) ->
do(qlc:q([X || X <- mnesia:table(shop)]));
现在可以启动数据库并生成一个查询了。
1> test_mnesia:start().
ok
2> test_mnesia:reset_tables().
{atomic,ok}
3> test mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
{shop,orange,100,3.8},
{shop,apple,20,2.3},
{shop,pear,200,3.6},
{shop,banana,420,4.5}]
注: 表里各行出现的顺序是随机的。
在这个示例中构建查询的代码行如下:
qlc:q([X || X <- mnesia:table(shop)])
它看上去非常像一个列表推导(参见4.5节)。事实上,qlc就代表了query list comprehension(查询列表推导)。它是其中一个可以用来访问Mnesia数据库内数据的模块。[X || X <- mnesia:table(shop)]的意思是“一个由X组成的列表,X提取自shop这个Mnesia表”。X的值是Erlang的shop记录。
2.从表里选择数据
%% test_mnesia.erl
%% 等效SQL命令
%% SELECT item,quantity FROM shop;
demo(select_some) ->
do(qlc:q([{X#shop.item, X#shop.quantity} || X <- mnesia:table(shop)]))
运行结果:
4> test_mnesia:demo(select_some).
{orange,100},{pear,200},{banana,420},{potato,2456},[apple,20}]
3.从表里有条件选择数据
%% test_mnesia.erl
%% 等效SQL命令
%% SELECT shop.item FROM shop
%% WHERE shop.quantity 250;
demo(reorder) ->
do(qlc:q([X#shop.item || X <- mnesia:table(shop),
X#shop.quantity < 250
]));
运行结果:
5> test_mnesia:demo(reorder).
[orange, pear, apple]
4.从两个表里选择数据(联接)
%% test mnesia.erl
%% 等效SQL命令
%% SELECT shop.item
%% FROM shop,cost
%% WHERE shop.item = cost.name
%% AND cost.price < 2
%% AND 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
]))·
运行结果:
6> test_mnesia:demo(join).
[apple]
%% 这里的关键在于联接shop表里的item名称和cost表里的name。
X#shop.item =:= Y#cost.name
三:添加和移除数据库里的数据
1.添加行
%% test_mnesia.erl
add_shop_item(Name, Quantity, Cost) ->
Row = #shop{item=Name, quantity = Quantity, cost = Cost},
F = fun() ->
mnesia:write(Row)
end,
mnesia:transaction(F).
1> test_mnesia:start().
ok
2> test_mnesia:reset_tables().
{atomic,ok}
%% 列出shop表
3> test_mnesia:demo(select_shop).
[{shop,0 range,100,3.8o000},
{shop,pear,200,3.60000},
{shop,banana,420,4.50000},
{shop,potato,2456,1.20000},
{shop,app1e,20,2.30000}]
%% 添加一个新行
4> test_mnesia:add_shop_item(orange,236,2.8).
{atomic,ok}
%% 再次列出shop表,看看有什么变化
5> test_mnesia:demo(select_shop).
[{shop,0 range,236,2.80000},
{shop,pear,200,3.60000},
{shop,banana,420,4.50000},
{sh0p,p0tat0,2456,1.20000},
{shop,apple,20,2.30000}]
注:shop表的主键是表内的第一列,也就是shop记录里的item字段。这个表属于“异键”类型
2.移除行
%% test_mnesia.erl
remove_shop_item(Item)->
Oid = {shop,Item},
F = fun() ->
mnesia:delete(Oid)
end,
mnesia:transaction(F).
运行结果:
6> test_mnesia:remove_shop_item(pear).
{atomic,ok}
%% 列出这个表,pear已经不见了
7> test_mnesia:demo(select_shop).
[{shop,0 range,236,2.80o00},
{shop,banana,420,4.50000},
{shop,p0tat0,2456,1.20000},
{shop,apple,20,2.30000}]
8> mnesia:stop().
ok
四:Mnesia 事务
do_something(...)->
F = fun() ->
%% ...
mnesia:write(Row)
%% ... 或者 ...
mnesia:delete(Oid)
%% ... 或者 ...
qlc:e(Q)
end,
mnesia:transaction(F)
1.中止事务
%% test_mnesia.erl
farmer(Nwant) ->
%% Nwant = 农民想要购买的橙子数量
F = fun() ->
%% 找出苹果的数量
[Apple] = mnesia:read({shop, apple}),
Napples = Apple#shop.quantity,
Applel = Apple#shop{quantity = Napples + 2*Nwant},
%% 更新数据库
mnesia:write(Applel),
%% 找出橙子的数量
[Orange] = mnesia:read({shop, orange}),
NOranges = Orange#shop.quantity,
if
NOranges >= Nwant ->
N1 = NOranges - Nwant,
Orangel = Orange#shop{quantity=N1},
%% 更新数据库
mnesia:write(Orangel);
true ->
%% 糟糕,橙子数量不够
mnesia:abort(oranges)
end
end,
mnesia:transaction(F).
1> test_mnesia:start().
ok
2> test_mnesia:reset_tables().
{atomic,ok}
%% 列出shop表
3> test_mnesia:demo(select_shop).
[{shop, orange,100,3.80o00},
{shop,pear,200,3.60000},
{shop,banana,420,4.50000},
{shop,potato,2456,1.20000},
{shop,apple,20,2.30000}]
%% 农民买了50个橙子
%% 支付了100个苹果
4> test_mnesia:farmer(50).
{atomic,ok}
%% 再次打印sh0p表
5> test_mnesia:demo(select_shop).
[{shop,orange,50,3.80000},
{shop,pear,200,3.60000},
{shop,banana,420,4.50000},
{shop,potato,2456,1.20000},
{shop,apple,120,2.30000}]
6> test_mnesia:farmer(100).
{aborted,oranges}
7> test_mnesia:demo(select shop).
[{shop,orange,50,3.80000},
{shop,pear,200,3.60000},
{shop,banana,420,4.5000},
{shop,potato,2456,1.20000},
{shop,app1e,120,2.30000}]
2.载入测试数据
%% test_mnesia.erl
example_tables() ->
[%% shop表
{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表
{cost, apple, 1.5},
{cost, orange, 2.4},
{cost, pear, 2.2},
{cost, banana, 1.5},
{cost, potato, 0.6}
].
%% test_mnesia.erl
reset_tables() ->
mnesia:clear_table(shop),
mnesia:clear_table(cost),
F = fun() ->
foreach(fun mnesia:write/1, example_tables())
end,
mnesia:transaction(F).
3.do()函数
%% test_mnesia.erl
do(Q) ->
F = fun() -> qlc:e(Q) end,
{atomic, Val} = mnesia:transaction(F),
Val.
五:表里保存复杂数据
%% test_mnesia.erl
-record(design, {id, plan}).
%% test_mnesia.erl
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,
[{foor, 1,
[{doors, 3},
{windows, 12},
{rooms, 5}]},
{foor, 2,
[{doors, 2},
{rooms, 4},
{windows, 15)])]}},
F = fun() ->
mnesia:write(Dl),
mnesia:write(D2),
mnesia:write(D3)
end,
mnesia:transaction(F).
1> test_mnesia:start().
ok
2> test_mnesia:add_plans().
{atomic,ok}
%% test_mnesia.erl
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}]}]}}]}
六:表的类型和位置
(1)内存表它们的速度非常快,但是里面的数据是 易失 的,所以如果机器崩溃或者你停止了 DBMS, 数据就会丢失。(2)磁盘表磁盘表应该不会受到系统崩溃的影响(前提是磁盘没有物理损坏)。
1. 创建表
(1)Name它是表的名称(一个原子)。按惯例它是一个 Erlang 记录的名称,表里的各行是这个记录的实例。(2){type, Type}它指定了表的类型。 Type 是 set 、 ordered_set 或 bag 中的一个。这些类型与 19.1 节里描述的类型含义相同。(3) {disc_copies, NodeList}NodeList 是一个 Erlang 节点列表,这些节点将保存表的磁盘副本。当我们使用这个选项时,系统还会在执行这个操作的节点上创建一个表的内存副本。你可以既在一个节点上保存 disc_copies 类型的副本表,又在另一个节点上保存该表的不同类型。这种做法能满足以下要求:(4) 读取操作非常快,并在内存里执行;(5)写入操作在持久性存储介质里执行。(6){ram_copies, NodeList}NodeList 是一个 Erlang 节点列表,这些节点将保存表的内存副本。(7){disc_only_copies, NodeList}NodeList 是一个 Erlang 节点列表,这些节点将只保存表的磁盘副本。这些表没有内存副本,访问起来会比较慢。(8){attributes, AtomList}这个列表包含表里各个值的列名。请注意,要创建一个包含 Erlang 记录 xxx 的表,可以用{attributes, record_info(fields, xxx)} 这种语法(也可以显式指定一个记录字段名列表)。