Every example using Ets has a corresponding example in Mnesia. In general, all Ets examples also apply to Dets tables.
Select/match operations on Ets and Mnesia tables can become
very expensive operations. They usually need to scan the complete
table. Try to structure the data to minimize the need for select/match
operations. However, if you require a select/match operation,
it is still more efficient than using
In some circumstances, the select/match operations do not need
to scan the complete table.
For example, if part of the key is bound when searching an
When creating a record to be used in a select/match operation, you want most of the fields to have the value "_". The easiest and fastest way to do that is as follows:
#person{age = 42, _ = '_'}.
The
DO
... ets:delete(Tab, Key), ...
DO NOT
... case ets:lookup(Tab, Key) of [] -> ok; [_|_] -> ets:delete(Tab, Key) end, ...
Do not fetch data that you already have.
Consider that you have a module that handles the abstract data
type
If the function
DO
%%% 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]).
DO NOT
%%% 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]).
For non-persistent database storage, prefer Ets tables over
Mnesia
Assuming an Ets table that uses
[#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"}]
If you must return all data stored in the Ets table, you
can use
DO
... ets:select(Tab,[{ #person{idno='_', name='_', age='$1', occupation = '_'}, [], ['$1']}]), ...
DO NOT
... TabList = ets:tab2list(Tab), lists:map(fun(X) -> X#person.age end, TabList), ...
If you are only interested in the age of all persons named "Bryan", then:
DO
... ets:select(Tab,[{ #person{idno='_', name="Bryan", age='$1', occupation = '_'}, [], ['$1']}]), ...
DO NOT
... TabList = ets:tab2list(Tab), lists:foldl(fun(X, Acc) -> case X#person.name of "Bryan" -> [X#person.age|Acc]; _ -> Acc end end, [], TabList), ...
REALLY DO NOT
... TabList = ets:tab2list(Tab), BryanList = lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList), lists:map(fun(X) -> X#person.age end, BryanList), ...
If you need all information stored in the Ets table about persons named "Bryan", then:
DO
... ets:select(Tab, [{#person{idno='_', name="Bryan", age='_', occupation = '_'}, [], ['$_']}]), ...
DO NOT
... TabList = ets:tab2list(Tab), lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList), ...
If the data in the table is to be accessed so that the order
of the keys in the table is significant, the table type
An
An Ets table is a single-key table (either a hash table or a
tree ordered by the key) and is to be used as one. In other
words, use the key to look up things whenever possible. A
lookup by a known key in a
A simple solution would be to use the
An index table for the table in the previous examples would have to be a bag (as keys would appear more than once) and can have the following contents:
[#index_entry{name="Adam", idno=1}, #index_entry{name="Bryan", idno=2}, #index_entry{name="Bryan", idno=3}, #index_entry{name="Carl", idno=4}]
Given this index table, a lookup of the
... MatchingIDs = ets:lookup(IndexTable,"Bryan"), lists:map(fun(#index_entry{idno = ID}) -> [#person{age = Age}] = ets:lookup(PersonTable, ID), Age end, MatchingIDs), ...
Notice that this code never uses
Keeping an index table introduces some overhead when inserting records in the table. The number of operations gained from the table must therefore be compared against the number of operations inserting objects in the table. However, notice that the gain is significant when the key can be used to lookup elements.
If you frequently do a lookup on a field that is not the key of the table, you lose performance using "mnesia:select/match_object" as this function traverses the whole table. You can create a secondary index instead and use "mnesia:index_read" to get faster access, however this requires more memory.
Example
-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), ...
Using transactions is a way to guarantee that the distributed
Mnesia database remains consistent, even when many different
processes update it in parallel. However, if you have
real-time requirements it is recommended to use
Example
... % 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}), ...