Erlang06_ OTP Mnesia_Erlang数据库
总篇:15
编辑于 2025/5/13 22:30
截稿于: 2025/5/13 23:30
简介
OTP中的分布式数据库组件,内部基于ets和dets,具备DBMS的一切操作功能。
特性:
- 支持事务,也几乎只在事务操作表数据
- 支持读写锁,锁的范围有四种:单记录(单行),整表,一般只锁单记录
- 支持索引 (加快查询)
- 不支持动态增加新字段/删除字段
- 表记录的第一个字段强制视为主键,不能更改其他字段为主键,所以通常是Id
原语
- ok | {error, Reason} = mnesia:create_schema([node()]):创建数据库物理结构(一个节点只执行一次)
- ok = mnesia:start():启动本节点数据库服务,像Mysqld
- stopped = mnesia:stop() :停止本节点数据库服务

- {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()} % 是否为本地内容
]
- {atomic, ok} | {aborted, Reason} = mnesia:delete_table(Name):删除表
- {atomic, ok} = mnesia:clear_table(user):清空表
- {atomic, ok} | {aborted, Reason} = mnesia:add_table_index(Tab, FieldName):动态添加索引
- {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
- 目录结构,在执行构建物理结构命令后出现新文件夹:

%使用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.

主要修改:
- 把数据从 ets存储变成mnesia数据库存储
- State不用存ets表名
其他部分无变化。
浙公网安备 33010602011771号