Erlang 20

4.构建Mnesia数据库 | 4. Build a Mnesia Database

4 建立一个Mnesia数据库

本节介绍设计Mnesia数据库时的基本步骤以及为编程人员提供不同解决方案的编程结构。包括以下主题:

  • 定义模式

  • 数据模型

  • 启动Mnesia

  • 创建表

4.1定义架构

Mnesia模式中描述了系统的配置。模式是一个特殊的表格,其中包含表格名称和每个表格的存储类型(即表格是存储在RAM中,光盘上还是两者以及它的位置)等信息。

与数据表不同,只能使用本节中描述的模式相关函数来访问和修改架构表中的信息。

Mnesia具有定义数据库架构的各种功能。可以移动或删除表,并且可以重新配置表布局。

这些功能的一个重要方面是系统可以在重新配置时访问表格。例如,可以移动一个表并同时对同一个表执行写入操作。此功能对于需要连续服务的应用程序至关重要。

本节描述用于模式管理的可用函数,所有这些函数都返回以下任何一个元组:

  • {atomic, ok}如果成功

  • {aborted, Reason}如果不成功

模式函数

模式函数如下:

  • mnesia:create_schema(NodeList)初始化一个新的空模式。 这是Mnesia启动之前的强制性要求。 Mnesia是真正的分布式DBMS,架构是在Mnesia系统中的所有节点上复制的系统表。 如果架构已经存在于NodeList中的任何节点上,则此功能失败。 该函数需要在参数NodeList中包含的所有db_nodes上停止Mnesia。 应用程序仅调用一次该函数,因为它通常是初始化新数据库的一次性活动。

  • mnesia:delete_schema(DiscNodeList)擦除DiscNodeList中节点上的所有旧模式。 它还会将所有旧表与所有数据一起移除。 该功能要求Mnesia在所有db_nodes上停止。

  • mnesia:delete_table(Tab)永久删除表的所有副本Tab

  • mnesia:clear_table(Tab)永久删除表格中的所有条目Tab

  • mnesia:move_table_copy(Tab,From,To)将表格Tab的副本从节点From移动到节点To。 表存储类型{type}被保留,所以如果一个RAM表从一个节点移动到另一个节点,它仍然是新节点上的RAM表。 其他事务在移动时仍然可以对表执行读写操作。

  • mnesia:add_table_copy(Tab,Node,Type)在节点Node上创建表Tab的副本。 参数类型必须是原子ram_copies,disc_copies或disc_only_copies中的任一个。 如果您将系统表模式的副本添加到节点,您也希望Mnesia模式也驻留在那里。 这个动作扩展了包含这个特定Mnesia系统的节点集。

  • mnesia:del_table_copy(Tab, Node)删除Tab节点处的表的副本Node。当表的最后一个副本被删除时,该表被删除。

  • mnesia:transform_table(Tab, Fun, NewAttributeList, NewRecordName)更改表格中所有记录的格式Tab。它将参数应用于Fun表中的所有记录。Fun必须是一个记录旧类型的函数,并返回新类型的记录。表格密钥不得更改。

例子:

-record(old, {key, val}).

参数Fun也可以是原子ignore,它表示只有关于表的元数据被更新。使用ignore不推荐的(因为它创建元数据与实际数据之间的不一致),但作为用户的可能性做一个自己的(离线)它包含变换。

  • change_table_copy_type(Tab,Node,ToType)更改表的存储类型。例如,在指定为节点的节点上,将RAM表更改为disc_table。数据模型Mnesia使用的数据模型是扩展关系数据模型。数据被组织为一组表格,并且不同数据记录之间的关系可以建模为描述关系的更多表格。每个表都包含Erlang记录的实例。记录表示为Erlang元组。每个对象标识符(OID)由一个表名和一个键组成。例如,如果员工记录由元组{employee,104732,klacke,7,male,98108,{221,015}}表示,则此记录具有OID,即元组{employee,104732}。因此,每个表都由记录组成,其中第一个元素是记录名称,第二个元素是一个键,标识该表中的特定记录。表名和密钥的组合是一个arity两元组{Tab,Key},称为OID。有关记录名称与表名称之间关系的更多信息,请参阅记录名称与表名称。使Mnesia数据模型成为扩展关系模型的原因是能够在属性字段中存储任意的Erlang术语。例如,一个属性值可以是OID的整个树,导致其他表中的其他项。这种类型的记录很难在传统的关系数据库管理系统中建模。4.3启动Mnesia在启动Mnesia之前,必须完成以下工作:

  • 必须在所有参与节点上初始化空架构。

  • Erlang系统必须启动。

  • 必须用函数定义和实现具有磁盘数据库架构的节点。mnesia:create_schema(NodeList)...

在运行具有两个或多个参与节点的分布式系统时,mnesia:start()必须在每个参与节点上执行该功能。这通常是嵌入式环境中启动脚本的一部分。在测试环境或交互式环境中,mnesia:start()也可以使用Erlang shell或其他程序。

