非常教程

Erlang 20参考手册

mnesia

3.开始 | 3. Getting Started

本节将向Mnesia介绍一个示例数据库。 以下部分引用了此示例,其中修改了示例以说明各种程序结构。 本节通过示例说明了以下强制性程序:

  • 开始Erlang会话。
  • 指定Mnesia要存储数据库的目录。
  • 使用一个属性来初始化新的数据库模式,该属性指定要在哪个或哪些节点上运行该数据库。
  • 开始Mnesia
  • 创建和填充数据库表。

3.1 首次启动Mnesia

本节提供了Mnesia系统启动的简化演示。来自Erlang shell的对话如下:

unix>  erl -mnesia dir '"/tmp/funky"'
Erlang (BEAM) emulator version 4.9

Eshell V4.9  (abort with ^G)
1> 
1> mnesia:create_schema([node()]).
ok
2> mnesia:start().
ok
3> mnesia:create_table(funky, []).
{atomic,ok}
4> mnesia:info().
---> Processes holding locks <--- 
---> Processes waiting for locks <--- 
---> Pending (remote) transactions <--- 
---> Active (local) transactions <---
---> Uncertain transactions <--- 
---> Active tables <--- 
funky          : with 0 records occupying 269 words of mem 
schema         : with 2 records occupying 353 words of mem 
===> System info in version "1.0", debug level = none <===
opt_disc. Directory "/tmp/funky" is used.
use fall-back at restart = false
running db nodes = [nonode@nohost]
stopped db nodes = [] 
remote           = []
ram_copies       = [funky]
disc_copies      = [schema]
disc_only_copies = []
[{nonode@nohost,disc_copies}] = [schema]
[{nonode@nohost,ram_copies}] = [funky]
1 transactions committed, 0 aborted, 0 restarted, 1 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []
ok      

在本例中,执行以下操作:

  • 第1步: Erlang系统从UNIX提示符开始,并带有一个标志-mnesia dir '"/tmp/funky"',表示在哪个目录中存储数据。
  • 步骤2:通过评估在本地节点上初始化新的空模式mnesia:create_schema([node()])。该模式通常包含有关数据库的信息。这在后面详细解释。
  • 第3步:通过评估启动DBMS mnesia:start()
  • 步骤4:funky通过评估表达式来创建第一个表格mnesia:create_table(funky, [])。该表被赋予默认属性。
  • 步骤5: mnesia:info()评估在终端上显示关于数据库状态的信息。

3.2 例子

一个Mnesia数据库被组织为一组表。每个表都填充了实例(Erlang记录)。一个表还有许多属性,例如位置和持久性。

数据库

此示例显示如何创建调用的数据库Company以及下图中显示的关系:

图3.1:公司实体关系图

数据库模型如下:

  • 有三个实体:部门,员工和项目。
  • 这些实体之间有三种关系:
- A department is managed by an employee, hence the `manager` relationship. 
- An employee works at a department, hence the `at_dep` relationship. 
- Each employee works on a number of projects, hence the `in_proj` relationship. 

定义结构和内容

首先将记录定义输入到一个名为的文本文件中company.hrl。该文件为示例数据库定义了以下结构:

-record(employee, {emp_no,
                   name,
                   salary,
                   sex,
                   phone,
                   room_no}).

-record(dept, {id, 
               name}).

-record(project, {name,
                  number}).


-record(manager, {emp,
                  dept}).

-record(at_dep, {emp,
                 dept_id}).

-record(in_proj, {emp,
                  proj_name}).

该结构在数据库中定义了六个表。 在Mnesia中,函数mnesia:create_table(Name,ArgList)创建表。 名称是表名。

注意

Mnesia的当前版本不要求表的名称与记录名称相同,请参阅记录名称与表名称。

例如,员工表使用函数mnesia:create_table(employee,[{attributes,record_info(fields,employee)}])创建。 表名员工与ArgList中指定的记录的名称相匹配。 表达式record_info(fields,RecordName)由Erlang预处理器处理,并计算到包含记录不同字段名称的列表。

程序

以下shell交互启动Mnesia并初始化公司数据库的模式:

% erl -mnesia dir '"/ldisc/scratch/Mnesia.Company"'
 Erlang (BEAM) emulator version 4.9
  
  Eshell V4.9  (abort with ^G)
  1> mnesia:create_schema([node()]).
  ok
  2> mnesia:start().
  ok

以下程序模块创建并填充以前定义的表格:

-include_lib("stdlib/include/qlc.hrl").
-include("company.hrl").

init() ->
    mnesia:create_table(employee,
                        [{attributes, record_info(fields, employee)}]),
    mnesia:create_table(dept,
                        [{attributes, record_info(fields, dept)}]),
    mnesia:create_table(project,
                        [{attributes, record_info(fields, project)}]),
    mnesia:create_table(manager, [{type, bag}, 
                                  {attributes, record_info(fields, manager)}]),
    mnesia:create_table(at_dep,
                         [{attributes, record_info(fields, at_dep)}]),
    mnesia:create_table(in_proj, [{type, bag}, 
                                  {attributes, record_info(fields, in_proj)}]).

程序解释

以下命令和函数用于启动Company数据库:

  • % erl -mnesia dir '"/ldisc/scratch/Mnesia.Company"'。这是启动Erlang系统的UNIX命令行条目。该标志-mnesia dir Dir指定数据库目录的位置。系统响应并等待提示进一步输入1>
  • mnesia:create_schema([node()])。这个函数具有格式mnesia:create_schema(DiscNodeList)并启动一个新的模式。在本例中,创建仅使用一个节点的非分布式系统。模式完整地解释在Define a Schema
  • mnesia:start()。此功能启动Mnesia并在中充分解释Start Mnesia

继续与Erlang shell进行对话产生以下内容:

3> company:init().
{atomic,ok}
4> mnesia:info().
---> Processes holding locks <--- 
---> Processes waiting for locks <--- 
---> Pending (remote) transactions <--- 
---> Active (local) transactions <---
---> Uncertain transactions <--- 
---> Active tables <--- 
in_proj        : with 0 records occuping 269 words of mem 
at_dep         : with 0 records occuping 269 words of mem 
manager        : with 0 records occuping 269 words of mem 
project        : with 0 records occuping 269 words of mem 
dept           : with 0 records occuping 269 words of mem 
employee       : with 0 records occuping 269 words of mem 
schema         : with 7 records occuping 571 words of mem 
===> System info in version "1.0", debug level = none <===
opt_disc. Directory "/ldisc/scratch/Mnesia.Company" is used.
use fall-back at restart = false
running db nodes = [nonode@nohost]
stopped db nodes = [] 
remote           = []
ram_copies       =
    [at_dep,dept,employee,in_proj,manager,project]
disc_copies      = [schema]
disc_only_copies = []
[{nonode@nohost,disc_copies}] = [schema]
[{nonode@nohost,ram_copies}] =
    [employee,dept,project,manager,at_dep,in_proj]
6 transactions committed, 0 aborted, 0 restarted, 6 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []
ok

一组表被创建。该函数mnesia:create_table(Name, ArgList)创建所需的数据库表。可用的选项ArgList在下面解释Create New Tables

该函数company:init/0创建表格。两个表是类型的bag。这是manager关系以及in_proj关系。这被解释为:一名员工可以担任多个部门的经理,一名员工可以参与多个项目。但是,这种at_dep关系是set,因为员工只能在一个部门工作。在这个数据模型中,存在一对一(1 set)和1对多(bag)的关系示例。

mnesia:info()现在表明一个数据库有七个本地表,其中六个是用户定义的表,一个是模式。由于在创建表格时运行了六次成功事务,所以已经发生了六次事务。

要编写一个将职员记录插入数据库的函数,必须插入一条at_dep记录和一组in_proj记录。检查用于完成此操作的以下代码:

insert_emp(Emp, DeptId, ProjNames) ->
    Ename = Emp#employee.name,
    Fun = fun() ->
                  mnesia:write(Emp),
                  AtDep = #at_dep{emp = Ename, dept_id = DeptId},
                  mnesia:write(AtDep),
                  mk_projs(Ename, ProjNames)
          end,
    mnesia:transaction(Fun).


mk_projs(Ename, [ProjName|Tail]) ->
    mnesia:write(#in_proj{emp = Ename, proj_name = ProjName}),
    mk_projs(Ename, Tail);
mk_projs(_, []) -> ok.
  • insert_emp/3参数如下:
-  `Emp` is an employee record. 
-  `DeptId` is the identity of the department where the employee works. 
-  `ProjNames` is a list of the names of the projects where the employee works.

该函数insert_emp/3创建一个功能对象(Fun)。Fun作为单个参数传递给函数mnesia:transaction(Fun)。这意味着Fun作为具有以下属性的交易运行:

  • A Fun成功或失败。
  • 操作相同数据记录的代码可以同时运行,而不会有不同的进程相互干扰。

该功能可以使用如下:

  Emp  = #employee{emp_no= 104732,
                   name = klacke,
                   salary = 7,
                   sex = male,
                   phone = 98108,
                   room_no = {221, 015}},
