7.表和数据库 | 7. Tables and Databases

7表和数据库

7.1 ETS、DETS和Mnesia

每个使用Ets的例子在Mnesia都有相应的例子。一般来说,所有Ets示例也适用于Dets表格。

选择/匹配操作

Ets和Mnesia表上的选择/匹配操作可能会变成非常昂贵的操作。他们通常需要扫描整个表格。尝试构建数据以最小化选择/匹配操作的需要。但是,如果您需要选择/匹配操作,它仍比使用效率更高tab2list。以下部分提供了这个以及如何避免选择/匹配的示例。的功能ets:select/2mnesia:select/3将被优先于ets:match/2ets:match_object/2,和mnesia:match_object/3

在某些情况下,选择/匹配操作不需要扫描整个表。例如,如果在搜索ordered_set表格时绑定了键的一部分,或者它是Mnesia表,并且在选定/匹配的字段上存在辅助索引。如果键完全绑定,则除非您有袋表并且只对具有特定键的元素子集感兴趣,否则没有必要进行选择/匹配。

创建要在选择/匹配操作中使用的记录时,您希望大多数字段的值为“_”。最简单快捷的方法如下:

#person{age = 42, _ = '_'}.

删除元素

delete如果元素不在表格中,操作被认为是成功的。因此,在删除之前检查该元素是否存在于Ets/Mnesia表中的所有尝试都是不必要的。下面是一个Ets表的例子:

... ets:delete(Tab, Key), ...

不要

... case ets:lookup(Tab, Key) of [] -> ok; [_|_] -> ets:delete(Tab, Key) end, ...

取数据

不要获取您已有的数据。

考虑你有一个处理抽象数据类型的模块Person。您导出接口功能print_person/1,它使用了内部职能print_name/1print_age/1以及print_occupation/1

如果函数print_name/1等是接口函数,则情况会有所不同,因为您不希望接口的用户知道内部数据表示。

