Erlang06_ OTP Mnesia_Erlang数据库

总篇:15

编辑于 2025/5/13 22:30

截稿于: 2025/5/13 23:30

简介

OTP中的分布式数据库组件,内部基于ets和dets,具备DBMS的一切操作功能。

特性:

  1. 支持事务,也几乎只在事务操作表数据
  2. 支持读写锁,锁的范围有四种:单记录(单行),整表,一般只锁单记录
  3. 支持索引 (加快查询)
  4. 不支持动态增加新字段/删除字段
  5. 表记录的第一个字段强制视为主键,不能更改其他字段为主键,所以通常是Id

原语

  1. ok | {error, Reason} = mnesia:create_schema([node()]):创建数据库物理结构(一个节点只执行一次)
  2. ok = mnesia:start():启动本节点数据库服务,像Mysqld
  3. stopped = mnesia:stop() :停止本节点数据库服务

  1. {atomic, ok} | {aborted, Reason} = mnesia:create_table(Name, Options):创建表,创建时必须有基本Options配置
{atomic, ok} | {aborted, Reason} = mnesia:create_table(Name, TableOptions):以Options配置创建表

Options:
[
     %必须的配置
    {attributes, record_info(fields, student)}, % 定义表结构为student记录
    {attributes, record_info(fields, {id,name,age})},% 也可以直接指定字段名不指定为记录名
    {disc_copies, [node()]},     % 存储类型选项 3选1 内存+磁盘存储 (数据持久化) ets+dets
    {ram_copies, [node()]},      % 存储类型选项 3选1 仅内存存储 (节点关闭后数据丢失) ets
    {disc_only_copies, [node()]},% 存储类型选项 3选1 仅磁盘存储 (数据持久化) dets

    % 非必须配置
    {type, Type},                % 表类型:set|ordered_set|bag,不写默认set
    {index, [AttributeName]},    % 指定索引字段列表
    % 以下不熟
    {record_name, Name},         % 记录名(如果与表名不同)
    {load_order, Integer},       % 加载顺序
    {majority, boolean()},       % 是否需要大多数节点确认
    {snmp, SnmpStruct},         % SNMP 配置
    {local_content, boolean()}   % 是否为本地内容
]
  1. {atomic, ok} | {aborted, Reason} = mnesia:delete_table(Name):删除表
  2. {atomic, ok} = mnesia:clear_table(user):清空表
  3. {atomic, ok} | {aborted, Reason} = mnesia:add_table_index(Tab, FieldName):动态添加索引
  4. {atomic, Value} | {aborted, Reason}(失败,回滚) = mnesia:transaction(Fun):事务执行Fun函数,最重要的部分,以下罗列事物的主要原语:
% Student =#student{1,jack,15} -> {student,1,jack,15}
mnesia:transaction(fun() ->
    % 写入记录,不用指定表名是因为表名就在记录里
    mnesia:write(Student),
    % 获取记录(单行,按主键)
    mnesia:read(Student),
    % 删除记录(按主键)
    mnesia:delete(Student),
    % 模式匹配记录(同ets),不用指定表名是因为表名就在记录里
    mnesia:match_object({student, '_', "jack", '_'}),
    % 按条件查询(QLC),就是将表内数据遍历取出来在列表推导式中过滤
    % qlc还提供 联表、排序、分页、limit ,Sql有的基本都有,但这里是erlang,不需要这么多复杂的操作,只需要按id查即可
    Q = qlc:q([X || X <- mnesia:table(student),
                    element(3, X) =:= "jack"]),
    qlc:e(Q),
    % 锁定记录,记录锁定后,在事务结束后自动释放
    mnesia:lock({record, student, 1}, write),   % 写锁(共享锁)多事务可读,不能写
    mnesia:lock({record, student, 1}, read),    % 读锁(排他锁)加锁事务可读写,其他既不能读也不能写
    mnesia:lock({table, student}, write),       % 锁定整个表 
    % 更新记录(先读后写),没有现成的update函数,所以更新记录的事务必须加写锁
    [OldRecord] = mnesia:read({student, 1}),
    NewRecord = setelement(3, OldRecord, "joe"),  
    mnesia:write(NewRecord)
end).

