Erlang——数据库


一般都需要用数据库进行发发工作。Mnesia是一种用Erlang编写的数据库,速度很快。

创建数据库

PS D:\Erlang6m\first\src> erl
Eshell V10.7  (abort with ^G)
1> mnesia:create_schema([node()]).
ok
2> init:stop().
ok
3>
D:\IdeaProjects\erlangdemo\src\demo5> dir
 目录: D:\Erlang6m\first\src
 Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----          2023/8/1     16:42                ...
d-----         2023/9/14     20:55                Mnesia.nonode@nohost

mnesia:create_schema([node()])函数指定在[node()]节点列表(即当前节点下)初始化一个mnesia数据库。

创建Mnesia数据库操作,只需执行一次。执行成功可看到新建了Mnesia.nonode@nohost目录

代码清单

-module(test_mnesia).
-author("").
-include_lib("stdlib/include/qlc.hrl").
-export([do_this_once/0,start/0,reset_tables/0,example_tables/0,demo/1,add_shop_item/3,remove_shop_item/1]).

-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).

reset_tables() ->
  mnesia:clear_table(shop),
  mnesia:clear_table(cost),
  F = fun() ->
    lists:foreach(fun mnesia:write/1, example_tables())
      end,
  mnesia:transaction(F).

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}
  ].

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
  ])).
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).
do(Q) ->
  F = fun() -> qlc:e(Q) end,
  {atomic, Val} = mnesia:transaction(F),
  Val.

启动和载入表(准备工作)

PS D:\Erlang6m\first\src> erl                        
stopped
3> test_mnesia:start().
ok
4> test_mnesia:reset_tables().
{atomic,ok}

查询所有数据

demo(select_shop) ->
  do(qlc:q([X || X <- mnesia:table(shop)]))

qlc:q/1的参数必须是列表解析语法语句本身,不能是其他任何结果相同的表达式。
数据行以不确定的顺序出现,没有做任何排序处理。

运行结果:

5> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
 {shop,apple,20,2.3},
 {shop,orange,100,3.8},
 {shop,pear,200,3.6},
 {shop,banana,420,4.5}]

查询指定列数据

demo(select_some) ->
  do(qlc:q([{X#shop.item, X#shop.quantity} || X <- mnesia:table(shop)]))

运行结果:

6> test_mnesia:demo(select_some).
[{potato,2456},
 {apple,20},
 {orange,100},
 {pear,200},
 {banana,420}]

条件查询

demo(reorder) ->
  do(qlc:q([X#shop.item || X <- mnesia:table(shop), X#shop.quantity < 250]))

把查询条件写到列表解析的语句中。

运行结果:

7> test_mnesia:demo(reorder).
[apple,orange,pear]

多表关联查询

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
  ]))

在shop表的item字段和cost表的name字段之间进行关联,语句如 X#shop.item=:=Y#cost.name
运行结果:

8> test_mnesia:demo(join).
[apple]

添加数据

add_shop_item(Name, Quantity, Cost) ->
  Row = #shop{item=Name, quantity=Quantity, cost=Cost},
  F = fun() ->
        mnesia:write(Row)
      end,
  mnesia:transaction(F).

如果新建的记录和表中的记录主键相同,那么老数据会被覆盖,若不然就作为新的一行数据插入。

运行结果:

9> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
 {shop,apple,20,2.3},   
 {shop,orange,100,3.8}, 
 {shop,pear,200,3.6},   
 {shop,banana,420,4.5}] 
10> test_mnesia:add_shop_item(peach,100,3.0). 
{atomic,ok}
11> test_mnesia:add_shop_item(orange,236,2.8).
{atomic,ok}
12> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
 {shop,apple,20,2.3},   
 {shop,peach,100,3.0},  
 {shop,orange,236,2.8}, 
 {shop,pear,200,3.6},   
 {shop,banana,420,4.5}]

删除数据

remove_shop_item(Item) ->
  Oid = {shop, Item},
  F = fun() ->
        mnesia:delete(Oid)
      end,
  mnesia:transaction(F).

删除数据需要知道这一行数据的目标ID(OID)。OID由表的名称和主键键值构成

运行结果:

13> test_mnesia:remove_shop_item(lemon).
{atomic,ok}
14> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
 {shop,apple,20,2.3},
 {shop,peach,100,3.0},
 {shop,orange,236,2.8},
 {shop,pear,200,3.6},
 {shop,banana,420,4.5}]

Mnesia事务

farmer(…) ->
F = fun() ->
mnesia:write(Row),

mnesia:delete(Oid),
qlc:e(Q) % 用qlc:q/1编译过的查询语句
end,
mnesia:transaction(F).
在匿名函数内部是mnesia:write/1,mnesia:delete/1,qlc:e(Q)这些操作的组合。
调用mnesia:transaction(F)来执行这个函数中的表达式序列,即执行这个匿名函数体。
这种操作能避免死锁问题。可以保证每次Mnesia交易访问一个表时,他会试图进行锁定,具体锁定单个记录还是整张表,他会根据查询语句进行判断。如果判断出这个查询可能会导致死锁,就会立即取消事务,并恢复这个过程中发生的任何更改。
如果锁定时,因为有其他的进程,正在访问数据,因而事物无法成功初始化,系统会稍后一会儿来重试这个事物,所以事物函数中的代码可能会被执行很多次。
所以,交易方法内的代码应该避免任何副作用,比如若函数内有IO输出,因为这个函数可能会被多次重试,会产生大量输出。

取消一个事务

以拿两个苹果换一个橘子为例
代码演示:

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).