初始化架构并启动Mnesia

让我们使用Getting Started中描述的示例数据库Company来说明如何在两个单独的节点上运行数据库,称为@ gin和b @ skeppet。 在启动Mnesia之前,每个节点都必须有Mnesia目录和初始化模式。 有两种方法可以指定要使用的Mnesia目录:

  • Mnesia通过在启动Erlang shell或应用程序脚本时提供应用程序参数来指定目录。以前,以下示例用于为Company数据库创建目录:%erl -mnesia dir'“/ldisc/scratch/Mnesia.Company''

  • 如果没有输入命令行标志,则该Mnesia目录将成为启动Erlang shell的节点上的当前工作目录。

启动Company数据库并在两个指定的节点上运行,输入以下命令:

  • On the node a@gin: gin %erl -sname a -mnesia dir '"/ldisc/scratch/Mnesia.company"'

  • 在节点上b@skeppet*

skeppet %erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'

  • 在两个节点中的一个节点上: (a@gin)1>mnesia:create_schema(a@gin, b@skeppet).

  • 功能mnesia:start()在两个节点上调用。

  • 若要初始化数据库,请在两个节点之一上执行以下代码:

dist_init() -> mnesia:create_table(employee, [{ram_copies, a@gin, b@skeppet}, {attributes, record_info(fields, employee)}]), mnesia:create_table(dept, [{ram_copies, a@gin, b@skeppet}, {attributes, record_info(fields, dept)}]), mnesia:create_table(project, [{ram_copies, a@gin, b@skeppet}, {attributes, record_info(fields, project)}]), mnesia:create_table(manager, [{type, bag}, {ram_copies, a@gin, b@skeppet}, {attributes, record_info(fields, manager)}]), mnesia:create_table(at_dep, [{ram_copies, a@gin, b@skeppet}, {attributes, record_info(fields, at_dep)}]), mnesia:create_table(in_proj, [{type, bag}, {ram_copies, a@gin, b@skeppet}, {attributes, record_info(fields, in_proj)}]).

如图所示,这两个目录驻留在不同的节点上,因为/ldisc/scratch(“本地”磁盘)存在于两个不同的节点上。

通过执行这些命令,两个Erlang节点被配置为运行Company数据库,并因此初始化数据库。设置时只需要一次。系统下一次启动时,mnesia:start()将在两个节点上调用,以便从光盘初始化系统。

在一个Mnesia节点系统中,每个节点都知道所有表的当前位置。在这个例子中,数据在两个节点上都被复制,而操纵表中数据的函数可以在两个节点中的任何一个上执行。Mnesia无论数据位于何处,操作数据的代码的行为都是相同的。

函数mnesia:stop()在执行该函数的节点上停止Mnesia。 mnesia:start / 0和mnesia:stop / 0函数在“本地”Mnesia系统上工作。 没有功能启动或停止一组节点。

启动程序

启动Mnesia通过调用以下函数:

mnesia:start().

此函数在本地启动DBMS。

配置的选择改变了表的位置和加载顺序。备选办法如下:

  • 只存储在本地的表将从本地初始化。Mnesia目录。

  • 本地和其他地方的复制表要么是从磁盘启动的,要么是通过从另一个节点复制整个表来启动的,这取决于不同副本中的哪个是最近的副本。Mnesia确定哪些表是最近的表。

  • 驻留在远程节点上的表一旦加载,其他节点就可以使用。

表初始化是异步的。函数调用mnesia:start()返回原子ok,然后开始初始化不同的表。根据数据库的大小,这可能需要一些时间,并且应用程序员必须等待应用程序需要使用之前所需的表。这是通过使用函数来实现的,该函数mnesia:wait_for_tables(TabList, Timeout)挂起调用者,直到所有指定的表TabList都被正确启动。

如果启动了一个节点上的复制表,但Mnesia推断出另一个(远程)复制品比本地节点上存在的复制品更新,并且初始化过程不继续,则会出现问题。 在这种情况下,对mnesia:wait_for_tables / 2的调用暂停调用方,直到远程节点已从本地磁盘初始化该表,并且该节点已将该表通过网络复制到本地节点。

但是,此过程可能非常耗时,快捷功能mnesia:force_load_table(Tab)以更快的速度加载光盘中的所有表。无论网络状况如何,该功能都会强制从光盘加载表。

因此,可以假设,如果一个应用程序要使用的表ab,应用程序必须执行类似于以下才可以使用的表一些行动:

