第十七章 Mnesia: Erlang数据库
17.1 数据库查询
用于测试的表
%% apple 20 2.3
%% orange 100 3.8
%% pear 200 3.6
-record(shop, {item, quantity, cost}).
%% apple 1.5
%% orange 2.4
%% pear 2.2
-record(cost, {name, price}).
17.1.1 选取表中所有的数据
%% 从Mnesia的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}]
17.1.2 选取表中的数据
%% 选取Mnesia的shop表中每条记录的两个字段
demo(select_some) ->
do(qlc:q([{X#shop.item, X#shop.quantity} || X <- mnesia:table(shop)]));
运行结果:
4> test_mnesia:demo(select_some).
[{potato,2456},
{orange,100},
{apple,20},
{pear,200},
{banana,420}]
17.1.3 按条件选取表中的数据
%% 查询shop表中库存量小于250的条目
demo(reorder) ->
do(qlc:q([X#shop.item || X <- mnesia:table(shop),
X#shop.quantity < 250
]));
运行结果:
5> test_mnesia:demo(reorder).
[orange,apple,pear]
17.1.4 从两个表选取数据(关联查询)
%% 查询shop表中库存量小于250且单价低于2的条目
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]
17.2 增删表中的数据
17.2.1 增加一行
%% 先构建记录
%% 然后指明调用的函数
%% 最后调用transaction执行操作
add_shop_item(Name, Quantity, Cost) ->
Row = #shop{item=Name, quantity=Quantity, cost=Cost},
F = fun() ->
mnesia:write(Row)
end,
mnesia:transaction(F).
运行结果:
7> test_mnesia:add_shop_item(orange, 236, 2.8).
{atomic,ok}
# 对于已经存在的记录将会更新
8> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
{shop,orange,236,2.8},
{shop,apple,20,2.3},
{shop,pear,200,3.6},
{shop,banana,420,4.5}]
17.2.2 删除一行
%% 以表名和主键键值作为数据的目标ID
remove_shop_item(Item) ->
Oid = {shop, Item},
F = fun() ->
mnesia:delete(Oid)
end,
mnesia:transaction(F).
运行结果:
9> test_mnesia:remove_shop_item(orange).
{atomic,ok}
10> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
{shop,apple,20,2.3},
{shop,pear,200,3.6},
{shop,banana,420,4.5}]
17.3 Mnesia事务
%% Mnesia事务的实现形式
%% 首先声明一个没有参数的函数F
%% 然后将具体的数据更新操作在F中实现
%% 最后调用transaction执行操作, 而transaction中实现了事务
do_something(...) ->
F = fun() ->
mnesia:write(Row),
mnesia:delete(Oid),
qlc:e(Q)
end,
mnesia:transaction(F)
17.3.1 取消一个事务
farmer(Nwant) ->
%% 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).
运行结果:
11> test_mnesia:reset_tables().
{atomic,ok}
12> 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}]
13> test_mnesia:farmer(50).
{atomic,ok}
14> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
{shop,orange,50,3.8},
{shop,apple,120,2.3},
{shop,pear,200,3.6},
{shop,banana,420,4.5}]
15> test_mnesia:farmer(100).
{aborted,oranges}
16> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
{shop,orange,50,3.8},
{shop,apple,120,2.3},
{shop,pear,200,3.6},
{shop,banana,420,4.5}]
17.3.2 加载测试数据
reset_tables() ->
%% 清空表
mnesia:clear_table(shop),
mnesia:clear_table(cost),
%% 声明函数, 执行写入
F = fun() ->
foreach(fun mnesia:write/1, example_tables())
end,
mnesia:transaction(F).
17.3.3 do()函数
%% Q是一个编译过的QLC查询
%% 在事务中调用qlc:e(Q)执行查询并返回到列表Val中
do(Q) ->
F = fun() ->qlc:e(Q) end,
{atomic, Val} = mnesia:transaction(F),
Val.
17.4 在表中保存复杂数据
%% 自定义的数据结构
-record(design, {id, plan}).
%% 将其添加到数据库
add_plans() ->
%% id, plan可以是任意类型的Erlang数据结构
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).
运行结果:
17> test_mnesia:add_plans().
{atomic,ok}
18> test_mnesia:get_plan({joe, 1}).
{atomic,[{design,{joe,1},{circle,10}}]}
19> test_mnesia:get_plan(fred).
{atomic,[{design,fred,{rectangle,10,5}}]}
17.5 表的类型和位置
Mnesia表有内存表和磁盘表, 其中磁盘表会使用记录操作日志的方式保证系统崩溃后可以通过日志来同步数据。
17.5.1 创建表
%% Name 表名(原子类型)
%% Args 参数(列表类型, 由{Key, Val}构成)
%% {type, Type} Type取值为set, ordered_set, bag
%% {disc_copies, NodeList} 备份存储的Erlang节点列表, 可以实现读操作使用内存而写操作数据被持久到磁盘
%% {ram_copies, NodeList} 内存备份的Erlang节点列表
%% {disc_only_copies, NodeList} 磁盘备份的Erlang节点列表
%% {attributes, AtomList} 声明表中每列的名称
mnesia:create_table(Name, Args)
17.5.2 表属性的常见组合
# Attrs是形如{attributes, ...}的元组
# 只在一个节点的内存中存储。
# 如果这个节点失效, 表中的数据将会丢失。
# 在所有的方法中, 这是最快的组合。
# 机器的物理内存要能放得下整张表。
- mnesia:create_table(shop, [Attrs])
# 在一个节点上做内存+磁盘备份
# 如果节点失效, 表中的数据可以从磁盘上恢复。
# 读操作很快, 写操作稍慢。
# 机器的物理内存要能放得下整张表。
- mnesia:create_table(shop, [Attrs, {disc_copies, [node()]}])
# 单个节点的磁盘备份。
# 无须考虑内存大小, 可以存储大量数据。
# 比用内存备份的方式要慢一些。
- mnesia:create_table(shop, [Attrs, {disc_only_copies, [node()]}])
# 这个表在两个节点上有内存备份。
# 如果两个节点都失效了, 数据会丢失。
# 机器的物理内存要能放得下整张表。
# 在两个节点上都能访问这个表。
- mnesia:create_table(shop, [Attrs, {ram_copies, [node(), someOtherNode()]}])
# 两个节点都做磁盘备份。
# 每个节点上都可以进行恢复。
# 两个节点都失效, 仍然可以恢复。
- mnesia:create_table(shop, [Attrs, {disc_copies, [node(), someOtherNode()]}])
17.5.3 表的行为
与Hadoop的HDFS类似
17.6 创建和初始化数据库
1> mnesia:create_schema([node()]).
ok
2> init:stop().
ok
3> matrix@MBP:17 $ ls
Mnesia.nonode@nohost test_mnesia.beam test_mnesia.erl