我们应该在检查橘子数量,数量足够再去做苹果数量的更新,但现在一上来就做更新是为了演示事务机制,更新操作在事务失败时会回滚。成功才会把橘子和苹果的数据写回到数据库。
执行演示:

1> c(test_mnesia).
{ok,test_mnesia}
2> test_mnesia:start().
ok
3> test_mnesia:reset_tables().
{atomic,ok}
4> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
 {shop,apple,20,2.3},   
 {shop,orange,100,3.8}, 
 {shop,pear,200,3.6},   
 {shop,banana,420,4.5}] 
5> test_mnesia:farmer(50).
{atomic,ok}
6> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
 {shop,apple,120,2.3},  
 {shop,orange,50,3.8},  
 {shop,pear,200,3.6},   
 {shop,banana,420,4.5}] 
7> test_mnesia:farmer(100).
{aborted,oranges}
8> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
 {shop,apple,120,2.3},
 {shop,orange,50,3.8},
 {shop,pear,200,3.6},
 {shop,banana,420,4.5}]

表的类型和位置

我们可以选择将表存储在内存或磁盘上,或者同时存在内存和磁盘上。也可以将数据表保存在一台机器上,或者在多台机器上备份。
内存表
非常快,但数据是瞬态的,当系统崩溃或停掉数据库时,这些数据都会丢失。
使用内存表时一定要检查当前系统的物理内存能否完整,装下这张表的全部数据,如果物理内存不够大,操作系统会使用虚拟内存,意味着大量的内存-磁盘数据会有换页操作,会导致性能的急剧恶化。
内存表的数据丢失问题很重要,可以给这个表在本机建立一个磁盘备份,或者在另外的机器上做个备份。可以是内存的,可以是磁盘的,也可以是两者混合型的。
磁盘表
系统崩溃不会导致其中的数据丢失,前提是没有发生磁盘物理损坏。
对于磁盘表来说,每次成功提交一个事务时,底层都会先将数据写到一个磁盘日志中,这个日志会持续保持增长,每隔一段时间会把日志中的数据同步到数据表中,并清除掉日志中相应条目。如果系统崩溃,下次重启时会检查这个磁盘日志,先将尚未写入的数据同步到数据库中,才会开放数据库服务。所以,只要一个事务成功,那么,其中的数据就会被写到磁盘日志中。尽管系统崩溃,也不会影响这个交易的数据。下次重启时,会自动将这些数据同步到数据表中。
磁盘表只有在一种情况下会发生数据丢失的情况,那就是在系统提交过程中崩溃,此时交易没有成功,提交数据没有写入磁盘日志,所以重启时无法进行同步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值