我们在程序开发过程中,存在如下的一段代码:
F = fun () ->
Recs = mnesia:index_read(table, Index, indexfield),
NewRecs = some_ops_on(Recs),
mnesia:write(NewRecs)
end,
mnesia:transaction(F).
这段代码在运行时并发一直上不去,读者是否清楚其原因呢?
文章的末尾总结了一些mnesia的使用注意事项,对源码没有兴趣的读者可以直接看末尾。
仔细分析一下mnesia:index_read/3的工作过程,就豁然开朗了,代码版本R15B03:
mnesia.erl
index_read(Tab, Key, Attr) ->
case get(mnesia_activity_state) of
{?DEFAULT_ACCESS, Tid, Ts} ->
index_read(Tid, Ts, Tab, Key, Attr, read);
{Mod, Tid, Ts} ->
Mod:index_read(Tid, Ts, Tab, Key, Attr, read);
_ ->
abort(no_transaction)
end.
index_read(Tid, Ts, Tab, Key, Attr, LockKind)
when is_atom(Tab), Tab /= schema ->
case element(1, Tid) of
ets ->
dirty_index_read(Tab, Key, Attr); % Should be optimized?
tid ->
Pos = mnesia_schema:attr_tab_to_pos(Tab, Attr),
case LockKind of
read ->
case has_var(Key) of
false ->
Store = Ts#tidstore.store,
Objs = mnesia_index:read(Tid, Store, Tab, Key, Pos),
%%进入mnesia_index:read进行索引读
Pat = setelement(Pos, val({Tab, wild_pattern}), Key),
add_written_match(Store, Pat, Tab, Objs);
true ->
abort({bad_type, Tab, Attr, Key})
end;
_ ->
abort({bad_type, Tab, LockKind})
end;
_Protocol ->
dirty_index_read(Tab, Key, Attr)
end;
index_read(_Tid, _Ts, Tab, _Key, _Attr, _LockKind) ->
abort({bad_type, Tab}).
mnesia_index.erl
read(Tid, Store, Tab, IxKey, Pos) ->
ResList = mnesia_locker:ixrlock(Tid, Store, Tab, IxKey, Pos),
%%上锁的同时读取记录
%% Remove all tuples which don't include Ixkey, happens when Tab is a bag
case val({Tab, setorbag}) of
bag ->
mnesia_lib:key_search_all(IxKey, Pos, ResList);
_ ->
ResList
end.
mnesia_locker.erl
ixrlock(Tid, Store, Tab, IxKey, Pos) ->
case val({Tab, where_to_read}) of
nowhere ->
mnesia:abort({no_exists, Tab});
Node ->
%%% Old code
%% R = l_request(Node, {ix_read, Tid, Tab, IxKey, Pos}, Store),
%% rlock_get_reply(Node, Store, Tab, R)
case need_lock(Store, Tab, ?ALL, read) of
no when Node =:= node() ->
ix_read_res(Tab,IxKey,Pos);
_ -> %% yes or need to get the result from other node
R = l_request(Node, {ix_read, Tid, Tab, IxKey, Pos}, Store),
%%首次到达时需要请求表锁
rlock_get_reply(Node, Store, Tab, R)
%%从锁授权中得到行记录
end
end.
l_request(Node, X, Store) ->
{?MODULE, Node} ! {self(), X},
%%向锁管理器请求锁,锁内容为ix_read
l_req_rec(Node, Store).
%%同步等待锁请求的返回
%%注意,这里也是大量进程所阻塞的地方,即等待锁请求的返回,这是由index_read后的write产生的,write全局锁与本地表锁冲突
l_req_rec(Node, Store) ->
?ets_insert(Store, {nodes, Node}),
receive
%%等待锁请求响应消息
{?MODULE, Node, Reply} ->
Reply;
{mnesia_down, Node} ->
{not_granted, {node_not_running, Node}}
end.
再来分析ix_read锁请求到达锁管理器后的处理:
mnesia_locker.erl
loop(State) ->
receive
…
{From, {ix_read, Tid, Tab, IxKey, Pos}} ->
case ?ets_lookup(mnesia_sticky_locks, Tab) of
[] ->
set_read_lock_on_all_keys(Tid,From,Tab,IxKey,Pos),
%%此处不考虑粘着锁,而直接考虑在该场景下的效果
loop(State);
[{_,N}] when N == node() ->
set_read_lock_on_all_keys(Tid,From,Tab,IxKey,Pos),
loop(State);
[{_,N}] ->
Req = {From, {ix_read, Tid, Tab, IxKey, Pos}},
From ! {?MODULE, node(), {switch, N, Req}},
loop(State)
end;
…
set_read_lock_on_all_keys(Tid, From, Tab, IxKey, Pos) ->
Oid = {Tab,?ALL},
Op = {ix_read,IxKey, Pos},
Lock = read,
case can_lock(Tid, Lock, Oid, {no, bad_luck}) of
{yes, Default} ->
Reply = grant_lock(Tid, Op, Lock, Oid, Default),
%%这里进行锁的授权
reply(From, Reply);
{{no, Lucky},_} ->
C = #cyclic{op = Op, lock = Lock, oid = Oid, lucky = Lucky},
?dbg("Rejected ~p ~p ~p ~p ~n", [Tid, Oid, Lock, Lucky]),
reply(From, {not_granted, C});
{{queue, Lucky},_} ->
?dbg("Queued ~p ~p ~p ~p ~n", [Tid, Oid, Lock, Lucky]),
%% Append to queue: Nice place for trace output
?ets_insert(mnesia_lock_queue,
#queue{oid = Oid, tid = Tid, op = Op,
pid = From, lucky = Lucky}),
?ets_insert(mnesia_tid_locks, {Tid, Oid, {queued, Op}})
end.
…
grant_lock(Tid, {ix_read,IxKey,Pos}, Lock, Oid = {Tab, _}, Default) ->
try
Res = ix_read_res(Tab, IxKey,Pos),
%%从索引表中读出行记录
set_lock(Tid, Oid, Lock, Default),
{granted, Res, [?ALL]}
%%可以清晰的看出,实际的锁授权即为表锁,Oid为{Tab,?ALL}
catch _:_ ->
{not_granted, {no_exists, Tab, {index, [Pos]}}}
end;
…
锁请求返回时,事务线程的处理:
mnesia_locker.erl
rlock_get_reply(Node, Store, Tab, {granted, V, RealKeys}) ->
%% Kept for backwards compatibility, keep until no old nodes
%% are available
L = fun(K) -> ?ets_insert(Store, {{locks, Tab, K}, read}) end,
lists:foreach(L, RealKeys),
?ets_insert(Store, {nodes, Node}),
V;
返回值即为查找到的行记录。
由此可见mnesia的索引存在的问题,即index_read为表锁,极大地影响并发,应慎用。
这里总结一些mnesia的使用注意事项:
1.一次事务读的行记录越少越好,跨越的表越少越好,因为每一次读都会产生一个读锁,记录和表越多,与写锁冲突的几率就越大,阻塞写的几率就越大;
2.如果多个表的主键相同,应该尽量将这些表合并,除非:
a)表的规模可能很大,导致一个ets表存不下这些数据,此时可以考虑拆字段或按id切分;
b)表只有很少的字段会频繁读到,一次读出全部内容的几率很小;
c)关于此条,也可以结合数据库表设计原则进行,但设计时一定要注意,mnesia只是一个kv存储;
3.不要用index_read,因为index_read会锁住全表,并严重阻塞写操作,使得读写较为平均的并发受到很大限制。如果需要索引,那么存两张表,一张专门用于索引,索引与主键一一映射;
4.majority表使用majority事务,这个事务至少有两次同步的网络请求和一次异步的网络请求,这个代价较大,而普通事务只有一次同步的网络请求和一次异步的网络请求,同步事务有两次同步的网络请求;
5.mnesia事务内部的操作应越短越好,因为访问的记录产生的锁只在事务提交时释放,如果内部无关操作太多,可能会阻塞其它请求;
相关推荐
mnesia_pg Postgres后端通过mnesia_ext到Mnesia 这是一个非常原始的实现,用作概念验证和初步基准测试。 尚未用于生产中。 随时进行改进。
session, specify a Mnesia database directory, initialize a database schema, start Mnesia, and create tables. Initial prototyping of record definitions is also discussed. • Build a Mnesia Database ...
目.录 1、介绍 1.1.关于.Mnesia 1.2.Mnesia.... 2、开始.Mnesia ...3、构建.Mnesia....5、其它.Mnesia....5.7.Mnesia....5.8.调试.Mnesia....5.9.Mnesia....5.11.Mnesia....6.Mnesia....11.1.mnesia_frag_hash.回调行为
oauth2_mnesia_backend 作者: Carlos ( )。 适用于后端。
B站视频地址: 做了文字校验,已经成功上线,有兴趣的小伙伴可以扫码体验:可以微信搜索:失忆备忘录一、失忆的由来之所以开发这款软件,是因为在那段时间事情很多,但是经常忘记。虽然市面上类似的功能很多,我之前...
Api-Social-Amnesia.zip,忘记过去。社交健忘症确保你的社交媒体帐户只显示你最近的历史,而不是5年前“那个阶段”的帖子。,一个api可以被认为是多个软件设备之间通信的指导手册。例如,api可用于web应用程序之间的...
语言:English (United States) 遗忘的延伸 Chrome失忆症是一个Chrome扩展程序,可让您有选择地不记得自己的任何浏览历史记录。...有关更多信息,请访问https://github.com/DanielBok/chrome-amnesia。
Mnesia用户手册Mnesia用户手册
失忆症是一种提醒,允许您定义警报,贴纸(贴子)以提醒您一些重要的内容以及有关所需内容的注释。 可以将警报编程为在给定时间显示,可以在桌面上放置贴纸以随时查看。
Mnesia用户手册.pdf
Amnesia
Mnesia是一个分布式数据库管理系统(DBMS),适合于电信和其它需要持续运行和具备软实时 特性的Erlang应用。
erlang系统自带的数据库mnesia的官方文档。
失忆症 “ amensia”项目的源代码,已针对CCK展示进行了修改
Mnesia用户手册(docx版) 详细讲解Mnesia数据库操作
Mnesia用户手册(PDF版本) 详细讲述Mnesia数据库操作。
Mnesia table fragmentation 过程及算法分析。erlang就算在64位下dets的空间限制仍旧是2g,同样影响了mnesia,如果有更大需求,就必须使用Mnesia的 table fragmentation 技术
Mnesia是一个分布式数据库管理系统(DBMS),适合于电信和其它需要持续运行和具备软实时特性的Erlang应用。 目 录 1 、介绍 . . .....1.1 关于 Mnesia ....11.1 mnesia_frag_hash 回调行为 . . .. . . .. . . 92
AMNESIA是一个Erlang库,提供了用于连接关系DBMS的抽象层。 它允许设计人员使用本机Erlang类型和语言构造将关系数据库集成到Erlang程序中。