查看源代码 入门
本节通过一个示例数据库介绍 Mnesia
。该示例在后续章节中被引用,其中修改示例以说明各种程序结构。本节通过示例说明以下强制性过程
- 启动 Erlang 会话。
- 指定
Mnesia
目录,数据库将存储在该目录中。 - 初始化一个新的数据库模式,该模式具有一个属性,用于指定数据库将在哪个或哪些节点上运行。
- 启动
Mnesia
。 - 创建和填充数据库表。
首次启动 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: 从 UNIX 提示符启动 Erlang 系统,并使用标志
-mnesia dir '"/tmp/funky"'
,该标志指示在哪个目录中存储数据。 - 步骤 2: 通过评估
mnesia:create_schema([node()])
,在本地节点上初始化一个新的空模式。该模式包含有关数据库的常规信息。稍后将详细说明。 - 步骤 3: 通过评估
mnesia:start()
启动 DBMS。 - 步骤 4: 通过评估表达式
mnesia:create_table(funky, [])
,创建一个名为funky
的第一个表。该表被赋予默认属性。 - 步骤 5: 评估
mnesia:info()
,以在终端上显示有关数据库状态的信息。
示例
Mnesia
数据库组织为一组表。每个表都填充了实例(Erlang 记录)。一个表还具有多个属性,例如位置和持久性。
数据库
此示例演示如何创建名为 Company
的数据库以及以下图表所示的关系
---
title: Company Entity-Relation Diagram
---
erDiagram
Dept {
atom id
string name
}
Employee {
int emp_no
string name
int salary
atom sex
int phone
tuple room_no
}
Project {
atom name
int number
}
Dept ||--|| Employee: Manager
Employee }|--|| Dept: At_dep
Employee }|--|{ Project: in_proj
数据库模型如下
- 有三个实体:部门、员工和项目。
- 这些实体之间存在三种关系
- 一个部门由一名员工管理,因此存在
manager
关系。 - 一名员工在某个部门工作,因此存在
at_dep
关系。 - 每个员工都参与多个项目,因此存在
in_proj
关系。
- 一个部门由一名员工管理,因此存在
定义结构和内容
首先,将记录定义输入到名为 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)
创建表。Name
是表名。
注意
当前版本的
Mnesia
不要求表名与记录名相同,请参阅 记录名与表名。
例如,使用函数 mnesia:create_table(employee, [{attributes, record_info(fields, employee)}])
创建员工表。表名 employee
与在 ArgList
中指定的记录名称匹配。表达式 record_info(fields, RecordName)
由 Erlang 预处理器处理,并计算为包含记录的不同字段名称的列表。
程序
以下 shell 交互启动 Mnesia
并初始化 Company
数据库的模式
% 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"'
。这是一个 UNIX 命令行条目,用于启动 Erlang 系统。标志-mnesia dir Dir
指定数据库目录的位置。系统响应并等待进一步输入,提示符为1>
。mnesia:create_schema([node()])
。此函数的格式为mnesia:create_schema(DiscNodeList)
,并启动一个新的模式。在此示例中,创建了一个仅使用一个节点的非分布式系统。在 定义模式 中完整地解释了模式。mnesia:start()
。此函数启动Mnesia
,并在 启动 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
可用的选项在 创建新表 中进行了解释。
函数 company:init/0
创建表。两个表的类型为 bag
。这是 manager
关系以及 in_proj
关系。这被解释为:一个员工可以管理多个部门,一个员工可以参与多个项目。但是,at_dep
关系是 set
,因为一个员工只能在一个部门工作。在此数据模型中,存在 1 对 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
是员工记录。DeptId
是员工工作的部门的标识。ProjNames
是员工参与的项目名称列表。
函数 insert_emp/3
创建一个函数对象 (Fun)。Fun
作为单个参数传递给函数 mnesia:transaction(Fun)
。这意味着 Fun
作为事务运行,具有以下属性
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 参考手册”中的“Fun 表达式”。。
初始数据库内容
在插入名为 klacke
的员工后,数据库具有以下记录
emp_no | name | salary | sex | phone | room_no |
---|---|---|---|---|---|
104732 | klacke | 7 | male | 98108 | {221, 015} |
表:employee 数据库记录
此 employee
记录具有 Erlang 记录/元组表示 {employee, 104732, klacke, 7, male, 98108, {221, 015}}
。
emp | dept_name |
---|---|
klacke | B/SFR |
表:at_dep 数据库记录
此 at_dep
记录具有 Erlang 元组表示 {at_dep, klacke, 'B/SFR'}
。
emp | proj_name |
---|---|
klacke | Erlang |
klacke | otp |
klacke | mnesia |
表:in_proj 数据库记录
此 in_proj
记录具有 Erlang 元组表示 {in_proj, klacke, 'Erlang', klacke, 'otp', klacke, 'mnesia'}
。
表中的行和 Mnesia
记录之间没有区别。这两个概念是相同的,并且在本用户指南中互换使用。
Mnesia
表由 Mnesia
记录填充。例如,元组 {boss, klacke, bjarne}
是一个记录。此元组中的第二个元素是键。要唯一地标识一个表,需要键和表名。术语对象标识符 (OID) 有时用于二元元组 {Tab, Key}
。记录 {boss, klacke, bjarne}
的 OID 是二元元组 {boss, klacke}
。元组的第一个元素是记录的类型,第二个元素是键。OID 可以指向零个、一个或多个记录,具体取决于表类型是 set
还是 bag
。
还可以插入记录 {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}.
这三个表 employees
、dept
和 projects
由实际记录组成。以下数据库内容存储在表中,并建立在关系之上。这些表是 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}.
房间号是员工记录的一个属性。这是一个结构化属性,由一个元组组成。元组的第一个元素标识走廊,第二个元素标识该走廊中的房间。另一种方法是将此表示为一个记录 -record(room, {corr, no}).
,而不是匿名元组表示。
现在 Company
数据库已初始化并包含数据。
编写查询
通常使用函数 mnesia:read/3
或 mnesia:read/1
从 DBMS 中检索数据。以下函数用于提高工资
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/1
进行的直接查找操作更昂贵。因此,在对性能要求严格的代码中应避免使用查询。
有两种方法可用于编写数据库查询
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 表中所有字段 sex
设置为 female
的条目。
可以从 shell 中调用此函数,如下所示
(klacke@gin)1> company:all_females().
{atomic, ["Carlsson Tuula", "Fedoriw Anna"]}
有关 select
及其语法的描述,请参阅 模式匹配。
使用 QLC
本节仅包含简单的入门示例。有关 QLC 查询语言的完整描述,请参阅 STDLIB
中的 qlc
手册页。
使用 QLC 可能比直接使用 Mnesia
函数更昂贵,但提供了更友好的语法。
以下函数从数据库中提取女性员工的列表
Q = qlc:q([E#employee.name || E <- mnesia:table(employee),
E#employee.sex == female]),
qlc:e(Q),
从 QLC 列表推导式访问 Mnesia
表必须始终在事务中完成。考虑以下函数
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
取自 employee 表,并且每个记录的属性 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}