【Mnesia文档】3、快速开始

Erlang/OTP Version: 24
Mnesia Documentation Version: 4.20
导航:Database - Mnesia - Getting Started
翻译内容来自官方文档

快速开始

本节通过一个示例数据库介绍Mnesia。本示例在以下章节中引用,其中对示例进行了修改,以说明各种程序结构。本节通过示例说明了以下强制性程序:

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

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:从UNIX提示符启动Erlang系统,并使用一个标志 -mnesia dir '"/tmp/funky"',指示在哪个目录中存储数据。
  • 步骤2:通过执行mnesia:create_schema([node()]).,在本地节点上初始化一个新的空Schema。架构包含有关数据库的一般信息。这将在后面详细解释。
  • 步骤3:通过执行mnesia:start().启动DBMS。
  • 步骤4:通过执行表达式mnesia:create_table(funky, []).,创建第一个表,称为funky。该表具有默认属性。
  • 步骤5:运行mnesia:info().以在终端上显示有关数据库状态的信息。

2、例子

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

数据库

此示例显示如何创建名为Company的数据库以及下图所示的关系:

Figure 3.1:Company 关系实体示意图

数据库模型如下所示:

  • 有三个实体:部门、员工和项目。
  • 这些实体之间有三种关系:
    1. 一个部门由一名员工管理,因此形成了manager关系。
    2. 员工在一个部门工作,因此存在at_dep关系。
    3. 每个员工都从事多个项目,因此存在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数据库初始化Schema:

     % 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()]).启动一个新的Schema。在本例中,创建一个仅用一个节点的非分布式系统。Schema在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的有效选项。

company:init/0函数创建了表。有两个表的类型bag,分别是manager关系和in_proj关系,解释:一名员工可以管理多个部门,一名员工可以参与多个项目。但是,由于一名员工只能在一个部门工作,因此at_dep关系是set类型。在此数据模型中,有1对1(set)和1对多(bag)的关系示例。

mnesia:info()指明现在的数据库有七个本地表,其中六个是用户定义的表,一个是Schema。已经提交了六个事务,因为在创建表时运行了六个成功的事务。六个事务被提交,在创建表时,运行了六个事务。

编写插入员工记录到数据库的函数,必须要插入一条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.
  • inster_emp/3参数如下:

    1. Emp是员工记录
    2. DeptId是员工工作所在部门的标识
    3. 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 Reference Manual小节中的Fun Expressions

初始化数据库的内容

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

emp_no name salary sex phone room_no
104732 klacke 7 male 98108

Table 3.1: employee Database Record

employee记录是Erlang的record/tuple,表示为{employee, 104732, klacke, 7, male, 98108, {211, 015}}.

emp dept_name
klacke B/SFR

Table 3.2: at_dep Database Record

at_dep记录是Erlang的tuple,表示为{at_dep, klacke, 'B/SFR'}.

emp proj_name
klacke Erlang
klacke otp
klacke mnesia

Table 3.3: in_proj Database Record

in_proj记录是Erlang的tuple,表示{in_proj, klacke, 'Erlang', klacke, 'otp', klacke, 'mnesia'}.

表中的行和Mnesia的记录没有差异,两个概念是相同的,在用户指南中可以互换使用。

Mnesia表由Mnesia记录填充。例如,元组{boss, klacke, bjarne}是一个记录,第二个原始是这个元组的键。要标识一个唯一表,就需要键和表名。有时使用对象标识符(OID)二元组{Tab, Key}。记录{boss, klacke, bjarne}的二元组OID为{boss, klacke}。第一个元素为记录的类型(译:元组名称),第二个元素是键。一个OID可以指引零个、一个、或多个记录,这取决于表类型是setbag

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

employeesdept、和projects三个表,由真实记录组成。以下数据库内容储存在表中,并基于关系构建。这些表格分别为managerat_depin_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_prot:

    {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/3mnesia: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更新记录,因此在读取表中的记录时,将获取写入锁(要读取的第三个参数)。

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

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

  • Mnesia函数
  • QLC
使用Mnesia函数

下面的函数,提取存储在数据库中女员工的名称:

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

select必须始终在活动(如事务)中运行。可以构造以下函数以从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),

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

在传统的关系数据库术语中,此操作称为选择,其次是投影(projection)。

前面的列表推导表达式包含许多语法元素:

  • 第一个[括号,表示为“build the list”
  • ||表示为“such that”,<- 表示“taken from”

因此,前面的列表推导演示了,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([], _) ->

raise_females/1函数返回{atomic, Number}元组,Number是获得加薪的女员工。如果发生错误,返回{aborted, Reason}Mnesia保证不会为任何员工加薪。

例子:

  33>company:raise_females(33).      {atomic,2}
posted @ 2021-10-02 20:51  AmazeBug  阅读(113)  评论(0)    收藏  举报