Mnesia用户手册:三,构建Mnesia数据库
本章详细介绍了设计Mnesia数据库和编程结构的基本步骤:
1)定义schema
2)数据模型
3)启动Mnesia
4)创建新表
1,定义schema
Mnesia系统的配置在schema里描述
schema是一个特殊的表,它包含了表名、每个表的存储类型(表应该存储为RAM、硬盘或两者)以及表的位置等信息
不像数据表,schema表里包含的信息只能通过schema相关的方法来访问和修改
Mnesia提供多种方法来定义数据库schema,可以移动表、删除表或者重新配置表布局
这些方法的一个重要特性是当表在重配置的过程中可以被访问
例如,可以在移动一个表的同时执行写操作
该特性对需要连续服务的应用非常好
下面的方法是schema管理所要用到的,它们都返回一个tuple {atomic, ok}或{aborted, Reason}
1)mnesia:create_schema(NodeList)
该方法用来初始化一个新的空schema,在Mnesia启动之前这是一个强制必要的步骤
Mnesia是一个完全分布的DBMS,而schema是一个系统表,它备份到Mnesia系统的所有节点上
如果NodeList中某一个节点已经有schema,则该方法会失败
该方法需要NodeList中所有节点上的Mnesia都停止之后才执行
应用程序只需调用该方法一次,因为通常只需要初始化数据库schema一次
2)mnesia:delete_schema(DiscNodeList)
该方法在DiscNodeList节点上擦除旧的schema,它也删除所有的旧table和数据
该方法需要所有节点上的Mnesia都停止后才执行
3)mnesia:delete_table(Tab)
该方法永久删除Tab表的备份
4)mnesia:clear_table(Tab)
该方法永久删除Tab表的记录
5)mnesia:move_table_copy(Tab, From, To)
该方法将Tab表的copy从From节点移动到To节点
表的存储类型{type}保留,这样当移动一个RAM表到另一个节点时,在新节点上也维持一个RAM表
在表移动的过程中仍然可以有事务执行读和写操作
6)mnesia:add_table_copy(Tab, Node, Type)
该方法在Node节点上创建Tab表的备份,Type参数为ram_copies/disc_copies/disc_only_copies
7)mnesia:del_table_copy(Tab, Node)
该方法在Node节点上删除Tab表的备份,当最后一个备份被删除后,表也被删除
8)mnesia:transform_table(Tab, Fun, NewAttributeList, NewRecordName)
该方法对Tab表的所有数据更改format,它对表里所有记录调用Fun
Fun应该是一个方法,参数为记录的旧类型,返回记录的新类型,表的key不能更改
- -record(old, {key, val}).
- -record(new, {key, val, extra}).
- Transformer =
- fun(X) when record(X, old) ->
- #new{key = X#old.key,
- val = X#old.val,
- extra = 42}
- end,
- {atomic, ok} = mnesia:transform_table(foo, Transformer,
- record_info(fields, new),
- new),
-record(old, {key, val}). -record(new, {key, val, extra}). Transformer = fun(X) when record(X, old) -> #new{key = X#old.key, val = X#old.val, extra = 42} end, {atomic, ok} = mnesia:transform_table(foo, Transformer, record_info(fields, new), new),
Fun参数也可以为ignore,它表示只更新表的meta data,不推荐使用
9)mnesia:change_table_copy_type(Tab, Node, ToType)
该方法更改表的存储类型,如从RAM改为disc_table
2,数据模型
Mnesia的数据库数据由record组成,record由tuple表示
record的第一个元素是record名,第二个元素是表的key,前两个元素组成的tuple称为oid
Mnesia数据模型是对关系模型的扩展,因为该模型可以在域属性里存储任意的Erlang term
例如,可以在一个属性里存储指向其他表里的oid树,而这种类型的记录在传统的关系型DBMS里很难建模
3,启动Mnesia
在启动Mnesia之前我们必须在相应的节点上初始化一个空的schema
1)Erlang系统必须启动
2)必须使用create_schema(NodeList)来定义数据库schema
当运行一个分布式系统时,可能有多个节点参与,则mnesia:start()方法必须在相应的节点上运行
典型的,在一个嵌入式环境里mnesia:start()应该为启动脚本的一部分
在一个测试环境或解释型环境里,mnesia:start()也可以从Erlang shell或其他程序里调用
初始化schema并启动Mnesia
要想在a@gin和b@skeppet这两个节点上运行Company数据库,每个节点必须已经具备Mnesia目录并初始化schema
有两种方式指定Mnesia目录
1)通过程序参数来指定
- %erl -mnesia dir '"/ldisc/scratch/Mnesia.Company"'
%erl -mnesia dir '"/ldisc/scratch/Mnesia.Company"'
2)如果没有输入命令行参数,则Mnesia使用当前节点的当前工作目录来作为Mnesia目录
以下命令在两个指定节点上运行Company数据库:
1)gin节点上
- gin %erl -sname a -mnesia dir '"/ldisc/scratch/Mnesia.company"'
gin %erl -sname a -mnesia dir '"/ldisc/scratch/Mnesia.company"'
2)在skeppet节点上
- skeppet %erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'
skeppet %erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'
3)在两个节点之一执行create_schema:
(a@gin1) > mnesia:create_schema([a@gin, b@skeppet]).
4)在两个节点上运行mnesia:start()
5)在两个节点之一执行以下代码来初始化数据库:
- 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,
- [{ram_copies, [a@gin, b@skeppet]},
- {attributes, record_info(fields, manager)}]),
- mnesia:create_table(at_dept,
- [{ram_copies, [a@gin, b@skeppet]},
- {attributes, record_info(fields, at_dept)}]),
- mnesia:create_table(in_proj,
- [{ram_copies, [a@gin, b@skeppet]},
- {attributes, record_info(fields, in_proj)}]).
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, [{ram_copies, [a@gin, b@skeppet]}, {attributes, record_info(fields, manager)}]), mnesia:create_table(at_dept, [{ram_copies, [a@gin, b@skeppet]}, {attributes, record_info(fields, at_dept)}]), mnesia:create_table(in_proj, [{ram_copies, [a@gin, b@skeppet]}, {attributes, record_info(fields, in_proj)}]).
数据库只需初始化一次,下次只需mnesia:start()启动即可从硬盘启动系统
mnesia:stop()方法在当前节点上停止Mnesia,start/0和stop/0都在本地Mnesia系统上起作用,没有启动和停止一些节点的方法
启动过程
Mnesia通过调用mnesia:start()来启动
该方法在本地初始化DBMS
配置选择会更改表的位置和加载顺序:
1)表只存储在本地,从本地Mnesia目录初始化
2)根据哪个备份是最新的来决定备份表从本地硬盘还是其它节点的完整表copy来初始化,Mnesia会决定哪份备份是最新的
3)一旦表被加载,则可以被其他节点访问
表的初始化时同步的,如果数据库比较大,则mnesia:start()可能消耗一部分时间
mnesia:wait_for_table(TabList, Timeout)会等待表的加载
mnesia:force_load_table(Tab)会强制从硬盘加载表而不管其他情况,这时可能远程的备份上的操作会丢失
4,创建新表
Mnesia提供方法mnesia:create_table(Name, ArgList)来创建新表,返回{atomic, ok}或{aborted, Reason}
1)Name是表名
2)ArgList是{Key, Value}的tuple
-{type, Type}
Type值为set、ordered_set或bag,默认为set
bag可以一个key对应多条record,而set和ordered_set只能一个key对应一条record
Mnesia表中不会出现重复的record(同样的key和content)
-{disc_copies, NodeList}
表存储在硬盘上
disc_copies类型的表备份的写操作会将数据写到disc和RAM中的表副本中
如果我们有如下需求:
1)读操作必须很快,直接在RAM操作
2)写操作必须写到硬盘中
那么我们可以一个表放到RAM中,一个disc_copies放到硬盘上
对disc_copies的表的写操作会分两步执行,首先将写操作添加到日志文件中,然后再RAM里执行真正的写操作
-{ram_copies, NodeList}
表存储在RAM里
ram_copies类型的表备份可以用mnesia:dump_tables(TabList)方法来导入到硬盘上
-{disc_only_copies, NodeList}
这种类型的表备份只存储在硬盘上,因此访问比较慢,但是这种类型的表消耗更少的内存
-{index, AttributeNameList}
AttributeNameList指定Mnesia构建和维护的索引名,有一个index表来索引list里的每个元素
Mnesia record的第一个field是key,所以不需要额外的索引
-{snmp, SnmpStruct}
表示该表通过简单网络管理协议(SNMP)来访问
-{local_content, true}
表名对所有Mnesia节点可见,但是内容对每个节点唯一
-{attributes, AtomList}
指定表的字段名
AtomList一般使用record_info(fields, record_name),而是硬编码属性列表,这样更易维护和更健壮,如果以后表结构更改则只用改record
-{record_name, Atom}
指定表记录的common name,表里所有的record都以Atom作为第一个元素
例如我们定义如下record:
- -record(funky, {x, y}).
-record(funky, {x, y}).
那么在两个备份节点和y属性上的一个额外索引的bag类型的表的创建代码:
- mnesia:create_table(funky, [{disc_copies, [N1, N2]}, {index, [y]}, {type, bag}, {attributes, record_info(fields, funky)}]).
mnesia:create_table(funky, [{disc_copies, [N1, N2]}, {index, [y]}, {type, bag}, {attributes, record_info(fields, funky)}]).
而mnesia:create_table(stuff, [])将在本地节点创建一个RAM表stuff,没有额外的索引,并且表的字段为[key,val]