%%% Interface function print_person(PersonId) -> %% Look up the person in the named table person, case ets:lookup(person, PersonId) of [Person] -> print_name(Person), print_age(Person), print_occupation(Person [] -> io:format("No person with ID = ~p~n", [PersonID]) end. %%% Internal functions print_name(Person) -> io:format("No person ~p~n", [Person#person.name]). print_age(Person) -> io:format("No person ~p~n", [Person#person.age]). print_occupation(Person) -> io:format("No person ~p~n", [Person#person.occupation]).

不要

%%% Interface function print_person(PersonId) -> %% Look up the person in the named table person, case ets:lookup(person, PersonId) of [Person] -> print_name(PersonID), print_age(PersonID), print_occupation(PersonID [] -> io:format("No person with ID = ~p~n", [PersonID]) end. %%% Internal functionss print_name(PersonID) -> [Person] = ets:lookup(person, PersonId), io:format("No person ~p~n", [Person#person.name]). print_age(PersonID) -> [Person] = ets:lookup(person, PersonId), io:format("No person ~p~n", [Person#person.age]). print_occupation(PersonID) -> [Person] = ets:lookup(person, PersonId), io:format("No person ~p~n", [Person#person.occupation]).

非持久数据库存储

对于非持久性数据库存储,比Enes local_content表更喜欢Mnesia 表。dirty_write与Ets写作相比,即使是Mnesia 业务也有固定的开销。Mnesia必须检查表格是否被复制或有索引,这至少涉及每个Ets的查找dirty_write。因此,Ets的写作总是比Mnesia写的要快。

tab2list

假设一个idno用作键的Ets表,它包含以下内容:

[#person{idno = 1, name = "Adam", age = 31, occupation = "mailman"}, #person{idno = 2, name = "Bryan", age = 31, occupation = "cashier"}, #person{idno = 3, name = "Bryan", age = 35, occupation = "banker"}, #person{idno = 4, name = "Carl", age = 25, occupation = "mailman"}]

如果您必须返回存储在Ets表中的所有数据,则可以使用ets:tab2list/1。但是,通常情况下,您只对部分信息感兴趣,在这种情况下,信息ets:tab2list/1很昂贵。如果您只想从每条记录中提取一个字段,例如每个人的年龄,则:

... ets:select(Tab,[{ #person{idno='_', name='_', age='$1', occupation = '_'}, [], ['$1']}]), ...

不要

... TabList = ets:tab2list(Tab), lists:map(fun(X) -> X#person.age end, TabList), ...

如果您只对名为“Bryan”的所有人的年龄感兴趣,那么:

... ets:select(Tab,[{ #person{idno='_', name="Bryan", age='$1', occupation = '_'}, [], ['$1']}]), ...

不要

... TabList = ets:tab2list(Tab), lists:foldl(fun(X, Acc) -> case X#person.name of "Bryan" -> [X#person.age|Acc]; _ -> Acc end end, [], TabList), ...

真的不

... TabList = ets:tab2list(Tab), BryanList = lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList), lists:map(fun(X) -> X#person.age end, BryanList), ...

如果您需要存储在Ets表中关于名为“Bryan”的人的所有信息,则:

... ets:select(Tab, [{#person{idno='_', name="Bryan", age='_', occupation = '_'}, [], ['$_']}]), ...

不要

... TabList = ets:tab2list(Tab), lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList), ...

Ordered_set表

如果要访问表中的数据以便表中的键的顺序很重要,ordered_set则可以使用表类型而不是更常用的set表类型。一个ordered_set总是在经过长期二郎为了有关的关键领域,以便从功能,如返回值selectmatch_object以及foldl由键值进行排序。ordered_set使用firstnext操作遍历an 也会返回所订购的键。

一个ordered_set只保证对象在处理关键顺序。即使密钥未包含在结果中,函数的结果也会ets:select/2键的顺序显示。

7.2 Ets-Specific

使用ETS表的密钥

一个Ets表是一个单键表(哈希表或按键排序的一棵树)并且将被用作一个表。换句话说,尽可能使用键来查找事物。setEts表中的已知密钥的查找是恒定的,对于ordered_setEts表,它是O(logN)。密钥查找总是比整个表必须被扫描的呼叫更可取。在前面的示例中,该字段idno是表的关键字,而只有名称已知的所有查找才会导致完全扫描(可能较大)的表以获得匹配结果。

一个简单的解决方案是将该name字段用作键而不是idno字段,但如果名称不是唯一的,则会导致问题。更通用的解决方案是创建第二个表,其中name包含键和idno数据,即索引(反转)与该name字段相关的表。显然,第二张表必须与主表保持一致。Mnesia可以为你做到这一点,但与使用Mnesia相关的开销相比,家庭酿造指数表可以非常有效。

前面示例中的表的索引表必须是袋子(因为键会出现多次),并且可以具有以下内容:

[#index_entry{name="Adam", idno=1}, #index_entry{name="Bryan", idno=2}, #index_entry{name="Bryan", idno=3}, #index_entry{name="Carl", idno=4}]

给定这个索引表,age所有名为“Bryan”的人员可以按如下方式查找字段:

... MatchingIDs = ets:lookup(IndexTable,"Bryan"), lists:map(fun(#index_entry{idno = ID}) -> [#person{age = Age}] = ets:lookup(PersonTable, ID), Age end, MatchingIDs), ...

请注意,此代码从不使用ets:match/2,而是使用该ets:lookup/2调用。该lists:map/2调用仅用于遍历idno表中与名称“Bryan”匹配的s;因此主表中的查找数量被最小化。

在表中插入记录时,保留索引表会引入一些开销。因此,从表中获得的操作数量必须与在表格中插入对象的操作数量进行比较。但是,请注意,当密钥可用于查找元素时,增益很重要。

7.3 Mnesia-特异性

二级指数

如果您经常在不是表格关键字的字段上进行查找,则会使用“mnesia:select / match_object”失去性能,因为此函数会遍历整个表格。您可以创建一个二级索引,并使用“mnesia:index_read”来获得更快的访问权限,但这需要更多的内存。

-record(person, {idno, name, age, occupation}). ... {atomic, ok} = mnesia:create_table(person, [{index,[#person.age]}, {attributes, record_info(fields, person)}]), {atomic, ok} = mnesia:add_table_index(person, age), ... PersonsAge42 = mnesia:dirty_index_read(person, 42, #person.age), ...

交易

使用事务处理是确保分布式Mnesia数据库保持一致的一种方式,即使许多不同的进程并行更新它们也是如此。但是,如果您有实时要求,建议使用dirty操作而不是事务。使用dirty操作时,您失去了一致性保证; 这通常只通过让一个进程更新表来解决。其他进程必须将更新请求发送到该进程。

... % Using transaction Fun = fun() -> [mnesia:read{Table, Key}), mnesia:read{Table2, Key2})] end, {atomic, [Result1, Result2]} = mnesia:transaction(Fun), ... % Same thing using dirty operations ... Result1 = mnesia:dirty_read{Table, Key}), Result2 = mnesia:dirty_read{Table2, Key2}), ...