我们在程序开发过程中,存在如下的一段代码:
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” 这个标题揭示了一个项目,它的目标是将PostgreSQL数据库作为Erlang的Mnesia分布式数据库系统的一个后端。Mnesia_ext是Mnesia的一个扩展,它允许添加自定义的数据...
"Amnesia_CCK"是一个基于开源系统开发的项目,它主要关注的是"失忆症"游戏的源代码,经过特定的调整和优化,以便在CCK(Content Creation Kit)平台上进行展示。CCK通常是一个工具集,允许用户创建、修改和扩展游戏...
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 ...
`oauth2_mnesia_backend`是Kivraoauth2项目中的一个组件,主要负责处理OAuth2认证过程中的数据存储部分,采用Erlang编程语言实现。OAuth2是一种授权框架,广泛用于安全地允许第三方应用访问用户资源,而无需共享用户...
目.录 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.回调行为
Erlang以其并发处理、容错性和高效性能在分布式系统领域备受推崇,而AMNESIA则进一步扩展了Erlang的功能,使开发者能够在Erlang环境中更自然地操作数据库。 在AMNESIA中,数据库操作被抽象化,使得程序员可以使用...
B站视频地址: 做了文字校验,已经成功上线,有兴趣的小伙伴可以扫码体验:可以微信搜索:失忆备忘录一、失忆的由来之所以开发这款软件,是因为在那段时间事情很多,但是经常忘记。虽然市面上类似的功能很多,我之前...
Api-Social-Amnesia.zip,忘记过去。社交健忘症确保你的社交媒体帐户只显示你最近的历史,而不是5年前“那个阶段”的帖子。,一个api可以被认为是多个软件设备之间通信的指导手册。例如,api可用于web应用程序之间的...
语言:English (United States) 遗忘的延伸 Chrome失忆症是一个Chrome扩展程序,可让您有选择地不记得自己的任何浏览历史记录。...有关更多信息,请访问https://github.com/DanielBok/chrome-amnesia。
Mnesia是Erlang OTP (Open Telephony Platform) 库中的一个核心组件,它是一个强大的分布式数据库系统,特别适用于需要高可用性、容错性和实时性能的场景,比如电信和相关领域。 Mnesia的设计理念是与Erlang的并发...
失忆症是一种提醒,允许您定义警报,贴纸(贴子)以提醒您一些重要的内容以及有关所需内容的注释。 可以将警报编程为在给定时间显示,可以在桌面上放置贴纸以随时查看。
随着业务需求的增长,单个 Mnesia 表的大小和性能可能会成为瓶颈。为了解决这个问题,Mnesia 提供了表分片(table fragmentation)的功能。通过使用 Linear Hashing 算法,Mnesia 可以将大表分散存储在多个节点上,...
Mnesia是一个分布式数据库管理系统(DBMS),适合于电信和其它需要持续运行和具备软实时特性的Erlang应用。 目 录 1 、介绍 . . .....1.1 关于 Mnesia ....11.1 mnesia_frag_hash 回调行为 . . .. . . .. . . 92
1. **分布式架构**:Mnesia支持在多台机器上分布数据和处理任务,这使得它能够在分布式环境中提供高性能和容错能力。 2. **容错性**:Mnesia被设计用于非停止系统,能够处理各种故障情况而不中断服务。这在电信系统...
由于Mnesia是为Erlang设计的,它能够充分利用Erlang的并发和轻量级进程特性,提供高效的性能。然而,为了达到最佳性能,需要根据应用需求进行适当的调优,比如选择合适的存储类型、优化查询、管理内存分配等。 ...