以上为常用原语,还有很多例如获取表信息的暂时忽略,现在基于次再次改造student_manage

student_db

  1. 目录结构,在执行构建物理结构命令后出现新文件夹:

%使用hrl 因为直接复制过来了,懒得再写一遍student.erl
%=============record=====================
-record(student, {
    id,
    name,
    age,
    grade,
    evaluation
}).
-module(student_db).

-include("../include/student.hrl").

%=============export=====================
-export([
    start/0,
    stop/0,
    add_student/1,
    delete_student/1,
    update_student/1,
    find_by_id/1
]).
-export([
    init/1,
    handle_call/3,
    handle_cast/2,
    handle_info/2,
    terminate/2,
    code_change/3
]).

%=============export functions=====================
start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

stop() -> gen_server:call(?MODULE, stop).

add_student(Student) -> gen_server:call(?MODULE, {add_student, Student}).

delete_student(Id) -> gen_server:call(?MODULE, {delete_student, Id}).

update_student(Student) -> gen_server:call(?MODULE, {update_student, Student}).

find_by_id(Id) -> gen_server:call(?MODULE, {find_by_id, Id}).

init([]) ->
    mnesia:create_schema([node()]),
    % 启动 Mnesia
    mnesia:start(),
    % 创建表
    mnesia:create_table(student, [
        {attributes, record_info(fields, student)},
        {disc_copies, [node()]},
        {type, set}
    ]),
    % 初始化数据
    InitialStudents = [
        #student{id = 1, name = "张三", age = 15, grade = "一班", evaluation = excellent},
        #student{id = 2, name = "李四", age = 16, grade = "二班", evaluation = great},
        #student{id = 3, name = "王五", age = 15, grade = "一班", evaluation = great},
        #student{id = 4, name = "赵六", age = 16, grade = "二班", evaluation = normal},
        #student{id = 5, name = "孙七", age = 15, grade = "一班", evaluation = normal}
    ],
    % 写入初始数据
    mnesia:transaction(fun() ->
        [mnesia:write(Student) || Student <- InitialStudents]
    end),
    {ok, []}.

