热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Erlang笔记Mnesia:Erlang数据库简单使用

1.创建数据库在当前节点创建PSE:\ERL\MnesiaerlEshellV8.3(abortwith^G)1node().nonode@nohost2mnesia:cre




1. 创建数据库


  • 在当前节点创建

PS E:\ERL\Mnesia> erl
Eshell V8.3 (abort with ^G)
1> node().
nonode@nohost
2> mnesia:create_schema([node()]).
ok
3> init:stop().
ok
4>
PS E:\ERL\Mnesia> ls
目录: E:\ERL\Mnesia
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2020/10/21 11:38 Mnesia.nonode@nohost
PS E:\ERL\Mnesia>

mnesia:create_schema(NodeList)会在NodeList(它必须是一个包含有效Erlang节点的列表)里的所有节点上都初始化一个新的Mnesia数据库。
Mnesia完成初始化并创建了一个名为Mnesia.nonode@nohost的目录结构来保存数据库。


  • 在特定节点创建

PS E:\ERL\Mnesia> erl -sname dylan
Eshell V8.3 (abort with ^G)
(dylan@DESKTOP-FRA3DA2)1> mnesia:create_schema([node()]).
ok
(dylan@DESKTOP-FRA3DA2)2> init:stop().
ok
(dylan@DESKTOP-FRA3DA2)3>
PS E:\ERL\Mnesia> ls
目录: E:\ERL\Mnesia
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2020/10/21 11:46 Mnesia.dylan@DESKTOP-FRA3DA2
d----- 2020/10/21 11:38 Mnesia.nonode@nohost

另外,可以在启动erl时通过参数-mnesia dir Dir指定数据库目录的位置。


  • Mnesia常用方法
    mnesia:start(). 启动mnesia数据库
    mnesia:stop(). 关闭mnesia数据库
    mnesia:info(). 显示mnesia数据库状态

2. 创建表

可以对Mnesia表进行多种方式的配置。首先,表可以位于内存或磁盘里。其次,表可以位于单台机器上,也可以在多台机器之间复制。


  • 内存表
    它们的速度非常快,但是里面的数据是易失的,所以如果机器崩溃或者停止了DBMS,数据就会丢失。
  • 磁盘表
    磁盘表应该不会受到系统崩溃的影响(前提是磁盘没有物理损坏)。当Mnesia事务写入一个表并且这个表是保存在磁盘上时,实际上是事务数据首先被写入了一个磁盘日志。

要创建一个表,则调用mnesia:create_table(Name, ArgS),其中ArgS是一个由{Key,Val}元组构成的列表。如果表创建成功,create_table就会返回{atomic, ok},否则返回{aborted, Reason}。常用参数:


  • Name
    表的名称(一个原子)。按惯例是一个Erlang记录的名称,表里的各行是这个记录的实例。
  • {type, Type}
    指定了表的类型。Type是setordered_setbag中的一个。
  • {disc_copies, NodeList}
    NodeList是一个Erlang节点列表,这些节点将保存表的磁盘副本。使用这个选项时,系统还会在执行这个操作的节点上创建一个表的内存副本。
    可以既在一个节点上保存disc_copies类型的副本表,又在另一个节点上保存该表的不同类型。这种做法能满足以下要求:

  1. 读取操作非常快,并在内存里执行;
  2. 写入操作在持久性存储介质里执行。

  • {ram_copies, NodeList}
    NodeList是一个Erlang节点列表,这些节点将保存表的内存副本。
  • {disc_only_copies, NodeList}
    NodeList是一个Erlang节点列表,这些节点将只保存表的磁盘副本。这些表没有内存副本,访问起来会比较慢。
  • {attributes, AtomList}
    这个列表包含表里各个值的列名。要创建一个包含Erlang记录xxx的表,可以用{attributes, record_info(fields, xxx)}这种语法(也可以显式指定一个记录字段名列表)。

