Erlang ETS表
1. ETS表
ETS表是一种用于存储和检索Erlang数据的内存数据库。采用键值(key-value)结构的存储方式。
ETS表有4种类型:
- set : 默认的表类型。每个键(key)对应一个值(value)。键是唯一的。
- ordered_set : 与 set 类似,但是按照 Erlang/Elixir 项式来排序。需要注意的是这里键的比较方式。键可以不同,只要「相等」即可,例如 1 和 1.0 就是「相等」的。
- bag : 每个键可以包括多个对象,但一个对象在一个键中只能有一个实例。
- duplicate_bag : 每个键可以包括多个对象,也允许对象重复.
基本操作
建表
ets:new(Name,Options) -> tid() | atom()
创建一个新的ETS表,并返回表的标识符。
Name是一个原子类型,如果想通过表明访问ETS表,则只需在Options添加named_table
例子:
> ets:new(t,[]).
40977
> ets:new(tab, [set, named_table]).
tab
> ets:new(tab2,[bag]).
16401
插入
ets:insert(Tab, ObjectOrObjects) -> true
向ETS表中插入一条记录
Tab是表的标识符即前面new/2的返回值,ObjectOrObjects是一个元组或者元组列表
例子:
> ets:insert(tab,{a,1}).
true
> ets:insert(tab,[{a,1,32}]).
true
> ets:lookup(tab,a).
[{a,1,32}]
ets:insert_new(Tab,ObjectOrObjects) -> boolean()
与ets:insert/2类似,不同之处在于不会进行数据覆盖,发现已存在key,则插入失败
查询
ets:lookup(Tab,Key)-> [tuple()]
根据键查找并返回匹配的记录
例子:
> ets:lookup(tab,a).
[{a,1,32}]
ets:match(Tab,Pattern) -> [term()]
根据模式匹配查找并返回符合条件的记录列表
例子:
> ets:match(tab,{‘_’,‘$3’,‘$2’}).
[[32,1]]
这里的{‘$1’,‘$3’,‘$2’}是匹配的语法
变量可以放在元组中进行模式匹配。常规变量的书写形式为’$0’、‘$1’、 ‘$2’等(数值编号不重要,只用来标识获取的结果的位置)。“可忽略”变量可被书写成’_'。即原本的1,32因为写的数值编号位置从而变为了32,1
ets:match_object(Tab,Pattern)->[tuple]
与ets:match/2类似,不过返回的是和模式匹配的完整记录
例子
> ets:match_object(tab,{‘_’,‘$3’,‘$2’}).
[{a,1,32}]
ets:select(Tab,MatchSpec) -> [term()]
除了可用与ets:match类似可用使用模式匹配的语法
[{{'$1', '_', '$3'}, [], ['$_']}
以上格式使用select/2 来做更高级的查询。分别表示匹配模式,条件语句,以及返回结果的格式。
可用更简单的使用
也可采用fun2ms/1进行匹配查询
例子:
> Fun = ets:fun2ms(fun ({,,V}) when V > 1 -> V end).
[{{‘‘,’’,‘$1’},[{‘>’,‘$1’,1}],[‘$1’]}]
*>*ets:select(tab,Fun).
" " ---------------//其实是[32]
删除
ets:delete(Tab) -> true
删除ETS表
ets:delete(Tab,Key) -> true
删除表中以Key为键的记录
与ets:lookup/2类似
例子:
> ets:delete(tab,a).
true
> ets:lookup(tab,a).
[]
2. 基于磁盘的 ETS (DETS)
DETS是ETS的磁盘实现版本,它们之间的主要不同在于:
- DETS中没有ordered_set类型的表;
- DETS磁盘文件的大小不能超过2 GB;
- 表的创建和关闭有些变化。创建新的数据库表要调用dets:open_file/2,关闭这个表可以调用dets:close/1。此后,可以调用dets:open_file/1重新打开该表
- ETS表格内部的操作是原子的,可以保证数据的一致性。对于ETS表格的操作,要么全部成功执行并提交,要么在失败时回滚。DETS表格没有提供像ETS那样的原子性操作。
基本操作
与ETS表操作类似,这里就只做简单演示
打开或创建表
dets:open_file(Name, Args) -> {'ok', Reference} | {'error', Reason}
第一个参数是表名,第二个Args是一个可选项
{type, type()}
:指定表的类型,与ets表的类型一样,ordered_set类型除外{repair, boolean()}
:指定在打开表格时是否自动进行修复操作。如果设置为 true,则 DETS 表格在打开时会进行修复,以解决可能出现的错误。默认值为 false{keypos, keypos()}
:指定用于查找记录的键的位置。keypos() 是一个整数,表示记录元组中键的位置,从 1 开始计数。默认值为 1{'access',access()}
: 指定访问权限,read | read_write, 即只有读
和读写
两个值
例子:
> dets:open_file(tab,[{type,set}]).
{ok,tab}
可用看见在目录下多了一个文件
也可指定目录生成
> dets:open_file(“F:/tab”,[]).
{ok,“F:/tab”}
插入数据
dets:insert(Name, Objects) -> 'ok' | {'error', Reason}
与ets:insert/2类似,
Name 就是表名,Objects是元组或元组列表
例子:
> dets:insert(tab,{a,1}).
ok
> dets:lookup(tab,a).
[{a,1}]
磁盘中有了数据
此外还有dets:insert_new/2函数,与ets:insert_new/2类似。
查询数据
dets:lookup(Name,Key) ->Objects | {'error', Reason}
与ets:lookup/2函数类似
例子:
> dets:lookup(tab,a).
[{a,1}]
此外还有dets:match,dets:select,也是与ets中的函数对应
删除数据
delete(Name, Key) -> 'ok' | {'error', Reason}
与ets:delete/2函数类似
例子:
> dets:delete(tab,a).
ok
> dets:lookup(tab,a).
[]
遍历
有时候我们需要对表中数据进行遍历操作,例如查询表中所有数据并返回。
这时候我们可用使用dets:first(Name)和dets:next(Name, Key)
,
例子
-module(test).
-author("01").
%%%=======================EXPORT=======================
-export([execute_transaction/4, select_all/1]).
%%%=======================INCLUDE======================
%%%=======================RECORD=======================
%%%=======================DEFINE=======================
%%%=======================TYPE=========================
%%%=================EXPORTED FUNCTIONS=================
%% ----------------------------------------------------
%% Description: 查询表所有数据
%% ----------------------------------------------------
select_all(Tab) ->
F = dets:first(Tab), %%获取表中第一个key
select_all(Tab, F, []).
select_all(_Tab, '$end_of_table', Acc) -> %% '$end_of_table' 是结束符
Acc;
select_all(Tab, Key, Acc) ->
NewAcc = lists:append(Acc, dets:lookup(Tab, Key)), %%获取下一个key
NextKey = dets:next(Tab, Key),
select_all(Tab, NextKey, NewAcc).
测试
在测试之前插入了多条数据与编译,这里不做演示
> {_,Tab} = dets:open_file(tab).
{ok,#Ref<0.0.10.188>}
> test:select_all(Tab).
[{b,21},{qw,{aeqw,wqeq}},{ws,ewq}]
当然,ets中也可以如此操作。
关闭表
dets:close(Name)
关闭表
例子:
> dets:close(tab).
ok
> dets:lookup(tab,a).
** exception error: bad argument
in function dets:lookup/2
called as dets:lookup(tab,a)
3. Mnesia
Mnesia是位于ETS和DETS之上的一个软件层,为这两个数据存储系统增加了许多新功能。它包含的大多数特性都是需要密集使用ETS和DETS软件系统所必需的。Mnesia可以自动管理ETS和DETS的写入,它既具有DETS的持久化能力,又具有ETS的性能,并且还能够自动地在多个Erlang节点间进行数据库的复制。
Mnesia的核心思想是:用记录来定义表的结构。每个表可以存储一批相似的记录,任何能够放到记录中的东西都可以存储到Mnesia表中,包括原子、进程标识、引用等。
Mnesia提供了对分区(sharding)、事务和分布式的支持。
启动 Mnesia
首先我定义了一个记录并把改记录使用rr(test)到erlang shell中
> mnesia:create_schema([node()]).
ok
> mnesia:start().
ok
> mnesia:create_table(person, [{attributes, record_info(fields, person)}]).
{atomic,ok}
mnesia:create_schema([node()])
在本地节点上初始化一个空的 schema.
mnesia:start()
启动mnesia.
mnesia:create_table(person, [{attributes, record_info(fields, person)}]).
来创建表 person.
如果想指定存储数据的目录可用在启动Erlang是使用参数-mnesia dir ""
指定了 Mnesia 存储数据的目录
操作
写mnesia:dirty_write(Val).
> mnesia:dirty_write({person, 1, “Seymour Skinner”, “Principal”}).
ok
> mnesia:dirty_write({person, 2, “Homer Simpson”, “Safety Inspector”}).
ok
读mnesia:dirty_read({Tab,Key}).
> mnesia:dirty_read({person,1}).
[#person{id = 1,name = “Seymour Skinner”,job = “Principal”}]
> mnesia:dirty_read({person,2}).
[#person{id = 2,name = “Homer Simpson”, job = “Safety Inspector”}]
> mnesia:dirty_read({person,3}).
[]
以上读写的方式是脏操作方式。一般情况下,我们都不会使用脏操作,因为脏操作并不一定保证成功。
此外还有mnesia:dirty_delete,mnesia:dirty_select
等脏操作
事务
事务是一种数据库操作的执行单元,它将一组数据库操作作为一个不可分割的逻辑单元来执行。事务具有原子性,一致性,隔离性,持久性。
Mnesia 的事务是通过对数据库的多个操作包含到一个函数体中来实现
mnesia:transaction/1
函数接受一个函数作为参数,该函数表示要在事务中执行的操作。如果该函数成功完成(不抛出异常),则事务被提交。如果函数抛出异常或返回 {aborted, Reason},则事务被回滚。
测试回滚
%% ----------------------------------------------------
%% Description: 测试事务
%% ----------------------------------------------------
write() ->
fun() ->
mnesia:write({person, 4, "Marge 1 Simpson", "home maker"}),
mnesia:write({person, 5, "Marge 2 Simpson", "home maker"}),
mnesia:write({person, 6, "Marge 3 Simpson", "home maker"}),
throw({error, "Some error"}), %%抛出异常
ok
end.
测试
> mnesia:transaction(test:write()).
{aborted,{throw,{error,“Some error”}}}
> mnesia:dirty_read({person,4}).
[]
测试不回滚
%% ----------------------------------------------------
%% Description: 测试事务
%% ----------------------------------------------------
write() ->
fun() ->
mnesia:write({person, 4, "Marge 1 Simpson", "home maker"}),
mnesia:write({person, 5, "Marge 2 Simpson", "home maker"}),
mnesia:write({person, 6, "Marge 3 Simpson", "home maker"}),
ok
end.
> mnesia:transaction(test:write()).
{atomic,ok}
> mnesia:dirty_read({person,4}).
[#person{id = 4,name = “Marge 1 Simpson”,job = “home maker”}]
> mnesia:dirty_read({person,5}).
[#person{id = 5,name = “Marge 2 Simpson”,job = “home maker”}]
> mnesia:dirty_read({person,6}).
[#person{id = 6,name = “Marge 3 Simpson”,job = “home maker”}]
索引
Mnesia 也支持在非主键字段上添加索引,然后通过这个索引来查询数据
mnesia:add_table_index(Tab, Ix)
例子:
> mnesia:add_table_index(person,job).
{atomic,ok}
结果跟 mnesia:create_table/2 返回的相同:
{atomic, ok} 表示执行成功
{aborted, Reason} 表示执行失败
通过索引获取数据
*>*mnesia:transaction(fun()->mnesia:index_read(person,“Principal”,job) end).
{atomic,[{person,1,“Seymour Skinner”,“Principal”}]}
查询
mnesia:match_object/1
与ets:match函数类似
例子
> mnesia:transaction(fun()-> mnesia:match_object({person,‘‘,“Seymour Skinner”,’’}) end).
{atomic,[{person,1,“Seymour Skinner”,“Principal”}]}
mnesia:select/2
与ets:select函数类似
例子
查询id小于3的数据的名字
*>*mnesia:transaction(fun () -> mnesia:select(person, [{{person, ‘$1’, ‘$2’, ‘$3’}, [{‘<’, ‘$1’, 3}], [‘$2’]}])end).
{atomic,[“Seymour Skinner”,“Homer Simpson”]}
当然也可用所有fun2ms函数进行查询,与ets中类似