`
wqtn22
  • 浏览: 101070 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

mnesia的index_read的性能问题

 
阅读更多

我们在程序开发过程中,存在如下的一段代码:

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

    “mnesia_pg:Postgres后端通过mnesia_ext到Mnesia” 这个标题揭示了一个项目,它的目标是将PostgreSQL数据库作为Erlang的Mnesia分布式数据库系统的一个后端。Mnesia_ext是Mnesia的一个扩展,它允许添加自定义的数据...

    amnesia_cck:“ amensia”项目的源代码,已针对CCK展示进行了修改-Show source code

    "Amnesia_CCK"是一个基于开源系统开发的项目,它主要关注的是"失忆症"游戏的源代码,经过特定的调整和优化,以便在CCK(Content Creation Kit)平台上进行展示。CCK通常是一个工具集,允许用户创建、修改和扩展游戏...

    Mnesia User's Guide

    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项目的Mnesia后端

    `oauth2_mnesia_backend`是Kivraoauth2项目中的一个组件,主要负责处理OAuth2认证过程中的数据存储部分,采用Erlang编程语言实现。OAuth2是一种授权框架,广泛用于安全地允许第三方应用访问用户资源,而无需共享用户...

    erlang——Mnesia用户手册.pdf

    目.录 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.回调行为

    amnesia-开源

    Erlang以其并发处理、容错性和高效性能在分布式系统领域备受推崇,而AMNESIA则进一步扩展了Erlang的功能,使开发者能够在Erlang环境中更自然地操作数据库。 在AMNESIA中,数据库操作被抽象化,使得程序员可以使用...

    amnesia:失忆备忘录

    B站视频地址: 做了文字校验,已经成功上线,有兴趣的小伙伴可以扫码体验:可以微信搜索:失忆备忘录一、失忆的由来之所以开发这款软件,是因为在那段时间事情很多,但是经常忘记。虽然市面上类似的功能很多,我之前...

    Api-Social-Amnesia.zip

    Api-Social-Amnesia.zip,忘记过去。社交健忘症确保你的社交媒体帐户只显示你最近的历史,而不是5年前“那个阶段”的帖子。,一个api可以被认为是多个软件设备之间通信的指导手册。例如,api可用于web应用程序之间的...

    Chrome Amnesia-crx插件

    语言:English (United States) 遗忘的延伸 Chrome失忆症是一个Chrome扩展程序,可让您有选择地不记得自己的任何浏览历史记录。...有关更多信息,请访问https://github.com/DanielBok/chrome-amnesia。

    Mnesia用户手册.zip

    Mnesia是Erlang OTP (Open Telephony Platform) 库中的一个核心组件,它是一个强大的分布式数据库系统,特别适用于需要高可用性、容错性和实时性能的场景,比如电信和相关领域。 Mnesia的设计理念是与Erlang的并发...

    Amnesia-开源

    失忆症是一种提醒,允许您定义警报,贴纸(贴子)以提醒您一些重要的内容以及有关所需内容的注释。 可以将警报编程为在给定时间显示,可以在桌面上放置贴纸以随时查看。

    Mnesia table fragmentation 过程及算法分析

    随着业务需求的增长,单个 Mnesia 表的大小和性能可能会成为瓶颈。为了解决这个问题,Mnesia 提供了表分片(table fragmentation)的功能。通过使用 Linear Hashing 算法,Mnesia 可以将大表分散存储在多个节点上,...

    Mnesia用户手册 4.4.10版.rar

    Mnesia是一个分布式数据库管理系统(DBMS),适合于电信和其它需要持续运行和具备软实时特性的Erlang应用。 目 录 1 、介绍 . . .....1.1 关于 Mnesia ....11.1 mnesia_frag_hash 回调行为 . . .. . . .. . . 92

    mnesia数据库文档

    1. **分布式架构**:Mnesia支持在多台机器上分布数据和处理任务,这使得它能够在分布式环境中提供高性能和容错能力。 2. **容错性**:Mnesia被设计用于非停止系统,能够处理各种故障情况而不中断服务。这在电信系统...

    Mnesia用户手册(docx版)

    由于Mnesia是为Erlang设计的,它能够充分利用Erlang的并发和轻量级进程特性,提供高效的性能。然而,为了达到最佳性能,需要根据应用需求进行适当的调优,比如选择合适的存储类型、优化查询、管理内存分配等。 ...

Global site tag (gtag.js) - Google Analytics