常用的表属性组合:

  • mnesia:create_table(shop, [{attributes, record_info(fields, xxx)}]).
    它会在单个节点上创建一个常驻内存的表。
    如果节点崩溃了,表就会丢失。
    它是所有表里最快的一种。
    内存必须能容纳这个表。

  • mnesia:create_table(shop, [{attributes, record_info(fields, xxx)}, {disc_copies, node()]}).
    它会在单个节点上创建一个常驻内存的表和一个磁盘副本。
    如果节点崩溃了,表就会从磁盘恢复。
    表的读访问很快,但写访问较慢。
    内存最好能容纳这个表。

  • mnesia:create_table(shop, [{attributes, record_info(fields, xxx)}, {disc_only_copies, node()]}).
    它只会在单个节点上创建一个磁盘副本。
    它用于那些因为太大而无法放入内存的表。
    它的访问速度比带有内存副本的方案更慢。

  • mnesia:create_table(shop, [{attributes, record_info(fields, xxx)}, {ram_copies, node(), soneOtherNode()]}).
    它会在两个节点上各创建一个常驻内存的表。
    如果两个节点都崩溃了,表就会丢失。
    内存必须能容纳这个表。
    可以在任何一个节点上访问这个表。

  • mnesia:create_table(shop, [{attributes, record_info(fields, xxx)}, {disc_copies, node(), soneOtherNode()]}).
    它会在多个节点上创建磁盘副本。
    无论哪个节点崩溃,我们都能恢复过来。
    即使所有节点都崩溃了,表也不会丢失。

首先,通过执行test_mnesia:do_only_once()创建一个数据库及数据表:

1> test_mnesia:do_only_once().
stopped
=INFO REPORT==== 30-Oct-2020::15:06:32 ===
application: mnesia
exited: stopped
type: temporary
2> mnesia:info().
===> System info in version "4.14.3", debug level = none <===
opt_disc. Directory "e:/ERL/Mnesia/Mnesia.nonode@nohost" is used.
use fallback at restart = false
running db nodes = []
stopped db nodes = [nonode@nohost]
ok
3> mnesia:start().
ok
4> mnesia:info().
---> Processes holding locks <---
---> Processes waiting for locks <---
---> Participant transactions <---
---> Coordinator transactions <---
---> Uncertain transactions <---
---> Active tables <---
cost : with 0 records occupying 302 words of mem
shop : with 0 records occupying 302 words of mem
schema : with 3 records occupying 645 words of mem
===> System info in version "4.14.3", debug level = none <===
opt_disc. Directory "e:/ERL/Mnesia/Mnesia.nonode@nohost" is used.
use fallback at restart = false
running db nodes = [nonode@nohost]
stopped db nodes = []
master node tables = []
remote = []
ram_copies = [cost,shop]
disc_copies = [schema]
disc_only_copies = []
[{nonode@nohost,disc_copies}] = [schema]
[{nonode@nohost,ram_copies}] = [shop,cost]
2 transactions committed, 0 aborted, 0 restarted, 0 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []
ok

test_mnesia:do_only_once()代码如下:

do_only_once() ->
mnesia:create_schema([node()]),
mnesia:start(),
create_table(),
mnesia:stop().
%% 创建数据表
create_table() ->
mnesia:create_table(shop, [{attributes, record_info(fields, shop)}]),
mnesia:create_table(cost, [{attributes, record_info(fields, cost)}]).

先通过test_mnesia:reset_tables()初始化数据表。

5> test_mnesia:reset_tables().
{atomic,ok}

3. 查询数据


  • 查询所有行:

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

test_mnesia:demo(select_shop)代码如下:

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

[X || X <- mnesia:table(shop)]的意思是“一个由X组成的列表,X提取自shop这个Mnesia表”。X的值是Erlang的shop记录。
qlc:q()会把查询编译成一种用于查询数据库的内部格式。把编译后的查询传递给一个名为do()的函数,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是这个事务函数的值。
需要注意:
qlc:q/1的参数必须是一个字面上的列表推导,不能是通过求值得出的。举个例子,下面的代码与示例里的代码不是等价的。

Val = [X || X <- mnesia:table(shop)],
qlc:q(Val).

  • 查询部分字段:

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

test_mnesia:demo(select_some)代码如下:

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

  • 条件查询:

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

test_mnesia:demo(reorder)代码如下:

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

  • 联表查询:

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

test_mnesia:demo(join)代码如下:

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

4. 添加行

11> test_mnesia:add_shop_item(tomato,100,1.0).
{atomic,ok}
12> test_mnesia:demo(select_shop).
[{shop,tomato,100,1.0},
{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}]
13> test_mnesia:add_shop_item(orange,100,1.0).
{atomic,ok}
14> test_mnesia:demo(select_shop).
[{shop,tomato,100,1.0},
{shop,potato,2456,1.2},
{shop,apple,20,2.3},
{shop,orange,100,1.0},
{shop,pear,200,3.6},
{shop,banana,420,4.5}]

shop表的主键是表内的第一列,也就是shop记录里的item字段。这个表属于“异键”类型。如果新创建的记录和数据表里的某一行具有相同的主键,就会覆盖那一行,否则就会创建一个新行。