case mnesia:wait_for_tables([a, b], 20000) of {timeout, RemainingTabs} -> panic(RemainingTabs ok -> synced end.

警告

当从本地光盘强制加载表时,在本地节点关闭并且远程副本处于活动状态时在复制表上执行的所有操作都将丢失。这可能会导致数据库变得不一致。

如果启动过程失败,函数mnesia:start()返回隐含的元组{error,{shutdown,{mnesia_sup,start_link,[normal,[]]}}}。 要获得有关启动失败的更多信息,请使用命令行参数-boot start_sasl作为erl脚本的参数。

4.4创建表

功能mnesia:create_table(Name, ArgList)创建表。在执行此函数时,它返回下列响应之一:

  • {atomic, ok}如果函数成功执行

  • {aborted, Reason}如果函数失败

函数参数如下:

  • Name是表的名称。构成表的通常是与记录名称相同的名称。有关详细信息,请参阅record_name...

  • ArgList{Key,Value}元组。下列参数是有效的:

- `{type, Type}`, where `Type` must be either of the atoms `set`, `ordered_set`, or `bag`. Default is `set`. Notice that currently ordered_set is not supported for disc_only_copies tables. A table of type set or ordered_set has either zero or one record per key, whereas a table of type bag can have an arbitrary number of records per key. The key for each record is always the first attribute of the record. The following example illustrates the difference between type set and bag: f() -> F = fun() -> mnesia:write{foo, 1, 2}), mnesia:write{foo, 1, 3}), mnesia:read{foo, 1}) end, mnesia:transaction(F). This transaction returns the list [{foo,1,3}] if table foo is of type set. However, the list [{foo,1,2}, {foo,1,3}] is returned if the table is of type bag. Mnesia tables can never contain duplicates of the same record in the same table. Duplicate records have attributes with the same contents and key.- `{disc_copies, NodeList}`, where `NodeList` is a list of the nodes where this table is to reside on disc.

将操作disc_copies写入到写入数据的表副本到光盘副本和表的RAM副本。

有可能有一个复制的类型表。disc_copies在一个节点上,存储在另一个节点上的同一表作为不同类型存储。默认值是[].如果需要具备下列业务特点,这种安排是可取的:

- Read operations must be fast and performed in RAM. - All write operations must be written to persistent storage.

disc_copies表副本上的写操作分两步执行。首先将写入操作附加到日志文件,然后在RAM中执行实际操作。

- `{ram_copies, NodeList}`, where `NodeList` is a list of the nodes where this table is stored in RAM. Default is `[node()]`. If the default value is used to create a table, it is located on the local node only.类型表副本ram_copies可以用以下功能转储到磁盘上mnesia:dump_tables(TabList)...- `{disc_only_copies, NodeList}`. These table replicas are stored on disc only and are therefore slower to access. However, a disc-only replica consumes less memory than a table replica of the other two storage types. - `{index, AttributeNameList}`, where `AttributeNameList` is a list of atoms specifying the names of the attributes `Mnesia` is to build and maintain. An index table exists for every element in the list. The first field of a `Mnesia` record is the key and thus need no extra index.

记录的第一个字段是元组的第二个元素,它是记录的表示。

- `{snmp, SnmpStruct}`. `SnmpStruct` is described in the `SNMP` User's Guide. Basically, if this attribute is present in `ArgList` of [`mnesia:create_table/2`](mnesia#create_table-2), the table is immediately accessible the SNMP.使用SNMP来操作和控制系统的应用程序的设计非常容易。Mnesia提供组成SNMP控件应用程序的逻辑表与构成Mnesia桌子。默认值是[]...- `{local_content, true}`. When an application needs a table whose contents is to be locally unique on each node, `local_content` tables can be used. The name of the table is known to all `Mnesia` nodes, but its contents is unique for each node. Access to this type of table must be done locally. - `{attributes, AtomList}` is a list of the attribute names for the records that are supposed to populate the table. Default is the list `[key, val]`. The table must at least have one extra attribute besides the key. When accessing single attributes in a record, it is not recommended to hard code the attribute names as atoms. Use the construct `record_info(fields, record_name)` instead.

表达record_info(fields, record_name)由Erlang预处理器处理,并返回记录字段名的列表。有记录定义-record(foo, {x,y,z}).,表达record_info(fields,foo)扩展到列表中。[x,y,z]因此,您可以提供属性名或使用record_info/2符号。

建议使用record_info/2符号,因为它变得更容易维护程序和程序变得更加健壮的未来记录的变化。

- `{record_name, Atom}` specifies the common name of all records stored in the table. All records stored in the table must have this name as their first element. `record_name` defaults to the name of the table. For more information, see [`Record Names versus Table Names`](mnesia_chap4#recordnames_tablenames).

例如,考虑以下记录定义:

-record(funky, {x, y}).

以下调用将创建一个在两个节点上复制的表格,在属性上具有额外的索引y并且是bag类型

mnesia:create_table(funky, [{disc_copies, [N1, N2]}, {index, [y]}, {type, bag}, {attributes, record_info(fields, funky)}]).

对以下默认值的调用将返回具有本地节点上RAM副本的表,而没有额外的索引,并且属性默认为列表。[key,val]...

mnesia:create_table(stuff, [])