handle_call({add_student, Student}, _From, State) ->
    Result = mnesia:transaction(fun() ->
        case mnesia:read({student, Student#student.id}) of
            [] -> mnesia:write(Student);
            [_] -> {error, "id_exist"}
        end
    end),
    case Result of
        {atomic, ok} -> {reply, {ok, "add_success"}, State};
        {atomic, {error, Reason}} -> {reply, {error, Reason}, State};
        {aborted, Reason} -> {reply, {error, Reason}, State}
    end;
handle_call({delete_student, Id}, _From, State) ->
    Result = mnesia:transaction(fun() ->
        case mnesia:read({student, Id}) of
            [] -> {error, "student_not_exist"};
            [_] -> mnesia:delete({student, Id})
        end
    end),
    case Result of
        {atomic, ok} -> {reply, {ok, "delete_success"}, State};
        {atomic, {error, Reason}} -> {reply, {error, Reason}, State};
        {aborted, Reason} -> {reply, {error, Reason}, State}
    end;
handle_call({update_student, Student}, _From, State) ->
    Result = mnesia:transaction(fun() ->
        case mnesia:read({student, Student#student.id}) of
            [] -> {error, "student_not_exist"};
            [_] -> mnesia:write(Student)
        end
    end),
    case Result of
        {atomic, ok} -> {reply, {ok, "update_success"}, State};
        {atomic, {error, Reason}} -> {reply, {error, Reason}, State};
        {aborted, Reason} -> {reply, {error, Reason}, State}
    end;
handle_call({find_by_id, Id}, _From, State) ->
    Result = mnesia:transaction(fun() ->
        case mnesia:read({student, Id}) of
            [] -> {error, "student_not_exist"};
            [Student] -> {ok, Student}
        end
    end),
    case Result of
        {atomic, {ok, Student}} -> {reply, {ok, Student}, State};
        {atomic, {error, Reason}} -> {reply, {error, Reason}, State};
        {aborted, Reason} -> {reply, {error, Reason}, State}
    end;
handle_call(stop, _From, State) ->
    {stop, normal, ok, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

-module(student_test).

-include("../include/student.hrl").

-export([
    start/0,
    stop/0,
    tests/0,
    find_by_id/0,
    add_student/0,
    update_student/0,
    delete_student/0
]).

start() ->
    student_db:start().

stop() -> student_db:stop().

tests() ->
    io:format("~ninto start:~n"),
    start(),
    io:format("~ninto find_by_id:~n"),
    find_by_id(),
    io:format("~ninto add_student:~n"),
    add_student(),
    io:format("~ninto update_student:~n"),
    update_student(),
    io:format("~ninto delete_student:~n"),
    delete_student(),
    io:format("~ninto stop:~n"),
    stop().

find_by_id() ->
    case student_db:find_by_id(1) of
        {ok, Student} ->
            io:format("find_by_id ok ~p: ~p~n", [1, Student]);
        {error, Reason} ->
            io:format("find_by_id error ~p: ~p~n", [1, Reason])
    end,

    case student_db:find_by_id(100) of
        {ok, Student2} ->
            io:format("find_by_id ok ~p: ~p~n", [100, Student2]);
        {error, Reason2} ->
            io:format("find_by_id error ~p: ~p~n", [100, Reason2])
    end.

add_student() ->
    Student = #student{
        id = 6,
        name = "小明",
        age = 16,
        grade = "三班",
        evaluation = great
    },
    case student_db:add_student(Student) of
        {ok, Msg} ->
            io:format("add_student ok ~p: ~p~n", [Student, Msg]);
        {error, Reason} ->
            io:format("add_student error ~p: ~p~n", [Student, Reason])
    end,

    Student1 = #student{
        id = 1,
        name = "第二个张三",
        age = 15,
        grade = "一班",
        evaluation = normal
    },
    case student_db:add_student(Student1) of
        {ok, Msg2} ->
            io:format("add_student error ~p: ~p~n", [Student1, Msg2]);
        {error, Reason2} ->
            io:format("add_student ok ~p: ~p~n", [Student1, Reason2])
    end.

update_student() ->
    UpdatedStudent = #student{
        id = 6,
        name = "小明",
        age = 17,
        grade = "三班",
        evaluation = excellent
    },
    case student_db:update_student(UpdatedStudent) of
        {ok, Msg} ->
            io:format("update_student ok ~p: ~p~n", [UpdatedStudent, Msg]);
        {error, Reason} ->
            io:format("update_student error ~p: ~p~n", [UpdatedStudent, Reason])
    end,

    NonExistStudent = #student{
        id = 100,
        name = "不存在的学生",
        age = 15,
        grade = "四班",
        evaluation = normal
    },
    case student_db:update_student(NonExistStudent) of
        {ok, Msg2} ->
            io:format("update_student error ~p: ~p~n", [NonExistStudent, Msg2]);
        {error, Reason2} ->
            io:format("update_student ok ~p: ~p~n", [NonExistStudent, Reason2])
    end.

delete_student() ->
    case student_db:delete_student(6) of
        {ok, Msg} ->
            io:format("delete_student ok ~p: ~p~n", [6, Msg]);
        {error, Reason} ->
            io:format("delete_student error ~p: ~p~n", [6, Reason])
    end,

    case student_db:delete_student(100) of
        {ok, Msg2} ->
            io:format("delete_student error ~p: ~p~n", [100, Msg2]);
        {error, Reason2} ->
            io:format("delete_student ok ~p: ~p~n", [100, Reason2])
    end,

    case student_db:find_by_id(6) of
        {ok, Student} ->
            io:format("find_by_id ok ~p: ~p~n", [6, Student]);
        {error, Reason3} ->
            io:format("find_by_id error ~p: ~p~n", [6, Reason3])
    end.

主要修改:

  1. 把数据从 ets存储变成mnesia数据库存储
  2. State不用存ets表名

其他部分无变化。

posted on 2025-05-13 23:41  依只  阅读(18)  评论(0)    收藏  举报

导航