insert_emp(Emp, 'B/SFR', [Erlang, mnesia, otp]).

注意

有关Funs的信息,请参阅Erlang Reference Manual系统文档中的“Fun表达式”一节。

初始数据库内容

插入名为employee的员工后klacke,数据库具有以下记录:

emp_no

name

salary

sex

phone

room_no

104732

klacke

7

male

98108

{221, 015}

employee记录具有Erlang记录/元组表示{employee, 104732, klacke, 7, male, 98108, {221, 015}}

emp

dept_name

klacke

B/SFR

at_dep记录具有Erlang元组表示形式{at_dep, klacke, 'B/SFR'}

emp

proj_name

klacke

Erlang

klacke

otp

klacke

mnesia

in_proj记录具有Erlang元组表示形式{in_proj, klacke, 'Erlang', klacke, 'otp', klacke, 'mnesia'}

表格和Mnesia记录中的行之间没有区别。这两个概念都是相同的,在本用户指南中可互换使用。

Mnesia表格由Mnesia记录填充。 例如,元组{boss,klacke,bjarne}是一条记录。 这个元组中的第二个元素是关键。 要唯一标识一个表,需要键和表名。 术语对象标识符(OID)有时用于元组两个元组{Tab,Key}。 纪录{boss,klacke,bjarne}的OID是arity两元组{boss,klacke}。 元组的第一个元素是记录的类型,第二个元素是键。 OID可以导致零个,一个或多个记录,具体取决于表类型是设置还是包。

记录{boss, klacke, bjarne}也可以插入。此记录包含对数据库中尚不存在的其他员工的隐式引用。Mnesia不强制执行此操作。

将记录和关系添加到数据库

在向Company数据库添加更多记录之后,结果可以是以下记录:

employees:

{employee, 104465, "Johnson Torbjorn",   1, male,  99184, {242,038}}.
{employee, 107912, "Carlsson Tuula",     2, female,94556, {242,056}}.
{employee, 114872, "Dacker Bjarne",      3, male,  99415, {221,035}}.
{employee, 104531, "Nilsson Hans",       3, male,  99495, {222,026}}.
{employee, 104659, "Tornkvist Torbjorn", 2, male,  99514, {222,022}}.
{employee, 104732, "Wikstrom Claes",     2, male,  99586, {221,015}}.
{employee, 117716, "Fedoriw Anna",       1, female,99143, {221,031}}.
{employee, 115018, "Mattsson Hakan",     3, male,  99251, {203,348}}.

dept:

{dept, 'B/SF',  "Open Telecom Platform"}.
{dept, 'B/SFP', "OTP - Product Development"}.
{dept, 'B/SFR', "Computer Science Laboratory"}.

projects:

%% projects
{project, erlang, 1}.
{project, otp, 2}.
{project, beam, 3}.
{project, mnesia, 5}.
{project, wolf, 6}.
{project, documentation, 7}.
{project, www, 8}.

这三张表,员工,部门和项目都由真实记录组成。 以下数据库内容存储在表中并建立在关系上。 这些表是manager,at_dep和in_proj。

manager:

{manager, 104465, 'B/SF'}.
{manager, 104465, 'B/SFP'}.
{manager, 114872, 'B/SFR'}.

at_dep:

{at_dep, 104465, 'B/SF'}.
{at_dep, 107912, 'B/SF'}.
{at_dep, 114872, 'B/SFR'}.
{at_dep, 104531, 'B/SFR'}.
{at_dep, 104659, 'B/SFR'}.
{at_dep, 104732, 'B/SFR'}.
{at_dep, 117716, 'B/SFP'}.
{at_dep, 115018, 'B/SFP'}.

in_proj:

{in_proj, 104465, otp}.
{in_proj, 107912, otp}.
{in_proj, 114872, otp}.
{in_proj, 104531, otp}.
{in_proj, 104531, mnesia}.
{in_proj, 104545, wolf}.
{in_proj, 104659, otp}.
{in_proj, 104659, wolf}.
{in_proj, 104732, otp}.
{in_proj, 104732, mnesia}.
{in_proj, 104732, erlang}.
{in_proj, 117716, otp}.
{in_proj, 117716, documentation}.
{in_proj, 115018, otp}.
{in_proj, 115018, mnesia}.