5. 删除行

要移除某一行,需要知道该行的对象ID(Object ID,简称OID)。它由表名和主键的值构成。

15> test_mnesia:remove_shop_item(tomato).
{atomic,ok}
16> test_mnesia:demo(select_shop).
[{shop,potato,2456,1.2},
{shop,apple,20,2.3},
{shop,orange,100,1.0},
{shop,pear,200,3.6},
{shop,banana,420,4.5}]

6. Mnesia事务

Mnesia采用一种悲观锁定(pessimistic locking)的策略。每当Mnesia事务管理器访问一个表时,都会根据上下文情况尝试锁定记录甚至整个表。如果它发现这可能导致死锁,就会立即中止事务并撤销之前所做的改动。
如果因为其他进程正在访问数据而导致事务一开始就失败了,系统就会进行短时间的等待,然后再次尝试执行事务。这么做的一种结果就是事务fun里的代码可能会被执行很多次。
出于这个原因,事务fun里的代码不应该做任何带有副作用的事情。举个例子:

F = fun() ->
...
io:format("STH Done"),
...
end,
mnesia:transaction(F).

也许会得到大量输出,因为这个fun可能会被多次重试。
有2点需要注意:


  1. mnesia:write/1mnesia:delete/1的调用只应该出现在由mnesia:transaction/1处理的fun内部。
  2. 永远不要编写代码来显式捕捉Mnesia访问函数(mnesia:write/1mnesia: delete/1等)里的异常错误,因为Mnesia的事务机制本身就依赖这些函数在失败时抛出异常错误。如果捕捉这些异常错误并试图自行处理它们,就会破坏事务机制。

另外,可以通过调用mnesia:abort(Reason)终止一个事务。


7. 表查看器

要查看我们保存在Mnesia里的数据,可以使用observer应用程序里内建的表查看器。用命令observer:start()启动observer


8. 完整代码

%% test_mnesia.erl
-module(test_mnesia).
-compile(export_all).
-include_lib("stdlib/include/qlc.hrl").
-record(shop, {item, quantity, cost}).
-record(cost, {name, price}).
do_only_once() ->
mnesia:create_schema([node()]),
mnesia:start(),
create_table(),
mnesia:stop().
%% 创建数据表
create_table() ->
mnesia:create_table(shop, [{attributes, record_info(fields, shop)}]),
mnesia:create_table(cost, [{attributes, record_info(fields, cost)}]).
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() ->
[%% The shop table
{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},
%% The cost table
{cost, apple, 1.5},
{cost, orange, 2.4},
{cost, pear, 2.2},
{cost, banana, 1.5},
{cost, potato, 0.6}
].
%% 查询
%% SQL equivalent
%% SELECT * FROM shop;
demo(select_shop) ->
do(qlc:q([X || X <- mnesia:table(shop)]));
%% SQL equivalent
%% SELECT item, quantity FROM shop;
demo(select_some) ->
do(qlc:q([{X#shop.item, X#shop.quantity} || X <- mnesia:table(shop)]));

%% SQL equivalent
%% 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
]));
%% 联表
%% SQL equivalent
%% 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
])).
do(Q) ->
F = fun() -> qlc:e(Q) end,
{atomic, Val} = mnesia:transaction(F),
Val.
%% 添加数据
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).

注:本博客为通过《Erlang程序设计 第二版》学习Erlang时所做的笔记。学习更详细的内容,建议直接阅读《Erlang程序设计 第二版》。



推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 解决VS写C#项目导入MySQL数据源报错“You have a usable connection already”问题的正确方法
    本文介绍了在VS写C#项目导入MySQL数据源时出现报错“You have a usable connection already”的问题,并给出了正确的解决方法。详细描述了问题的出现情况和报错信息,并提供了解决该问题的步骤和注意事项。 ... [详细]
  • 本文详细介绍了MySQL表分区的创建、增加和删除方法,包括查看分区数据量和全库数据量的方法。欢迎大家阅读并给予点评。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • WhenIusepythontoapplythepymysqlmoduletoaddafieldtoatableinthemysqldatabase,itdo ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • 本文介绍了如何使用PHP代码将表格导出为UTF8格式的Excel文件。首先,需要连接到数据库并获取表格的列名。然后,设置文件名和文件指针,并将内容写入文件。最后,设置响应头部,将文件作为附件下载。 ... [详细]
  • 本文整理了Java中org.apache.pig.backend.executionengine.ExecException.<init>()方法的一些代码 ... [详细]
author-avatar
Snape吾爱
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有