Mnesia:Erlang数据库

一:创建初始数据库

在做任何事之前,必须先创建一个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表

商 品数 量费 用
apple202.3
orange1003.8
pear2003.6
banana4204.5
potato24561.2

                                                           cost表

名 称价 格
apple1.5
orange2.4
pear2.2
banana1.5
potato0.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.从表里选择数据

下面这个查询会从 shop 表里选择 item quantity 列:
%% 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}]
上面这个查询里的 X 值是类型为 shop 的记录。 你就会记得X#shop.item 指的是 shop 记录里的 item 字段。因此,元组 {X#shop.item, X#shop. quantity} 是一个由X item quantity 字段所组成的元组。

3.从表里有条件选择数据

下面这个查询会列出 shop 表里所有符合条件(库存数量小于 250 )的商品。也许可以用它来确定哪些商需要再次订购。请注意这个条件如何自然地表达为列表推导的一部分。
%% 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.从两个表里选择数据(联接)

现在假设想要重新订购的商品必须库存少于 250 ,并且价格低于 2.0 个货币单位。要做到这一点,我们需要访问两个表。这个查询如下:
%% 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

三:添加和移除数据库里的数据

我们再次假定你已经创建了数据库并定义了一个 shop 表。现在我们想为该表添加或移除行。

1.添加行

可以像下面这样给 shop 表添加一行:
%% 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).
它会创建一个 shop 记录并把它插入表中。如下所示:
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.移除行

要移除某一行,需要知道该行的对象 ID Object ID ,简称 OID )。它由表名和主键的值构成。
%% 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)
F 是一个不带参数的 fun 。我们在 F 里调用了下列函数的某种组合形式: mnesia:write/1 mnesia:delete/1 qlc:e(Q) Q 是 用 qlc:q/1 编 译 的 查 询 )。 构 建 完 fun 后,调用 mnesia:transaction(F),它会执行 fun 里的表达式序列。
事务( transaction)能阻止有缺陷的程序代码,但更重要的是,它能阻止对数据库的并发访问。
Mnesia采用一种悲观锁定 pessimistic locking )的策略。每当 Mnesia 事务管理器访问一个表
时,都会根据上下文情况尝试锁定记录甚至整个表。如果它发现这可能导致死锁,就会立即中止
事务并撤销之前所做的改动。

1.中止事务

我们的商店附近有一个农场,那里有农民在种植苹果。这个农民很喜欢橙子,他用苹果来交易橙子。当前的价格是两个苹果换一个橙子。因此,要购买N 个橙子,农民需要支付 2*N 个苹果。下面的函数会在农民购买橙子时更新数据库:
%% 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}]
下午,农民想要再买 100 个橙子:
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}]
当这个事务失败时(即调用 mnesia:abort(Reason) 时), mnesia:write 所做的更改都被撤销了。正因为如此,数据库恢复到了进入事务之前的状态。

2.载入测试数据

test_mnesia:example_tables/0 函数的作用是提供数据来初始化各个数据表。元组的第一个元素是表名,后面是遵循原始记录定义顺序的表数据。
%% 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}
   ].
接下来是负责把来自示例表的数据插入 Mnesia 的代码。它所做的就是对 example_ tables/1返回列表里的每一个元组调用 mnesia:write
%% 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()函数

demo/1 调用的 do() 函数略微复杂一些。如下:
%% test_mnesia.erl
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 的数据结构。事实上,可以在 Mnesia 表里保存任何你喜欢的Erlang数据结构。为了演示这一点,我们假定有许多建筑师想要把他们的设计保存在一个Mnesia 数据库里。首先必须定义一个记录来表示他们的设计。
%% 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}]}]}}]}
如你所见,数据库的键和提取出来的记录都可以是任意的 Erlang 数据类型。

六:表的类型和位置

表可以位于内存或磁盘里(或者两者皆有)。其次,表可以位于单台机器上,也可以在多台机器之间复制。在设计表时必须考虑到想要保存在表里的数据类型。以下是各类表的性质。
(1)内存表
它们的速度非常快,但是里面的数据是 易失 的,所以如果机器崩溃或者你停止了 DBMS, 数据就会丢失。
(2)磁盘表
磁盘表应该不会受到系统崩溃的影响(前提是磁盘没有物理损坏)。

1. 创建表

要创建一个表,我们会调用 mnesia:create_table(Name, ArgS) ,其中 ArgS 是一个由{Key,Val}元组构成的列表。如果表创建成功, create_table 就会返回 {atomic, ok} ,否则返回{aborted, Reason} 。下面是 create_table 最常用的一些参数。
(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)} 这种语法(也可以显式指定一个记录字段
名列表)。

2.表的行为 

当一个表被复制到多个 Erlang 节点时,它会尽可能远地进行同步。如果某个节点崩溃了,系统仍然会正常工作,但是副本的数量会减少。当崩溃的节点重新上线时,它会与其他存有副本的节点重新进行同步。
  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明明如皓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值