房间号码是员工记录的一个属性。 这是一个由元组组成的结构化属性。 元组的第一个元素标识走廊,第二个元素标识该走廊中的房间。 另一种方法是将其表示为记录 - 记录(房间,{corr,no})。 而不是匿名的元组表示法。

Company数据库现在已经初始化并包含数据。

编写查询

从DBMS中检索数据通常需要使用函数mnesia:read/3mnesia:read/1。以下函数提高薪水:

raise(Eno, Raise) ->
    F = fun() ->
                [E] = mnesia:read(employee, Eno, write),
                Salary = E#employee.salary + Raise,
                New = E#employee{salary = Salary},
                mnesia:write(New)
        end,
    mnesia:transaction(F).

由于希望mnesia:write/1在工资增加后使用该函数来更新记录,所以read当读取来自表的记录时,获取写锁(第三个参数)。

直接读取表中的值并不总是可能的。可能需要搜索一个或多个表以获取所需的数据,这可通过编写数据库查询来完成。查询总是比直接查找更昂贵的操作mnesia:read。因此,请避免在性能严重的代码中进行查询。

有两种方法可用于编写数据库查询:

  • Mnesia 功能
  • QLC
使用Mnesia函数

以下函数提取存储在数据库中的女雇员的姓名:

mnesia:select(employee, [{#employee{sex = female, name = '$1', _ = '_'},[], ['$1']}]).

select必须始终在一个活动中运行,例如交易。下面的函数可以构造来从shell调用:

all_females() ->
    F = fun() ->
		Female = #employee{sex = female, name = '$1', _ = '_'},
		mnesia:select(employee, [{Female, [], ['$1']}])
        end,
    mnesia:transaction(F).

select表达式匹配表employee中的所有条目,其字段性别设置为female。

这个函数可以从shell调用如下:

(klacke@gin)1> company:all_females().
{atomic,  ["Carlsson Tuula", "Fedoriw Anna"]}

有关描述select和语法,请参阅Pattern Matching

使用QLC

本节仅包含简单的介绍性示例。 有关QLC查询语言的完整说明,请参见STDLIB中的qlc手册页。

使用QLC可能比Mnesia直接使用函数更昂贵,但提供了一个很好的语法。

以下函数从数据库中提取女性员工列表:

Q = qlc:q([E#employee.name || E <- mnesia:table(employee),
                      E#employee.sex == female]),
qlc:e(Q),

Mnesia从QLC列表理解访问表格必须始终在一个事务中完成。考虑以下功能:

females() ->
    F = fun() ->
		Q = qlc:q([E#employee.name || E <- mnesia:table(employee),
					      E#employee.sex == female]),
		qlc:e(Q)
	end,
    mnesia:transaction(F).

这个函数可以从shell调用如下:

(klacke@gin)1> company:females().
{atomic, ["Carlsson Tuula", "Fedoriw Anna"]}

在传统的关系数据库术语中,这个操作称为选择,然后是投影。

之前的列表理解表达式包含许多语法元素:

  • 第一个[括号被认为是“建立列表”。
  • ||“这样”,箭头<-读作“取自”。

因此,前面的列表理解演示列表的形成E#employee.name,使得E从员工的表中获得,并且属性sex的每个记录的是等于原子female

整个列表理解必须赋予函数qlc:q/1

带有低级Mnesia函数的列表推导可以在同一个事务中进行组合。要提高所有女性员工的工资,请执行以下操作:

raise_females(Amount) ->
    F = fun() ->
                Q = qlc:q([E || E <- mnesia:table(employee),
                                E#employee.sex == female]),
		Fs = qlc:e(Q),
                over_write(Fs, Amount)
        end,
    mnesia:transaction(F).

over_write([E|Tail], Amount) ->
    Salary = E#employee.salary + Amount,
    New = E#employee{salary = Salary},
    mnesia:write(New),
    1 + over_write(Tail, Amount);
over_write([], _) ->
    0.

该函数raise_females/1返回元组{atomic, Number}Number收到加薪的女性雇员的数量在哪里。如果发生错误,{aborted, Reason}则返回该值,并Mnesia保证不会为任何员工提高工资。

例:

33>company:raise_females(33).
{atomic,2}
Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1