MYSQL使用三部曲(二)--MYSQL语句、索引、视图、存储过程、触发器
一、初识数据库
1. 概念
数据库
按照数据结构来组织、存储和管理数据的仓库;是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合
OLTP
OLTP(online transaction processing)翻译为联机事务处理;主要对数据库增删改查;
OLTP主要用来记录某类业务事件的发生;数据会以增删改的方式在数据库中进行数据的更新处理 操作,要求实时性高、稳定性强、确保数据及时更新成功;
OLAP
OLAP(On-Line Analytical Processing)翻译为联机分析处理;主要对数据库查询;
当数据积累到一定的程度,我们需要对过去发生的事情做一个总结分析时,就需要把过去一段时间 内产生的数据拿出来进行统计分析,从中获取我们想要的信息,为公司做决策提供支持,这时候就 是在做OLAP了;
SQL命令
结构化查询语言(Structured Query Language)简称SQL,是一种特殊目的的编程语言,是一种数 据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。SQL是关系数据 库系统的标准语言。
关系型数据库包括:MySQL, SQL Server, Oracle, Sybase, postgreSQL 以及 MS Access等;
SQL命令包括:DQL、DML、DDL、DCL以及TCL;
DQL
Data Query Language-数据查询语句;
select:从一个或者多个表中检索特定的记录
DML
Data Manipulate Language-数据操作语言;
insert:插入记录
update:更新记录
delete:删除记录
DDL
Data Define Language-数据定义语言
create:创建一个新的表、表的视图、或者在数据库中的对象
alter:修改现有的数据库对象,例如修改表的属性或者字段
drop:删除表、数据库对象或者视图
DCL
Data Control Language-数据控制语言
grant:授予用户权限
revoke:收回用户权限
TCL
Transaction Control Language-事务控制语言
commit:事务提交
rollback:事务回滚
数据库术语
数据库:数据库是一些关联表的集合
数据表:表是数据的矩阵
列:一列包含相同类型的数据
行:或者称为记录,是一组相关的数据
主键:主键是唯一的;一个数据表只能包含一个主键
外键:外键用来关联两个表,来保证参照完整性;MyISAM存储引擎本身并不支持外键,只能起到注释作用;而InnoDB完整支持外键
复合键:或称组合键,将多个列作为一个索引键
索引:用于快速访问数据表的数据,索引是对表中的一列或者多列的值进行排序的一种结构
2. MySQL体系结构
MySQL由这几部分组成:连接池组件、管理服务和工具组件、SQL接口组件、查询分析器组件、优化器组件、缓冲组件、插件式存储引擎、物理文件。
连接者
不同语言的代码程序和mysql的交互(SQL交互)
连接池
管理缓冲用户连接、用户名、密码、权限校验、线程处理等需要缓存的需求;
网络处理流程:主程序接收连接,接收连接交由连接池处理
主要处理方式:IO多路复用select+阻塞的IO
MySQL命令处理是并发处理的,且MySQL是操作磁盘数据的重IO操作,所以使用阻塞的IO。
MySQL有最大连接数限制,可以使用下面语句查看
show variables like '%max_connections%';
管理服务和工具组件
系统管理和控制工具,例如备份恢复、MySQL复制、集群等
SQL接口
将SQL语句解析生成相应对象;DML,DDL,存储过程,视图,触发器等
查询解析器
将SQL对象 交由解析器验证和解析,并生成语法树
查询优化器
SQL语句执行前使用查询优化器进行优化
缓冲组件
是一块内存取余,用来你不磁盘速度较慢对数据库性能的影响;在数据库进行读页操作,首先将从磁盘读到的页存放在缓存池重,下一次再读取相同的页时,首先判断该页是否在缓冲池中,若在缓冲池命中,直接读取;否则读取磁盘中的页,说明该页被LRU淘汰了;缓冲池中LRU采用最近最少使用算法来进行管理;
缓冲池缓存的数据类型有:索引页、数据页、以及与存储引擎缓存相关的数据(比如InnoDB引擎:undo页、插入缓冲、自适应hash索引、InnoDB相关锁信息、数据字典信息等)
插件式存储引擎
引擎是表级的,及每个不同的表可以指定不同的引擎。比较常用的引擎有MyISAM和InnoDB。MyISAM使用的是表锁,InnoDB使用行锁。
3. 数据库设计三范式
为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这 种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库, 必须满足一定的范式。
范式一
确保每列保持原子性;数据库表中的所有字段都是不可分割的原子值
范式二
确保表中的每列都和主键相关,而不能只与主键的某一部分相关(组合索引)
范式三
确保每列都和主键直接相关,而不是间接相关;减少数据冗余
反范式
范式可以避免数据冗余,减少数据库的空间,减小维护数据完整性的麻烦;但是采用数据库范式化 设计,可能导致数据库业务涉及的表变多,并且造成更多的联表查询,将导致整个系统的性能降 低;因此处于性能考虑,可能需要进行反范式设计
二、SQL语句简述
1. 数据类型
具体数据类型可参考:https://www.runoob.com/mysql/mysql-data-types.html
例:定义无符号整数类型 INT UNSIGNED
数据类型 INT(11),表示数据类型为INT,括号内的11只有与关键字ZEROFILL配合使用才有效果,表示最小字符显示宽度
2. CRUD
执行过程
创建数据库
CREATE DATABASE `数据库名` DEFAULT CHARACTER SET utf8;
删除数据库
DROP DATABASE `数据库名`;
选择数据库
USE `数据库名`;
创建表
CREATE TABLE `table_name` (column_name column_type); CREATE TABLE IF NOT EXISTS `0voice_tbl` ( `id` INT UNSIGNED AUTO_INCREMENT COMMENT '编号', `course` VARCHAR(100) NOT NULL COMMENT '课程', `teacher` VARCHAR(40) NOT NULL COMMENT '讲师', `price` DECIMAL(8,2) NOT NULL COMMENT '价格', PRIMARY KEY ( `id` ) )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT = '课程表';
【】AUTO_INCREMENT是表示该字段的值自增,https://www.cnblogs.com/unrealCat/p/16042181.html
【】COMMENT是对该字段的解释
【】PRIMARY KEY是指定主键
删除表
DROP TABLE IF EXISTS `table_name`;
清空数据库
TRUNCATE TABLE `table_name`; DELETE TABLE `table_name`;
【】DELETE的无条件语句和TRUNCATE都可以清空数据库,区别是TRUNCATE会清除AUTO_INCREMENT的值,使从1开始自增。
增
INSERT INTO `table_name`[(`field1`, `field2`, ..., `fieldn`)] VALUES (value1,value2, ..., valuen);
INSERT INTO `0voice_tbl` (`course`, `teacher`, `price`) VALUES ('C/C++Linux服务器开发/高级架构师', 'Mark', 7580.0);
删
DELETE FROM `table_name` [WHERE Clause]; DELETE FROM `0voice_tbl` WHERE id = 3;
【】当使用safe update时,对于delete和update会有一些安全限制,可以通过set global sql_safe_updates=off 来设置(不建议)
改
UPDATE table_name SET field1=new_value1, field2=new_value2 [, fieldn=new_valuen] UPDATE `0voice_tbl` SET `teacher` = 'Mark' WHERE id = 2; -- 累加 UPDATE `0Voice_tbl` set `age` = `age` + 1 WHERE id = 2;
查
SELECT field1, field2,...fieldN FROM table_name [WHERE Clause]
3. 高级查询
查询数据准备:https://www.cnblogs.com/unrealCat/p/16042252.html
基础查询
-- 全部查询 SELECT * FROM student; -- 只查询部分字段 SELECT `sname`, `class_id` FROM student; -- 别名 列名 不要用关键字 SELECT `sname` AS '姓名' , `class_id` AS '班级ID' FROM student; -- 把查询出来的结果的重复记录去掉 SELECT distinct `class_id` FROM student;
【】distinct可以去除重复字段的查询结果,一般用来查询不重复记录的条数
条件查询
-- 查询姓名为 邓洋洋 的学生信息 SELECT * FROM `student` WHERE `name` = '邓洋洋'; -- 查询性别为 男,并且班级为 2 的学生信息 SELECT * FROM `student` WHERE `gender`="男" AND `class_id`=2;
范围查询
-- 查询班级id 1 到 3 的学生的信息 SELECT * FROM `student` WHERE `class_id` BETWEEN 1 AND 3;
判空查询
SELECT * FROM `student` WHERE `class_id` IS NOT NULL; #判断不为空 SELECT * FROM `student` WHERE `class_id` IS NULL; #判断为空 SELECT * FROM `student` WHERE `gender` <> ''; #判断不为空字符串 SELECT * FROM `student` WHERE `gender` = ''; #判断为空字符串
【】InnoDB的索引的B+树,使用 is null判断会造成索引失效
模糊查询
-- 使用 like关键字,"%"代表任意数量的字符,”_”代表占位符 -- 查询名字为 谢 开头的学生的信息 SELECT * FROM `teacher` WHERE `tname` LIKE '谢%'; -- 查询姓名里第二个字为 小 的学生的信息 SELECT * FROM `teacher` WHERE `tname` LIKE '_小%';
【】更多模糊查询可以看正则表达式
分页查询
-- 分页查询主要用于查看第N条 到 第M条的信息,通常和排序查询一起使用 -- 使用limit关键字,第一个参数表示从条记录开始显示,第二个参数表示要显示的数目。表中默认第一条记录的参数为0。 -- 查询第二条到第三条内容 SELECT * FROM `student` LIMIT 1,2;
查询后排序
-- 关键字:order by field, asc:升序, desc:降序 SELECT * FROM `score` ORDER BY `num` ASC; -- 按照多个字段排序 SELECT * FROM `score` ORDER BY `course_id` DESC, `num` DESC;
聚合查询
SELECT sum(`num`) FROM `score`; SELECT avg(`num`) FROM `score`; SELECT max(`num`) FROM `score`; SELECT min(`num`) FROM `score`; SELECT count(`num`) FROM `score`;
分组查询
-- 分组加group_concat SELECT `gender`, group_concat(`age`) as ages FROM `student` GROUP BY `gender`; -- 可以把查询出来的结果根据某个条件来分组显示 SELECT `gender` FROM `student` GROUP BY `gender`; -- 分组加聚合 SELECT `gender`, count(*) as num FROM `student` GROUP BY `gender`; -- 分组加条件 SELECT `gender`, count(*) as num FROM `student` GROUP BY `gender` HAVING num> 6;
【】group_concat()也是聚合函数,用于将多行合并成一行,语法:
- group_concat( [distinct] `要合并的字段` [order by `排序字段` asc/desc ] [separator '分隔符'] )
【】跟group by配合的条件语句是having
3.2 联表查询
INNER JOIN
只取两张表有对应关系的记录
SELECT cid FROM `course` INNER JOIN `teacher` ON course.teacher_id = teacher.tid;
【】联表的条件的用on
LEFT JOIN
在内连接的基础上保留坐标没有对应关系的记录
SELECT course.cid FROM `course` LEFT JOIN `teacher` ON course.teacher_id = teacher.tid;
RIGHT JOIN
在内连接的基础上保留右表没有对应关系的记录
SELECT course.cid FROM `course` RIGHT JOIN `teacher` ON course.teacher_id = teacher.tid;·
3.3 子查询/合并查询
子查询--单行子查询
select * from course where teacher_id =
(select tid from teacher where tname = '谢小二老师')
子查询--多行子查询
多行子查询即返回多行记录的子查询
IN关键字:运算符可以检测结果集中是否存在某个特定的值,如果检测成功就执行外部的查询
EXISTS关键字:内层查询语句不返回查询的记录。而是返回一个真假值。如果内层查询语句查询 到满足条件的记录,就返回一个真值(true),否则,将返回一个假值(false)。当返回的值为 true时,外层查询语句将进行查询;当返回的为false时,外层查询语句不进行查询或者查询不出 任何记录。
ALL关键字:表示满足所有条件。使用ALL关键字时,只有满足内层查询语句返回的所有结果,才 可以执行外层查询语句。ALL运算符必须以比较运算符开头。
ANY关键字:允许创建一个表达式,对子查询的返回值列表,进行比较,只要满足内层子查询中 的,任意一个比较条件,就返回一个结果作为外层查询条件。
在FROM子句中使用子查询:子查询出现在from子句中,这种情况下将子查询当做一个临时表使 用。
select * from student where class_id in (select cid from course where teacher_id = 2); select * from student where exists (select cid from course where cid = 5); SELECT student_id FROM score WHERE
num < ALL(SELECT num FROM score WHERE student_id BETWEEN 1 AND 3) SELECT student_id, sname FROM (SELECT * FROM score WHERE course_id = 1 OR course_id = 2) AS A LEFT JOIN student ON A.student_id = student.sid;
3.4 正则表达式
MySQL中使用 REGEXP 操作符来进行正则表达式匹配。
4. 视图
定义
视图(view)是一种虚拟存在的表,是一个逻辑表,本身并不包含数据。其内容由查询定义。
基表:用来创建视图的表叫做基表;
通过视图,可以展现基表的部分数据;
视图数据来自定义视图的查询中使用的表,使用视图动态生成;
优点
简单:使用视图的用户完全不需要关心后面对应的表的结构、关联条件和筛选条件,对用户来说已经是过滤好的复合条件的结果集。
安全:使用视图的用户只能访问他们被允许查询的结果集,对表的权限管理并不能限制到某个行某个列,但是通过视图可以简单的实现
数据独立:一旦视图的结构确定了,可以屏蔽表结构变化对用户的影响,源表增加列对视图没有影响;源表修改列名,则可以通过修改视图来解决,不会造成对访问者的影响。
语法
CREATE VIEW <视图名> AS <SELECT语句>
案例
-- 创建视图 -- 查询“c++高级”课程比“音视频”课程成绩高的所有学生的学号; CREATE VIEW view_test1 AS SELECT A.student_id FROM ( SELECT student_id, num FROM score WHERE course_id = 1 ) AS A LEFT JOIN ( SELECT student_id, num FROM score WHERE course_id = 2 ) AS B ON A.student_id = B.student_id WHERE A.num > IF (isnull(B.num), 0, B.num);
【】IF(condition,value_if_true,value_if_false)
【】isnull(expr),如果expr是null,isnull()函数返回true
【】IFNULL(expression,alt_value),如果expression不会null,返回expression,否则返回alt_value
作用
- 可复用,减少重复语句书写;类似程序中函数的作用
- 重构利器
- 逻辑更清晰,屏蔽查询细节,关注数据返回
- 权限控制,某些表对用户屏蔽,但是可以给该用户通过视图来对该表操作
拓展
物化视图和普通视图的区别
5. 存储过程
定义
SQL语句需要先编译然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。
存储过程是可编程的函数,在数据库中创建并保存,可以由SQL语句和控制结构组成。当想要在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过程是非常有用的。数据库中的存储过程可以看做是对编程中面向对象方法的模拟,它允许控制数据的访问方式。
特点
- 能完成较复杂的判断和运算
- 可编程性强,灵活
- SQL编程的代码可重复使用
- 执行的速度相对快一些
- 减少网络之间的数据传输,节省开销
语法
CREATE PROCEDURE 过程名
([[IN|OUT|INOUT] 参数名 数据类型
[,[IN|OUT|INOUT] 参数名 数据类型…]]) [特性 ...] 过程体
存储过程根据想要可能会有有输入、输出、输入输出参数,如果有多个参数要用“,”分隔开。
MySQL存储过程的参数用在存储过程中的定义,共有三种参数类型,IN,OUT,INOUT,分别是传入参数、传出参数、传入传出参数。
过程体的开始与结束使用BEGIN与END进行标识
案例
DELIMITER // CREATE PROCEDURE proc_test1() BEGIN SELECT current_time(); SELECT current_date(); END // DELIMITER ; call proc_test1();
【】DELIMITER与存储过程无关,它的作用是修改分隔符。MySQL的默认分隔符是“;”,当MySQL解释器检测到分隔符就会执行SQL语句。但是,有时候,当你输入一大段内容并且中间包含有分号时,你希望能在全部内容输入完成后再执行,这时候就需要修改分隔符。DELIMITER //表示将分隔符设置为“//”。
案例--IN
DELIMITER // CREATE PROCEDURE proc_in_param (IN p_in INT) BEGIN SELECT p_in ; SET p_in = 2 ; SELECT p_in ; END ;// DELIMITER ; -- 调用 SET @p_in = 1; CALL proc_in_param (@p_in); -- p_in虽然在存储过程中被修改,但并不影响@p_id的值 SELECT @p_in; #=1
【】@是用户自定义变量,@@是系统变量
案例--OUT
DELIMITER // CREATE PROCEDURE proc_out_param(OUT p_out int) BEGIN SELECT p_out; SET p_out=2; SELECT p_out; END; // DELIMITER ; -- 调用 SET @p_out=1; CALL proc_out_param(@p_out); SELECT @p_out; -- 2
【】--也是MySQL的一种注释方式,注意,在--后要加空格
案例--INOUT
DELIMITER // CREATE PROCEDURE proc_inout_param(INOUT p_inout int) BEGIN SELECT p_inout; SET p_inout=2; SELECT p_inout; END; // DELIMITER ; #调用 SET @p_inout=1; CALL proc_inout_param(@p_inout) ; SELECT @p_inout; -- 2
6. 流程控制
流程控制语句只能用在存储过程中。
IF
IF condition THEN ... ELSEIF condition THEN ... ELSE ... END IF;
CASE
CASE value WHEN value THEN ... WHEN value THEN ... ELSE ... END CASE;
WHILE
WHILE condition DO ... END WHILE;
LEAVE
-- 相当于break LEAVE label; -- LEAVE语句退出循环或程序块,只能和BEGIN ... END,LOOP,REPEAT,WHILE语句配合使用
ITERATE
-- 相当于continue ITERATE label;
LOOP
-- 相当于while(true){...} LOOP ... END LOOP -- 可以通过LEAVE语句退出循环
REPEAT
-- 相当于do...while(condition) REPEAT ... UNTIL condition END REPEAT;
7. 游标
游标是针对行操作的,对从数据库中select查询得到的结果集的每一行可以进行分开的独立的相同或不相同的操作。
对于取出多行数据集,需要针对每行操作;可以使用游标;游标常用于存储过程、函数、触发器、事件;
游标相当于行迭代器
定义游标
DECLARE cursor_name CURSOR FOR select_statement #select_statement这可以是你查询出来的任意集合
打开游标
OPEN cursor_name;
取游标数据
FETCH cursor_name INTO var_name[,var_name,.......]
关闭游标
CLOSE cursor_name;
释放游标
DEALLOCATE cursor_name;
设置游标结束标志
DECLARE done INT DEFAULT 0; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; #当游标结束时 done 被标记1
【】在使用游标过程中,可能会用到一些接收游标值的变量,我们需要定义他们
-- 创建 用于接收游标值的变量 DECLARE p_id,p_age,p_total INT ; DECLARE p_sex TINYINT ; -- 注意:接收游标值为中文时,需要给变量 指定字符集utf8 DECLARE p_name VARCHAR (32) CHARACTER SET utf8 ; -- 接收游标结束标志的变量 DECLARE done INT DEFAULT 0 ;
案例
DELIMITER // CREATE PROCEDURE proc_while( IN age_in INT, OUT total_out INT ) BRGIN -- 创建用于接收游标值的变量 DECLARE p_id,p_age,p_total INT; DECLARE p_sex TINYINT; DECLARE p_name VARCHAR(32) CHARACTER SET utf8; DECLARE done INT DEFAULT 0; DECLARE cur_teacher CURSOR FOR SELECT teacher_id, teacher_name, teacher_sex, teacher_age FROM teacher WHERE teacher_age > age_in; -- 指定游标循环结束时的返回值 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; -- 打开游标 OPEN cur_teacher; -- 初始化变量 SET p_total = 0; -- while循环 WHILE done != 1 DO FETCH cur_teacher INTO p_id,p_name,p_sex,p_age; IF done != 1 THEN SET p_total = p_total+1; #游标读取到空行,才会设置结束标志 END IF; END WHILE; -- 关闭游标 CLOSE cur_teacher; -- 将累计结果复制给输出参数 SET total_out = p_total; END // DELIMITER ; -- 调用 SET @p_age = 20; CALL proc_while(@p_age, @total); SELECT @total;
8. 触发器
触发器是行级触发,即一个事务中如果有多个操作会触发触发器,则会一条一条地触发触发器。慎用触发器。
(触发器表示,发生DML时触发,不具备事务性)
定义
触发器(trigger)是MySQL提供给程序员和数据分析员来保证数据完整性的一种方法,它是与表事件相关的特殊的存储过程,它的执行不是有程序调用的,也不是手工启动,而是由事件来触发,比如当对一个表进行DML操作(insert,delete,update)时就会激活它执行。
四要素
监视对象:table
监视事件:insert、update、delete
触发时间:before、after
触发事件:insert、update、delete
语法
CREATE TRIGGER trigger_name trigger_time trigger_event ON table_name FOR EACH ROW [trigger_order] trigger_body -- 此处谢执行语句 -- trigger_name:定义的触发器名 -- trigger_time:{BEFORE | AFTER} -- trigger_event:{INSERT | UPDATE | DELETE} -- trigger_order:{FOLLOWS | PRECEDES} other_trigger_name # trigger_order提供给程序员设置触发器的触发顺序
NEW和OLD
在INSERT型触发器中,NEW用来表示将要(BEFORE)或已经(AFTER)插入的新数据
在DELETE型触发器中,OLD用来表示将要或已经被删除的原数据
在UPDATE型触发器中,OLD用来表示将要或已经被修改的原数据,NEW用来表示将要或已经修改为的新数据
NEW.columnName (columnName为相应数据表某一列名)
OLD.columnName
案例
CREATE TABLE `work` ( `id` INT PRIMARY KEY auto_increment, `address` VARCHAR (32) ) DEFAULT charset = utf8 ENGINE = INNODB; CREATE TABLE `time` ( `id` INT PRIMARY KEY auto_increment, `time` DATETIME ) DEFAULT charset = utf8 ENGINE = INNODB; CREATE TRIGGER trig_test1 AFTER INSERT ON `work` FOR EACH ROW INSERT INTO `time` VALUES(NULL,NOW());
【】now()函数返回现在的系统时间
9. 权限管理
创建用户
CREATE USER username@host IDENTIFIED BY yourpassword;
【】host: 指定该用户在哪个主机上可以登陆,如果是本地用户可用 localhost ,如果想让该用户可 以从任意远程主机登陆,可以使用通配符 % ;
授权
GRANT privileges ON databasename.tablename TO 'username'@'host' WITH GRANT OPTION;
【】privileges :用户的操作权限,如 SELECT , INSERT , UPDATE 等,如果要授予所的权限则使用ALL;
【】databasename.tablename :如果是 *.* 表示任意数据库以及任意表;
【】WITH GRANT OPTION :这个选项表示该用户可以将自己拥有的权限授权给别人。注意:经常有人在 创建操作用户的时候不指定 WITH GRANT OPTION 选项导致后来该用户不能使用 GRANT 命令创建 用户或者给其它用户授权。 如果不想这个用户有这个 grant 的权限,则不要加该 WITH GRANT OPTION 选项;
对视图授权
GRANT select, SHOW VIEW ON `databasename`.`tablename` to 'username'@'host';
刷新权限
-- 修改权限后需要刷新权限 FLUSH PRIVILEGES;
10. 远程连接
修改文件配置
注释mysqld.cnf中的bind-address,修改mysql.user表,然后重启mysql
-- mysqld.cnf #bind-address=127.0.0.1
-- 修改user表 select `user`, `host` from `mysql`.`user`; update user set host='%' where user='root';
一般不推荐直接修改root用户,建议新建一个可远程访问的用户,并赋予需要的权限。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现