dream

一个菜鸟程序员的成长历程

0%

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

MySQL零基础教程

本教程为零基础教程,零基础小白也可以直接学习,有基础的可以跳到后面的原理篇学习。
基础概念和SQL已经更新完成。

接下来是应用篇,应用篇的内容大致如下图所示。

应用学习

高阶应用实战

除了常用的增删改查以外,MySQL还有很多的功能。比如CTE窗口函数这种。再比如一些特性比如视图触发器等。

这次我们就来看一下对于这些如何使用。

有些功能用好了可以完成很多实际业务需求的。

CTE(common table expressions)

在MySQL中,CTE(Common Table Expressions,公共表表达式)是一种临时的结果集,可以在查询中被引用。CTE通常用于简化复杂的查询,使查询更易于理解和维护。CTE在MySQL 8.0及更高版本中得到了支持。

CTE的主要特点

临时结果集:

  • CTE是一个临时的结果集,可以在查询中被引用。它类似于子查询,但更易于阅读和维护。
    简化复杂查询:
  • CTE可以将复杂的查询分解为多个简单的部分,使查询更易于理解和维护。
    可重用性:
  • CTE可以被多次引用,减少了代码重复,提高了开发效率。
    递归查询:
  • CTE支持递归查询,可以用于处理层次结构或递归数据。

使用CTE实现获取每个课程中分数最高的学生信息。

通过WITH语句来声明一个临时表。表名cteSource,表的内容就是最的sid,通过SELECT MAX(sid) FROM enrolled查询出来的结果。字段名叫maxId

然后在查询语句里面就可以连接cteSource表,然后通过sid = cteSource.maxId 来获取到sid最大的用户信息。

1
2
3
4
5
WITH cteSource (maxId) AS (
SELECT MAX(sid) FROM enrolled
)
SELECT name FROM student, cteSource
WHERE student.sid = cteSource.maxId

还有一些其他的用法,比如:

1
2
3
4
5
6
7
WITH cte1 (col1) AS (
SELECT 1
),
cte2 (col2) AS (
SELECT 2
)
SELECT * FROM cte1, cte2;

CTE(公共表表达式)使用场景

  1. 递归查询(如树形结构)

CTE最常见的场景之一是递归查询,比如组织架构、分类目录、菜单结构等树状数据。例如,查询某个员工的所有下属员工。

1
2
3
4
5
6
7
8
9
10
11
12
WITH RECURSIVE subordinates AS (
SELECT id, name, manager_id
FROM employees
WHERE manager_id IS NULL -- 顶层经理

UNION ALL

SELECT e.id, e.name, e.manager_id
FROM employees e
INNER JOIN subordinates s ON e.manager_id = s.id
)
SELECT * FROM subordinates;
  1. 简化复杂查询逻辑,提高可读性

当查询逻辑复杂时,可以将中间结果用CTE分段处理,让SQL更易读。例如,先筛选出某类用户,再基于这些用户做进一步统计。

1
2
3
4
5
6
WITH active_users AS (
SELECT user_id
FROM user_logins
WHERE login_date > CURRENT_DATE - INTERVAL '30 days'
)
SELECT COUNT(*) FROM orders WHERE user_id IN (SELECT user_id FROM active_users);
  1. 多次引用中间结果,避免重复计算

如果某个子查询需要被多次引用,CTE可以定义一次,后续多次调用,避免重复书写和计算,提高效率。

1
2
3
4
5
6
7
WITH sales_summary AS (
SELECT product_id, SUM(amount) AS total_sales
FROM sales
GROUP BY product_id
)
SELECT * FROM sales_summary WHERE total_sales > 1000;
-- 也可以在后续多个查询中引用 sales_summary
  1. 分步处理数据,逐步细化结果

例如,先计算每个部门的总销售额,再筛选出销售额最高的部门。

1
2
3
4
5
6
7
8
9
10
11
12
WITH dept_sales AS (
SELECT department_id, SUM(sales) AS total_sales
FROM employees
GROUP BY department_id
),
top_dept AS (
SELECT department_id
FROM dept_sales
ORDER BY total_sales DESC
LIMIT 1
)
SELECT * FROM employees WHERE department_id IN (SELECT department_id FROM top_dept);
  1. 数据去重和排名

CTE可以配合窗口函数实现数据去重、分组排名等需求。

1
2
3
4
5
6
WITH ranked_orders AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS rn
FROM orders
)
SELECT * FROM ranked_orders WHERE rn = 1;

实战案例

比如我之前的业务。有一个公司表,一个公司合同表,一个公司合同申请表。在后台的公司合同列表里面,要展示最新的申请记录和申请状态。

如果没有CTE的话,我们就需要使用子查询来实现。这对于一个列表来说,性能是很低的。

表结构假设:

  • company:公司表,字段如 company_id, company_name 等。
  • company_contract:公司合同表,字段如 contract_id, company_id, contract_no 等。
  • company_contract_application:公司合同申请表,字段如 application_id, contract_id, apply_time, status 等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT
c.company_id,
c.company_name,
cc.contract_id,
cc.contract_no,
cca.application_id,
cca.apply_time,
cca.status
FROM company c
JOIN company_contract cc ON c.company_id = cc.company_id
LEFT JOIN company_contract_application cca
ON cc.contract_id = cca.contract_id
AND cca.apply_time = (
SELECT MAX(apply_time)
FROM company_contract_application
WHERE contract_id = cc.contract_id
);

而有了CTE以后,我们可以通过CTE来实现,这样可以提升性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
WITH latest_applications AS (
SELECT
application_id,
contract_id,
apply_time,
status,
ROW_NUMBER() OVER (PARTITION BY contract_id ORDER BY apply_time DESC) AS rn
FROM company_contract_application
)
SELECT
c.company_id,
c.company_name,
cc.contract_id,
cc.contract_no,
la.application_id,
la.apply_time,
la.status
FROM company c
JOIN company_contract cc ON c.company_id = cc.company_id
LEFT JOIN latest_applications la ON cc.contract_id = la.contract_id AND la.rn = 1;

窗口函数

窗口函数(Window Function)是SQL标准中的一种功能强大的工具,它允许在查询中对一组行进行计算,而不会像聚合函数那样消除行的个数。窗口函数在MySQL 8.0及更高版本中得到了支持,它们可以用于计算移动平均值、累积和、排名等复杂的分析任务。

窗口函数的主要特点

行级计算:

  • 窗口函数在每一行上执行计算,同时可以访问同一组中的其他行。
  • 这与聚合函数不同,聚合函数会将多行数据合并为一行。
    分区和排序:
  • 窗口函数可以使用PARTITION BY子句将数据分成多个分区,每个分区独立计算。
  • 可以使用ORDER BY子句在每个分区内对数据进行排序。
    灵活的范围定义:
  • 窗口函数可以定义计算的范围,如当前行的前几行或后几行。
  • 使用ROWS或RANGE子句可以指定计算的范围。
    多种功能:
  • 窗口函数提供了多种功能,如ROW_NUMBER()、RANK()、DENSE_RANK()、NTILE()、SUM()、AVG()、LEAD()、LAG()等。

ROW_NUMBERRANK都需要和OVER一起使用。

  • ROW_NUMBER(): 显示当前行号
  • RANK() : 显示排序后的排名,如果没有排序,都是1
  • OVER()
    • PARTITION BY 进行分组
    • GROUP BY 进行分组
    • ORDER BY 排序

001

002

003

实战:获取每个课程中分数最高的学生信息

下面的SQL,在postgresql中执行成功,mysql8执行报错。

首先查询所有课程信息,并按照课程分组,按照分数排序。

1
2
3
SELECT *,
RANK() OVER (PARTITION BY cid ORDER BY grade ASC)
AS rank FROM enrolled

004

接着搜索上表中分数为1,也就是分数最高的学生。也就是每个课分数最高的学生信息。

1
2
3
4
5
6
SELECT * FROM (
SELECT *,
RANK() OVER (PARTITION BY cid
ORDER BY grade ASC)
AS rank FROM enrolled) AS ranking
WHERE ranking.rank = 1

005

视图

在MySQL中,视图(View)是一种虚拟表,其内容由SQL查询定义。视图并不存储实际的数据,而是根据定义的查询动态生成数据。视图可以简化复杂的SQL操作,提供数据的逻辑抽象,并且可以限制对某些数据的访问,从而增强数据的安全性。

MySQL中的视图是虚拟视图,说白了就是一条SQL语句,当查询视图的时候执行SQL语句而已。

除此之外,还有一个东西叫做物化视图,MySQL并没有实现这个东西,物化视图就是一张真的表,而不是一个SQL语句,因此查询效率更好。

视图的主要特点

虚拟表:

  • 视图是一个虚拟表,其内容由SQL查询定义。视图本身并不存储数据,而是根据定义的查询动态生成数据。

简化复杂查询:

  • 视图可以简化复杂的SQL操作,将复杂的查询逻辑封装起来,使用户可以像查询普通表一样查询视图。

数据抽象:

  • 视图提供数据的逻辑抽象,隐藏了底层表的复杂性,使用户可以更直观地访问数据。

安全性:

  • 视图可以限制对某些数据的访问,增强数据的安全性。通过视图,用户只能访问视图定义的特定数据,而不能访问底层表的全部数据。

更新限制:

  • 视图可以是可更新的,也可以是不可更新的。可更新视图允许用户通过视图插入、更新或删除数据,但需要满足一定的条件。不可更新视图则不允许用户通过视图修改数据。

创建一个视图。下面的语句,创建一个视图,视图名称是sales_employees,内容就是后面的Select语句的结果。当原始表employees变化以后,视图的内容也会跟着变化。

1
2
3
4
CREATE VIEW sales_employees AS
SELECT name, salary
FROM employees
WHERE department = 'Sales';

实战:交易聚合

我之前有一个业务,存在两个表,一个是买单表,代表买方要买一个产品出的价格。一个是卖单表,代表卖方要卖一个产品出的价格。

现在有一个需求是:在C端的一个页面上,要求混合展示买单信息和卖单信息,按照价格从低到高排序

数据库假设:

  • buy: 买单表
  • sell: 卖单表
  • order: 交易视图
1
2
3
4
CREATE VIEW order AS
SELECT *
FROM buy, sell
order by price asc;

触发器

在MySQL中,触发器(Trigger)是一种特殊的存储过程,它在特定的数据库操作(如INSERT、UPDATE、DELETE)发生时自动执行。触发器可以用于实现复杂的业务逻辑,确保数据的完整性和一致性,以及自动维护数据的同步。

触发器的主要特点

自动执行:

  • 触发器在特定的数据库操作发生时自动执行,无需显式调用。这使得触发器可以用于实现自动化的数据处理和维护。

数据完整性:

  • 触发器可以用于确保数据的完整性和一致性。例如,可以在插入或更新数据时自动检查数据的有效性,或者在删除数据时自动清理相关数据。

业务逻辑:

  • 触发器可以用于实现复杂的业务逻辑。例如,可以在插入或更新数据时自动计算某些字段的值,或者在删除数据时自动更新相关表的数据。

数据同步:

  • 触发器可以用于自动维护数据的同步。例如,可以在插入或更新数据时自动更新相关表的数据,或者在删除数据时自动清理相关表的数据。

触发器的类型

  • BEFORE INSERT:
    • 在插入数据之前执行触发器逻辑。
  • AFTER INSERT:
    • 在插入数据之后执行触发器逻辑。
  • BEFORE UPDATE:
    • 在更新数据之前执行触发器逻辑。
  • AFTER UPDATE:
    • 在更新数据之后执行触发器逻辑。
  • BEFORE DELETE:
    • 在删除数据之前执行触发器逻辑。
  • AFTER DELETE:
    • 在删除数据之后执行触发器逻辑。

触发器的限制

性能影响:

  • 触发器的执行会增加数据库操作的开销,可能会影响性能。因此,应谨慎使用触发器,避免在高频操作的表上定义过多的触发器。
    复杂性:
  • 触发器的逻辑可以非常复杂,但过多的复杂逻辑可能导致触发器难以维护和调试。因此,应尽量保持触发器的逻辑简单明了。
    调试困难:
  • 触发器的调试相对困难,因为它们在特定的操作发生时自动执行,难以直接观察和调试。因此,建议在开发和测试阶段充分测试触发器的逻辑。

因此,实际开发中基本不使用触发器。

存储过程

在MySQL中,存储过程(Stored Procedure)是一种预编译的SQL语句集合,它存储在数据库中,可以通过调用其名称并传递参数来执行。存储过程可以包含复杂的逻辑和多个SQL语句,用于完成特定的任务。它们类似于其他编程语言中的函数或方法。

可以把存储过程想成一个函数。只不过是在MySQL中的函数,这个函数可以实现各种功能。可以实现一些复杂的SQL处理,这样可以简化调用。

存储过程的主要特点

预编译:

  • 存储过程在创建时被预编译并存储在数据库中,这使得它们的执行速度比单独的SQL语句更快。
    代码重用:
  • 存储过程可以被多次调用,减少了代码重复,提高了开发效率。
    减少网络流量:
  • 存储过程在服务器端执行,减少了客户端和服务器之间的网络流量,因为只需要发送存储过程的名称和参数,而不是大量的SQL语句。
    安全性:
  • 存储过程可以限制用户对底层数据的直接访问,只允许通过存储过程进行数据操作,从而增强数据的安全性。
    事务管理:
  • 存储过程可以包含事务控制语句,如COMMIT和ROLLBACK,确保数据操作的完整性和一致性。

存储过程的限制

性能影响:

  • 存储过程的执行会增加数据库操作的开销,可能会影响性能。因此,应谨慎使用存储过程,避免在高频操作的表上定义过多的存储过程。
    复杂性:
  • 存储过程的逻辑可以非常复杂,但过多的复杂逻辑可能导致存储过程难以维护和调试。因此,应尽量保持存储过程的逻辑简单明了。
    调试困难:
  • 存储过程的调试相对困难,因为它们在服务器端执行,难以直接观察和调试。因此,建议在开发和测试阶段充分测试存储过程的逻辑。

实战案例

存储过程的实际应用场景有限。

我们之前有一个场景,是需要批量处理数据,有几十行SQL语句。因此将这些SQL封装到了一个存储过程中,这样只需要执行这个存储过程就可以了。

这里给出一个简单的实战案例:

创建一个存储过程get_employee_details,用于根据员工ID获取员工的详细信息:

DELIMITER用来设置结束符,比如正常的句子结束符是句号。代码结束符是分号;

IN代表输入参数,也就是这个函数有一个输入参数emp_id,是int类型。
还有out代表输出参数,用于返回结果。
INOUT代表既可以输入参数也可以是输出参数。

1
2
3
4
5
6
7
8
9
10
DELIMITER //

CREATE PROCEDURE get_employee_details(IN emp_id INT)
BEGIN
SELECT name, department, salary
FROM employees
WHERE id = emp_id;
END //

DELIMITER ;

上面这个存储过程的函数体,就是一段select查询语句。

存储函数

在MySQL中,存储函数(Stored Function)是一种预编译的SQL语句集合,类似于存储过程,但它返回一个值。存储函数可以被SQL语句直接调用,就像调用普通的函数一样。存储函数通常用于封装复杂的逻辑,并在查询中重用这些逻辑。

存储函数同样是一个函数,和上面的存储过程差不多。

存储过程和存储函数的区别

区别类别 存储过程 存储函数
返回值 1. 可以返回多个值,通过OUT或INOUT参数返回。
2. 可执行多个SQL语句,但不直接返回单一值。
1. 必须返回一个单一的值。
2. 可以被SQL语句直接调用,就像普通函数一样。
调用方式 1. 通过CALL语句调用。
2. 可执行复杂逻辑,包括多个SQL语句和事务控制。
1. 可直接在SQL语句中调用,如普通函数。
2. 通常用于封装复杂逻辑,并在查询中重用。

存储函数的主要特点

主要特点 说明
返回值 存储函数必须返回一个值,这使得它们可以被SQL语句直接调用。
代码重用 存储函数可以被多次调用,减少了代码重复,提高了开发效率。
减少网络流量 存储函数在服务器端执行,减少了客户端和服务器之间的网络流量,因为只需要发送函数的名称和参数,而不是大量的SQL语句。
安全性 存储函数可以限制用户对底层数据的直接访问,只允许通过函数进行数据操作,从而增强数据的安全性。
事务管理 存储函数可以包含事务控制语句,如COMMIT和ROLLBACK,确保数据操作的完整性和一致性。

实战案例

创建一个存储函数get_employee_salary,用于根据员工ID获取员工的薪资:

可以看到和上面存储过程的区别,声明了一个返回值,类型是DECIMAL,最后通过return返回了,并且声明了一个变量。参数也没有IN、OUT这种了。

1
2
3
4
5
6
7
8
9
10
11
12
13
DELIMITER //

CREATE FUNCTION get_employee_salary(emp_id INT)
RETURNS DECIMAL(10, 2)
BEGIN
DECLARE emp_salary DECIMAL(10, 2);
SELECT salary INTO emp_salary
FROM employees
WHERE id = emp_id;
RETURN emp_salary;
END //

DELIMITER ;

总结

本次讲解了MySQL的高级功能和实战应用的一些场景。

只有知道了场景才能活学活用。这里面最常用的其实还是CTE和窗口函数。

可以多多练习,希望遇到这些场景的时候,大家能想起来使用这些功能。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

MySQL零基础教程

本教程为零基础教程,零基础小白也可以直接学习,有基础的可以跳到后面的原理篇学习。
基础概念和SQL已经更新完成。

接下来是应用篇,应用篇的内容大致如下图所示。

应用学习

增删改查实战

MySQL是最常用的关系型数据库之一,掌握基本的增、删、改、查(CRUD)操作是学习MySQL的基础。本篇文章将通过详细示例,帮助零基础的读者快速上手MySQL的增、删、改、查操作。无论是新手还是有一定数据库基础的开发者,都能从中受益。

1. MySQL环境准备

在进行任何操作之前,首先需要确保你已经成功安装了MySQL数据库,并能顺利连接到数据库。如果没有安装MySQL,请参考MySQL官方网站或相关教程进行安装。

连接到MySQL

在终端中使用以下命令登录MySQL:

1
mysql -u root -p

输入密码后,成功登录后会进入MySQL命令行界面。

创建数据库

在MySQL中,所有的操作都发生在数据库中,因此我们首先需要创建一个数据库:

1
CREATE DATABASE mydb;

然后进入到mydb数据库:

1
USE mydb;

创建表

在进行增删改查操作之前,先创建一个简单的表。假设我们要管理一个学生信息表,表结构如下:

1
2
3
4
5
6
CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
age INT,
grade VARCHAR(10)
);

表结构说明:

  • id:学生的唯一标识,使用AUTO_INCREMENT自增。
  • name:学生的姓名,VARCHAR(50)表示最多存储50个字符。
  • age:学生的年龄,使用INT表示整数类型。
  • grade:学生的年级,VARCHAR(10)表示最多存储10个字符。

2. 增:插入数据(INSERT)

在MySQL中,使用INSERT INTO语句来插入数据。我们可以插入一条或多条记录。

插入一条记录

插入一条记录到students表:

1
2
3
INSERT INTO students (name, age, grade) 
VALUES ('John Doe', 20, 'Sophomore');

  • INSERT INTO students (name, age, grade):指定插入的数据列。
  • VALUES ('John Doe', 20, 'Sophomore'):插入的数据值,注意顺序要和列名匹配。

插入多条记录

我们也可以一次性插入多条记录:

1
2
3
4
5
6
INSERT INTO students (name, age, grade) 
VALUES
('Alice', 19, 'Freshman'),
('Bob', 21, 'Junior'),
('Charlie', 22, 'Senior');

  • 通过多个VALUES列表插入多条记录,减少了插入的语句数量,提升了效率。

插入数据并返回自动生成的ID

如果表中有AUTO_INCREMENT字段(如id),可以使用LAST_INSERT_ID()函数获取插入后生成的ID:

1
2
3
INSERT INTO students (name, age, grade) 
VALUES ('David', 23, 'Graduate');
SELECT LAST_INSERT_ID();

3. 查:查询数据(SELECT)

MySQL使用SELECT语句从表中查询数据。可以查询全部列,也可以选择特定列,还可以进行条件过滤、排序等。

查询所有记录

查询students表中所有的数据:

1
SELECT * FROM students;
  • *表示查询所有列的数据。

查询特定列

查询nameage列的数据:

1
SELECT name, age FROM students;

使用WHERE条件筛选数据

查询年纪大于20的学生:

1
SELECT * FROM students WHERE age > 20;
  • WHERE用于添加条件,过滤符合条件的记录。

排序查询结果

按年龄升序排列学生:

1
SELECT * FROM students ORDER BY age ASC;
  • ASC表示升序(默认),如果要降序排列,可以使用DESC
1
SELECT * FROM students ORDER BY age DESC;

使用LIMIT限制返回结果数量

如果只想查看前两条记录:

1
SELECT * FROM students LIMIT 2;

模糊查询(LIKE)

使用LIKE进行模糊查询,查找名字中包含“a”的学生:

1
SELECT * FROM students WHERE name LIKE '%a%';
  • %是通配符,表示任意字符。

聚合函数

MySQL提供了多种聚合函数,如COUNT()AVG()SUM()等。

查询学生人数:

1
2
SELECT COUNT(*) FROM students;

查询平均年龄:

1
2
SELECT AVG(age) FROM students;

4. 改:更新数据(UPDATE)

使用UPDATE语句修改表中已有的数据。可以根据条件更新特定记录,也可以批量更新。

更新单条记录

更新id=1的学生姓名为“John Smith”:

1
2
3
4
UPDATE students 
SET name = 'John Smith'
WHERE id = 1;

  • SET用于指定需要更新的列和新的值。
  • WHERE用于指定要更新的记录,确保不修改所有记录。

更新多条记录

更新所有年龄大于20的学生年级为“Graduate”:

1
2
3
4
UPDATE students 
SET grade = 'Graduate'
WHERE age > 20;

更新多个字段

同时更新多个字段:

1
2
3
4
UPDATE students 
SET age = 23, grade = 'Postgraduate'
WHERE id = 2;

5. 删:删除数据(DELETE)

使用DELETE语句删除表中的记录,可以删除特定记录或删除所有记录。

删除单条记录

删除id=1的学生:

1
DELETE FROM students WHERE id = 1;
  • WHERE子句用于指定删除条件,确保只删除满足条件的记录。

删除多条记录

删除所有年龄大于20的学生:

1
DELETE FROM students WHERE age > 20;

删除所有记录

删除students表中的所有记录(注意:表结构不变):

1
DELETE FROM students;

删除表中的记录(TRUNCATE)

TRUNCATE语句与DELETE类似,但其删除的是所有数据并且不记录日志,因此效率更高:

1
TRUNCATE TABLE students;
  • TRUNCATE会立即清空表中的所有数据,并且不能恢复。

删除表(DROP)

如果需要删除整个表,包括表结构:

1
DROP TABLE students;
  • DROP会删除表以及表中所有的数据,无法恢复,因此要小心使用。

总结

在MySQL中,增、删、改、查(CRUD)操作是数据库管理的基础。通过上述操作,我们能够对数据库表中的数据进行增删改查,并且可以通过各种条件、排序和聚合功能来灵活查询数据。

这里,要特别注意更新删除操作。一定要加条件,不然就会更新全表数据,或者删除全表数据。大家有没有误操作过呢?一起分享下。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

MySQL零基础教程

本教程为零基础教程,零基础小白也可以直接学习,有基础的可以跳到后面的原理篇学习。
基础概念和SQL已经更新完成。

接下来是应用篇,应用篇的内容大致如下图所示。

应用学习

索引设计

之前已经讲过表设计了,这次来说说索引设计。

索引设计也是一个比较重要的东西,可谓是重中之重。想要学好,很难。也是面试的常见问题。

在MySQL数据库中,索引是提升查询效率的核心工具。没有索引的查询,就像是在浩瀚的书海中翻书,效率低下。而良好的索引设计,则能帮助我们高效地找到我们需要的信息,避免无谓的资源浪费。因此,理解索引的原理、种类以及如何设计索引是每个开发者必须掌握的基础技能。

索引是什么?

索引是一种数据结构,它通过特定的方式排列数据,以便于快速查询。可以把索引想象成一本书的目录,通过目录可以快速找到你想要的章节,而不用从头到尾翻书。

在MySQL中,索引通常是通过B树或B+树等数据结构实现的,数据库查询引擎可以利用这些结构快速查找数据。

我们先看一下索引都有哪些类型,最常用的当然是b+树索引啦

  • B+树索引:B+树是一种多路自平衡的树结构,所有的叶子节点都在同一层,并且叶子节点通过链表连接。它是MySQL中最常用的索引类型,支持范围查询、精确查找等操作。
  • 哈希索引:哈希索引基于哈希表的原理,通过哈希函数对索引列进行计算,得到一个哈希值,哈希值对应的桶中存储数据的指针。
  • 全文索引:全文索引是专门用于文本检索的索引结构,通常用于TEXT类型的数据。MySQL会将文本字段中的每个单词进行索引,创建倒排索引,以提高文本搜索的效率。
  • 空间索引: 空间索引是用于地理空间数据类型(如POINTLINESTRINGPOLYGON)的索引结构,通常使用R树(或改进版的R+树)来存储和查询空间数据。
  • 倒排索引:倒排索引主要用于文本搜索。它将文档中每个词的出现位置存储在一个列表中,从而支持高效的文本检索。
  • 位图索引:位图索引使用位图(bitmap)来表示数据列中各个值的存在情况。例如,假设某一列有3个不同的值,可以用3个位来表示每个值的出现情况。

这些主要是根据索引结构来划分的。接下来详细看一下各个结构的优缺点,这样我们才能更好的进行选择。

B+树索引(B+ Tree Index)

结构:

  • B+树是一种多路自平衡的树结构,所有的叶子节点都在同一层,并且叶子节点通过链表连接。
  • 它是MySQL中最常用的索引类型,支持范围查询、精确查找等操作。

优点:

  • 高效的范围查询:由于B+树是顺序存储的,支持范围查询,查询某个区间的数据时非常高效。
  • 高效的精确查询:B+树通过逐层查找,可以快速定位到数据行。
  • 支持排序:由于叶子节点按顺序排列,B+树非常适合用于ORDER BYGROUP BY等操作。
  • 空间利用高:每个非叶子节点仅包含键,而不包含数据,可以节省存储空间。

缺点:

  • 性能受页大小影响:B+树的性能与页的大小(每页存储的记录数)密切相关,过小的页会导致频繁的I/O操作,过大的页则会浪费内存。
  • 写入性能较低:对于频繁插入、删除操作的场景,B+树索引的维护成本较高,可能导致性能下降。

应用场景:

  • 最常用于主键索引、普通索引和唯一索引。尤其适用于需要进行范围查询、排序的场景。

特点:

  • b+ tree 删除和插入的复杂度都是O(log n), b 是 balance (平衡),推荐阅读paper: the ubiquitous B-tree
  • b+ tree,保证每个节点都必须是半满的,对于存放在节点中的key数量来说,key数量至少为M/2 - 1个,M为树的高度,key的数量必须小于 M - 1,如果当删除数据以后导致key数量小于M/2 - 1个,就会进行平衡,使他满足M/2 - 1个。

    M/2 - 1 ≤ key数量 ≤ M - 1

  • 如果一个中间节点有k个key,那你就会有k+1个非空孩子节点,也就是k+1个指向下方节点的指针。每个节点的内容是一个指针和一个key
  • 叶子节点之间有连接叶子节点的兄弟指针,这个想法来源于b link tree。每个节点的内容是一个数据和一个key,数据可以是一个record id 也可以是一个 tuple
  • b+ tree 标准填充容量大概是67% - 69%,对于一个大小是8kb的page来说,如果高度为4,大约能记录30 0000个键值对。
  • b+ tree的节点大小,机械硬盘的大小最好在1M,ssd的大小在10KB

b tree 和 b+ tree 的区别

  • b tree的中间节点也可以存数据,所以key是不重复的
  • b+ tree的中间节点没有数据,所有数据都在叶子节点,所以key有可能既存在中间节点也存在叶子节点。会重复
  • b tree的性能在并行处理上更差,因为修改以后需要向上传播也需要向下传播修改,这个时候两边都要增加锁
  • b+ tree的性能更好,因为只修改叶子节点,所以只需要向上传播,只需要增加一个锁

b+ tree 插入

  1. 向下扫描,找到对应的叶子节点
  2. 如果可以插入就直接插入
  3. 如果不可以插入,那么从中间分开,变成两个叶子节点,并将中间的key传递给父节点,插入父节点。
  4. 如果父节点可以插入就直接插入并分出一个指针指向新的叶子节点
  5. 如果父节点不可以插入重复上述操作3

b+ tree 删除

  1. 向下扫描,找到对应的叶子节点,这个时候就会增加latch,因为不知道需不需要合并,操作以后才会释放
  2. 如果可以删除就直接删除
  3. 如果删除后导致key数量 < M/2 - 1,那么就会出发合并,因为不满足key数量啦
  4. 进行合并的时候删除这个key,然后先查看左右的兄弟节点,是否能直接把数据插入过来,如果可以的话就掠夺一个key过来,然后向上传播
  5. 如果不能掠夺,那么就合并到兄弟节点,然后向上传播。

b+ tree的查找

  • 对于<a,b,c>,查找a=5 and b=3也是可以走索引的,但是hash索引就不行,有些数据库还支持b=3的搜索走索引,比如oracle和sql server

推荐书籍 Modern B-Tree Techniques

哈希索引(Hash Index)

结构:

  • 哈希索引基于哈希表的原理,通过哈希函数对索引列进行计算,得到一个哈希值,哈希值对应的桶中存储数据的指针。

优点:

  • 非常高效的等值查询:哈希索引的查找时间复杂度是O(1),因此对于精确查找(=)非常高效。
  • 查询速度快:对于大量的数据,哈希索引可以快速找到对应的记录。

缺点:

  • 不支持范围查询:由于哈希是通过哈希值计算的,哈希索引不支持范围查询(BETWEEN、>、<等)。
  • 空间浪费:哈希表可能会出现哈希冲突,造成空间浪费和性能下降。
  • 不支持排序:哈希索引无法用于ORDER BY、GROUP BY等排序操作。

应用场景:

  • 适合用于精确查找,但不适合范围查询和排序操作。MySQL中内存表(MEMORY存储引擎)默认使用哈希索引。

哈希索引的应用场景有限,因此实际工作中使用较少。

哈希分为静态hash实现和动态hash实现。

静态hash

  • liner probe hashing
    • 如果要插入的位置有值了,就往下扫描,扫描到空的位置插入
    • 删除的时候可以增加一个墓碑标记,这样就知道这里是有数据的不是空,查找的时候就会继续往下扫描而不会是没找到
    • 删除的时候还可以把后面的数据往前移动,但是这样有的数据就不再原来的位置了,就找不到了。因为只会往下扫描不会往上扫描
  • robin hood hashing
    • 记录距离数,表示插入的位置和应该插入的位置的距离。从0开始。
    • 插入的时候判断距离数,进行劫富济贫,如果你向下扫描到距离数为3的地方插入,而在距离数为2的地方的数据x,x的距离数比你小,比如是0,1.那么你就占据这里,你插入距离数为2的地方,而将x插入你下面,x的距离数会+1.
    • 从整体来看,这个方法牺牲了插入的效率,将数据的距离数变得更加平均
  • cuckoo hashing
    • 该方法使用两个或多个hash table来记录数据,对A进行两次hash,得出两个hash table中的插入位置,随机选择一个进行插入
    • 如果选择的插入位置已经有数据了,就选择另一个插入
    • 如果两个都有数据了,就占据一个,然后对这个位置上之前的数据B再次hash选择其余位置。

动态hash

  • chained hashing
    • 把所有相同hash的组成一个bucket链表,然后一直往后面增加
    • java的hash table默认就是这样的
  • extendible hashing
    • 对 chained hashing 的扩展
    • 有一个slot array,在slot array上有一个 counter, 如果counter = 2,代表看hash以后的数字的前两个bit,slot array就有4个位置,分别是00,01,10,11
    • 每个slot指向一个bucket
    • hash以后找到前两位对应的slot指向的bucket,将数据放进去,如果满了,放不下了就进行拆分
    • 将slot array的counter扩容为3,看前3个bit,slot array变成了8个位置
    • 只将这个满了的bucket拆分成2个,其余的不变,重新进行slot的映射
    • 再次hash这个值,看前3个bit找到对应的slot,在找到对应的bucket,然后插入进去
  • linear hashing
    • 对 extendible hashing 的扩展
    • 去掉了 conter,因为他每次加1,都会扩容一倍
    • 增加了split point,一开始指向0,然后每次overflow需要拆分的时候就拆分split point指向的那个bucket,然后slot array只扩容一个,这个时候出现第二个hash函数并将split point+1
    • 查询的时候如果slot array的位置小于split point,就使用第二个hash函数,因为被拆分了
    • 如果大于等于split point,就使用第一个hash函数

全文索引(Full-text Index)

结构:

  • 全文索引是专门用于文本检索的索引结构,通常用于TEXT类型的数据。
  • MySQL会将文本字段中的每个单词进行索引,创建倒排索引,以提高文本搜索的效率。

优点:

  • 高效的全文搜索:可以用于模糊匹配、大规模文本数据的快速搜索,支持MATCH…AGAINST查询。
  • 支持复杂查询:全文索引不仅支持简单的文本搜索,还可以支持布尔模式、自然语言模式等复杂的文本搜索。

缺点:

  • 只适用于TEXT字段:全文索引主要用于大文本字段,如TEXT、LONGTEXT等。
  • 更新开销大:对于频繁更新的表,全文索引的更新性能较差。
  • 无法使用普通的LIKE查询:只能通过MATCH…AGAINST进行查询。

应用场景:

  • 适用于需要进行全文搜索的场景,如文章搜索、日志分析等。

数据库全文索引适用于某个字段需要支持全文搜索。且不想进行大的改造。也没有太多的后续扩展需求。

如果对于全文搜索的要求比较高的话,还是推荐使用ES等搜索引擎进行改造的。

空间索引(Spatial Index)

结构:

  • 空间索引是用于地理空间数据类型(如POINT、LINESTRING、POLYGON)的索引结构,通常使用R树(或改进版的R+树)来存储和查询空间数据。

优点:

  • 高效的空间查询:能够高效地查询空间数据,支持ST_Within、ST_Intersects等空间查询操作。
  • 支持多维数据:可以存储和查询多维数据,如经纬度、位置坐标等。

缺点:

  • 空间数据类型限制:只适用于空间数据类型(如GEOMETRY、POINT等)。
  • 查询性能受数据分布影响:空间索引的性能受空间数据的分布和密度影响,密集区域查询可能性能不佳。

应用场景:

  • 适用于地理信息系统(GIS)等需要进行空间数据查询的应用场景,如地图服务、定位服务等。

倒排索引(Inverted Index)

结构:

  • 倒排索引主要用于文本搜索。它将文档中每个词的出现位置存储在一个列表中,从而支持高效的文本检索。

优点:

  • 高效的关键词检索:倒排索引非常适合处理大量文本数据,可以通过关键词快速查找相关文档。
  • 支持复杂查询:可以支持基于关键词的复杂查询,如布尔查询、短语查询等。

缺点:

  • 只适用于文本数据:倒排索引主要用于文本搜索,不能像B+树一样处理范围查询。
  • 内存消耗大:倒排索引需要占用较大的内存空间,尤其是在数据量较大时。

应用场景:

  • 主要应用于搜索引擎、内容管理系统、文档管理系统等需要高效检索文本内容的场景。

位图索引(Bitmap Index)

结构:

  • 位图索引使用位图(bitmap)来表示数据列中各个值的存在情况。例如,假设某一列有3个不同的值,可以用3个位来表示每个值的出现情况。

优点:

  • 适用于低基数数据:当字段的可能值较少(如性别、状态等字段)时,位图索引可以节省大量的存储空间。
  • 查询效率高:位图索引非常适合多条件组合查询,尤其是当查询涉及多个低基数列时。

缺点:

  • 适用范围有限:位图索引不适用于高基数的字段(如年龄、收入等),因为位图的大小随着基数增加会急剧增加,导致空间浪费。
  • 更新成本高:每次更新时,需要修改位图,更新开销较大。

应用场景:

  • 适用于低基数的字段,特别是用于多条件组合查询时。

索引的作用

  • 加速查询:索引最主要的作用就是提高查询速度。通过索引,MySQL可以直接定位到目标数据行,而不是扫描整个表。
  • 提高排序效率:在使用ORDER BYGROUP BY时,索引可以帮助MySQL高效地进行排序。
  • 优化连接操作:当查询涉及多个表的连接时,索引可以加速连接的过程。
  • 减少数据扫描量:在进行范围查询、匹配查询等操作时,索引可以大幅减少需要扫描的记录数。

索引的种类

上面已经介绍了索引的结构了,接下来看一下索引的类型,这些类型是逻辑上的。而结构则是物理上的。

这些索引类型下面都可以支持不同的索引结构,进行组合。

主键索引(Primary Key Index)

特点:主键索引是唯一的,且不允许NULL值。每个表只能有一个主键索引。
应用场景:主键索引用于表中唯一标识记录的字段。通常选择业务中最重要且唯一的字段作为主键,如id。

主键索引一般使用自增ID或者使用雪花算法,主要保证自增即可。

唯一索引(Unique Index)

  • 特点:唯一索引保证索引列的值唯一,但允许NULL值。
  • 应用场景:当某一列的值需要保证唯一性时,使用唯一索引。例如,邮箱、用户名等字段。

唯一索引的唯一约束是由数据库来保证的。

这样的好处是保证了数据的绝对唯一。并且加速了一些查询。比如手机号上有唯一索引,那么查询手机号=xxx这个条件的时候,找到一个就会停止继续扫描了。如果没有唯一索引,那么会继续扫描。

不好的地方在于减慢了插入更新操作的速度。因为每次插入的时候数据库都需要判断唯一性。

具体使用就需要个人来权衡了。

普通索引(Index)

特点:普通索引没有唯一性要求。它主要用于提高查询效率。
应用场景:当我们需要查询某些列的数据时,且这些列没有唯一性要求,使用普通索引就能提高查询性能。

全文索引(Fulltext Index)

特点:全文索引适用于文本数据的快速搜索,尤其在TEXT类型的列中非常有效。
应用场景:当需要对大量文本数据进行搜索(如文章内容搜索)时,使用全文索引。

联合索引(Composite Index)

特点:联合索引是由多个列组成的索引。MySQL通过联合索引来优化涉及多个列的查询。
应用场景:当查询经常涉及多个列的条件时,使用联合索引可以提高查询效率。

如果涉及多个列的查询,可以不用设置多个索引,而是设置一个联合索引。

index merge 优化

index merge就是多个索引并发扫描,再将扫描结果合并

索引合并不适用于全文索引。

索引合并访问方法检索具有多个范围扫描的行,并将其结果合并为一个。此访问方法只合并单个表的索引扫描,而不合并多个表的扫描。合并可以产生其底层扫描的并集、交集或交集的并集。

可以使用索引合并的查询示例:

1
2
3
4
5
6
7
8
9
10
11
12
SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;

SELECT * FROM tbl_name
WHERE (key1 = 10 OR key2 = 20) AND non_key = 30;

SELECT * FROM t1, t2
WHERE (t1.key1 IN (1,2) OR t1.key2 LIKE 'value%')
AND t2.key1 = t1.some_col;

SELECT * FROM t1, t2
WHERE t1.key1 = 1
AND (t2.key1 = t1.some_col OR t2.key2 = t1.some_col2);

如果你的查询有一个带有深度AND/OR嵌套的复杂WHERE子句,并且MySQL没有选择最佳计划,请尝试使用以下恒等转换来分发术语:

1
2
(x AND y) OR z => (x OR z) AND (y OR z)
(x OR y) AND z => (x AND z) OR (y AND z)

在EXPLAIN输出中,Index Merge方法在type列中显示为index_merge。在本例中,key列包含使用的索引列表,key_len包含这些索引的最长键部分列表。

索引合并访问方法有几种算法,它们显示在EXPLAIN输出的Extra字段中:

  • intersect:对多个and条件生效
  • union:对多个or条件生效
  • sort_union:sort-union算法和union算法之间的区别在于,sort-union算法必须首先获取所有行的行ID,并在返回任何行之前对其进行排序。

索引合并的使用取决于optimizer_switch系统变量的index_merge、index_merge_intersection、index_merge_union和index_merge_sort_union标志的值。默认情况下,所有这些标志都是打开的。

如何设计索引?

设计索引不仅仅是为每个字段都加上索引,而是要根据查询的特点来合理选择索引。以下是一些常见的索引设计思路:

优先选择经常作为查询条件的字段

在WHERE子句中频繁使用的字段,通常应该创建索引。
经常用来连接(JOIN)的字段也应该加索引。
如果某个字段的值具有较高的唯一性(如id、手机号等),它是理想的索引候选。

合理选择索引的顺序

对于联合索引,列的顺序非常重要。MySQL通过最左前缀匹配原则来匹配索引,因此选择索引列的顺序时要考虑到查询的条件顺序。
例如,在查询WHERE name='John' AND age>25时,若创建了(name, age)的联合索引,那么查询会使用该索引。如果把age放在前面,查询可能无法利用该索引。

不要过度使用索引

索引虽然提高了查询效率,但也带来了一些负担。每次插入、更新、删除数据时,索引都会被更新。过多的索引会导致写操作的性能下降。因此,应当只为常用的查询字段建立索引,避免为每个字段都加索引。

避免在频繁更新的字段上创建索引

对于一些经常进行更新操作的字段,不建议创建索引,因为每次更新时都会重新计算索引,这会影响性能。

使用覆盖索引(Covering Index)

覆盖索引是一种特殊的索引,它能满足查询的所有需求,即查询字段完全在索引中,可以避免访问数据表的操作。通过合理设计索引列,尽量使查询能够通过索引完成。

例如,查询SELECT id, name FROM users WHERE age > 30时,可以创建一个包含id、name和age的联合索引,这样查询就可以直接通过索引返回结果。

索引优化案例

案例1:快速查找用户信息

假设有一个users表,结构如下:

1
2
3
4
5
6
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100),
age INT
);

如果我们经常需要查询某个email对应的用户,可以为email列添加一个唯一索引:

1
CREATE UNIQUE INDEX idx_email ON users(email);

这样,查询SELECT * FROM users WHERE email='test@example.com';时,就能迅速找到对应的记录。

优化多条件查询

假设我们有一个orders表,用于记录订单信息,查询经常会使用status和order_date这两个字段。如果我们创建一个联合索引:

1
CREATE INDEX idx_status_date ON orders(status, order_date);

这样,查询SELECT * FROM orders WHERE status='shipped' AND order_date='2025-06-30';时,MySQL会利用该索引提高查询效率。

案例3:避免全表扫描

假设我们有一个大表products,其中包含了成千上万的商品信息。如果我们需要根据name来查询某个商品,通过给name字段创建索引:

1
CREATE INDEX idx_name ON products(name);

查询时,MySQL就能通过该索引快速定位到商品,而不是扫描整个表。

总结

索引设计是MySQL数据库优化的核心技术之一。通过合理的索引设计,我们可以极大地提高查询性能,但过多或不恰当的索引设计可能带来负面影响。掌握索引的基本原理与设计策略,将帮助你在MySQL数据库的应用中更加高效地管理和优化数据查询。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

抖音为啥总让你“上头”?揭秘它偷摸给你“画像”的全过程!

“救命!这说的不就是我吗?”

1
2
3
4
5
“晚上11点,你躺在床上打开抖音,心想‘就刷10分钟’——结果一抬头,凌晨1点半了🙃。”

“明明只想看个搞笑视频,却被推送了‘如何暴瘦20斤’,你吓得手机一抖:它怎么知道我最近胖了?!”

“更离谱的是,你刚和朋友聊到想养猫,第二天抖音就塞给你100个猫片,仿佛在你家装了窃听器…”
  • “抖音人均‘熬夜冠军’:90%的用户每天刷视频超过1小时,60%的人会因‘再刷一条’熬夜到凌晨。”
  • “但比熬夜更可怕的是——它总能猜中你心里那点小秘密:暗恋的人、想买的东西、甚至你妈都不知道的生活习惯!”

你是否有以下疑问?

“明明没填过年龄、职业、爱好,抖音为啥比亲妈还懂你?”
“真相是:它早就在你眼皮底下,偷偷画了一张‘数字分身’的像!”
“今天我们就来扒一扒,抖音是怎么‘算计’你的——看完你可能再也不敢乱点赞了…”

“别慌!这不是科幻片,3分钟带你看懂背后的套路——从此刷抖音,你才是操控算法的人👊。”

什么是用户画像?——用“人话”解释专业概念

  1. 先举个“栗子”,秒懂用户画像

“你网购时的样子,就是用户画像的答案!”

  • 场景1:你经常买咖啡豆、搜“办公室提神神器” → 淘宝立马给你贴标签:“熬夜打工人,咖啡续命党”。

  • 场景2:你总看母婴测评、收藏育儿干货 → 小红书默默标记你:“新手宝妈,爱囤货的细节控”。

一句话总结:

用户画像 = 平台用你的行为数据,“拼”出的一本个人说明书,上面写满了你的喜好习惯,甚至小心思。

  1. 别怕!它画的不是你的脸,而是你的“影子”
  • 误区澄清:
    • “画像不是偷拍你的身份证,也不会知道你叫张三李四。”
    • 它只关心你做了什么,而不是你是谁
  • 举个反向例子🌰:
    • 如果你用爸妈手机刷了一天广场舞视频 → 抖音可能误判机主是退休阿姨,第二天狂推养生茶丝巾(爸妈:???)。
    • 结论:用户画像可能出错,但它会跟着你的行为“随时改作业”!
  1. 你的“数字分身”是怎么被画出来的?
  • 第一步:暗中观察你的“蛛丝马迹”
    • 明面操作:点赞、评论、关注 → 你主动告诉平台“我爱这个!”
    • 暗地追踪:
      • 你在一只柯基视频上停留了20秒 → 系统:“这人喜欢狗,记下来!”
      • 你总在晚上11点后刷美食视频 → 系统:“夜宵爱好者,深夜放毒目标用户!”
  • 第二步:给你贴满“隐形标签”
    • 标签类型:
      • 基础款:性别年龄(猜的)、地理位置(比如“北京通州打工人”)。
      • 进阶款:“社恐宅家党”“刘畊宏女孩”“甄嬛传十级学者”。
    • 终极目标:用无数个标签把你“拆解”得明明白白。
  1. 用户画像能有多离谱?网友亲身经历炸锅

爆笑案例:

  • 网友A:搜过一次“痔疮膏”,从此抖音推荐全是“肛肠医院专家号”(本人:我只是手滑!)。
  • 网友B:借朋友手机看了一次挖掘机视频,结果算法认定他是“蓝翔潜在学员”(朋友:你害我!)。

严肃提醒:

用户画像不一定100%准确,但会像牛皮糖一样粘着你——你越刷,它越懂(或越瞎猜)。

  1. 为什么平台非要给你“画像”?真相扎心了

对你:

  • 方便!不用手动搜索,喜欢的内容自动喂到嘴边。

对平台:

  • 留住你:让你刷到停不下来 → 增加广告曝光 → 赚更多钱。

说白了:

你以为在刷视频,其实是你的“画像”在帮抖音“打工”!

抖音如何“偷摸”给你画像?——3步拆解

步骤1:暗中观察——你的“小动作”全被记在小本本上

你以为你在玩手机?不,你在给抖音“交作业”!

  • 明面上的“送分题”:
    • 点赞 → “这个视频我超爱!”
    • 收藏 → “好东西必须囤着!”
    • 关注 → “这博主是我亲爹/妈!”
  • 暗地里的“监控大法”:
    • 停留时长:
      • 看帅哥美女视频停留30秒 → 系统:“这人好色,记下来!”
      • 看科普视频3秒就划走 → 系统:“学渣,下次少推!”
    • 重复观看:
      • 同一条修狗视频看了5遍 → 系统:“狗党实锤,明天首页全变宠物乐园!”
    • 搜索关键词:
      • 搜过“脱发自救” → 第二天推荐生发液广告+秃头博主励志故事(扎心了💔)。

步骤2:算法“算命”——找到你的“网络双胞胎”

套路1:协同过滤——拉群找“同类”

  • “你爱看腹肌男,我也爱看→咱俩就是异父异母的亲兄弟!”
    • 你点赞了“刘畊宏跳操” → 抖音找到100个同样爱健身的人 → 把她们喜欢的蛋白粉广告、瑜伽裤测评全塞给你。
  • 副作用:
    • 不小心点了一次广场舞视频 → 接下来一周都是“老年Disco”(年轻人:救救我😇)。

套路2:深度学习——系统比你更懂你

  • “你连刷10条露营视频?懂了,你上辈子是棵树!”
    • 系统发现你爱看“野外生存” → 自动关联推荐帐篷、烧烤架,甚至防狼喷雾(虽然你可能只是云露营)。
  • 离谱案例:
    • 网友@小陈:拍过一次穿JK制服的照片 → 抖音判定她是“二次元宅女”,狂推动漫混剪和手办福袋(实际是帮妹妹拍的)。

步骤3:24小时“PUA”——越刷越上瘾的终极陷阱

实时更新画像:今天你是宝妈,明天变驴友

  • 周一刷美妆教程 → 画像:“精致都市丽人”。
  • 周二突然看育儿知识 → 画像秒变:“新手妈妈”(哪怕你单身🐶)。
  • 系统逻辑:宁可错杀一千,绝不放过一个!

多维度“绑架”你的注意力:

  • BGM操控:你常听周杰伦 → 同类音乐视频优先推送。
  • 时间陷阱:发现你每晚10点活跃 → 准时投放“助眠神器”广告(结果你越看越精神)。
  • 地域追踪:IP在北京 → 推荐“环球影城攻略”;切换到老家县城 → 立刻变成“相亲神曲合集”。

举个真实场景,让你脊背发凉

“一条视频引发的血案”:

你偶然点开“考研英语”视频,看了15秒 → 系统标记“潜在考生”。

第二天首页出现“考研逆袭故事” → 你忍不住看完。

一周后,抖音开始推荐“考研辅导班”“防脱发洗发水”,甚至“二战考研租房攻略”……

结果:你本来只想随便看看,现在满脑子都是“我是不是该考研了?”

总结:抖音的“画像公式”

你的行为数据 + 算法的“脑补” = 一个比你妈还操心的“数字管家”

  • 温馨提示:

如果不想被“画像”绑架,试试这招👇:

长按视频 → 点击“不感兴趣” → 让算法怀疑人生!

(但大概率你会继续刷,因为——真香!)

为什么我们心甘情愿被“操控”?——人性的弱点

多巴胺陷阱:下一个视频永远在“勾引”你

“像赌博一样上瘾”:

  • 每次下滑都像“开盲盒”——可能是搞笑段子、萌宠暴击,或是心动帅哥,这种不确定的惊喜感让大脑疯狂分泌多巴胺。

案例:

网友@小鱼:“明明困得要死,但总感觉下一个视频会更精彩,结果刷到天亮……我像个赌徒!”

生理真相:

  • 刷抖音时的大脑活跃区和吸毒时高度相似——你不是在玩手机,是在“嗑电子药”。

即时满足:快乐来得太容易,谁还愿意“吃苦”?

“3秒一个爽点,谁扛得住?”

  • 看书要专注1小时,追剧要等更新,但抖音的快乐“即刷即有”:
  • 想放松?10秒就能笑到喷饭。
  • 想学习?1分钟“速成”Excel技巧(虽然转头就忘)。

对比伤害:

现实中的努力需要坚持,但抖音的快乐“一键直达”——人天生偏爱“捷径”。

信息茧房:温柔地“杀死”你的好奇心

“你以为你在选内容?其实是算法给你造了个温室”:

  • 爱看美妆就推全妆教程,爱看游戏就塞电竞直播……你越满意,系统越不敢让你看别的。

副作用:

网友@阿凯:“刷了3个月抖音,我以为全世界都爱看钓鱼,直到发现我妈的首页全是婆媳伦理剧……”

心理机制:

  • 人本能地抗拒“认知失调”——看到不同观点会难受,而抖音让你永远“舒适”。

社交货币:不刷抖音,你就“没朋友”

“梗都不知道,还怎么混圈子?”:

  • 当全网都在跳“科目三”、玩“小土豆梗”时,你不刷抖音=聊天插不上话=被社交圈抛弃。

案例:

大学生@露露:“室友们讨论抖音热梗哈哈大笑,我只能假装听懂,默默打开手机开刷……”

平台心机:

  • 抖音刻意制造“全民爆梗”,让你觉得:不参与=落伍=孤独。

虚拟认同:点赞和关注,成了“电子氧气”

“现实中当小透明,抖音里当赛博顶流”:

  • 发一条自拍视频,收到100个点赞 → 感觉自己被全世界喜爱(尽管陌生人根本不认识你)。

心理补偿:

  • 工作被老板骂?感情不顺?抖音里发条视频,评论区总有人夸你“姐妹好美!”——这种即时认同让人欲罢不能。

偷走时间还不让你愧疚:碎片化的“正当理由”

“我就刷5分钟,放松一下怎么了?”:

  • 等公交、上厕所、吃饭……所有碎片时间都被抖音填满,你以为在“高效利用时间”,实际是被切割成无法专注的废片。

真实伤害:

  • 研究显示:频繁切换注意力,会导致记忆力下降、焦虑加剧——但你根本停不下来,因为“5分钟”太容易骗过自己。

总结:我们不是输给抖音,是输给人性

“抖音不可怕,可怕的是它比你还懂你”:

  • 它利用人性的懒惰、贪婪、虚荣、恐惧,织了一张温柔的网——你明知道是陷阱,却自愿跳进去。

终极矛盾:

  • 我们既渴望抖音提供的快乐,又厌恶被控制的无力感,最后在“真香”和“戒断”之间反复横跳。

警惕!被“画像”背后的代价

你以为的“贴心”,可能是“掏空你”的陷阱

代价1:隐私裸奔——你的生活成了“透明超市”

案例:

1
2
3
和朋友微信聊“想买吸尘器”,第二天抖音就推荐10款 → “它怎么听到的?!”(其实是通过输入法、跨APP数据共享)。

搜过“租房攻略”,立刻被中介广告轰炸 → 你的焦虑成了别人赚钱的工具。

扎心真相:

  • 免费刷视频的代价,可能是你的隐私被“标价售卖”——广告主花钱就能精准找到你。

代价2:信息茧房——活成“数字井底之蛙”

  • “你看到的世界,是算法想让你看到的”:
    • 爱看正能量 → 首页全是人间美好 → 你以为社会一片和谐。
    • 爱看负面新闻 → 推送越来越极端 → 你变得焦虑、厌世。

网友自嘲:

  • “在抖音待了3年,我以为全国女生都月入5万,只有我穷得真实😭”

代价3:钱包被“绑架”——算法让你花钱更上头

套路拆解:

  • 先给你推“普通人逆袭”故事(制造焦虑)。
  • 再塞给你“3天速成副业”课程(提供希望)。
  • 最后弹出“限时折扣”支付页面(逼你冲动下单)。

血泪教训:

网友@娜娜:“刷抖音学了‘配音赚钱’,花2998元买课,最后只赚了9块8……”

防坑指南——普通人如何“反杀”算法?

招式1:给算法“喂假饭”

操作手册:

  • 偶尔故意乱刷:猛看完全不感兴趣的内容(比如广场舞、拖拉机维修)。
  • 随机点赞:给宠物视频点❤️,再给财经分析点❤️ → 让系统怀疑人生。

效果:

  • 算法画像混乱 → 推荐内容“精神分裂”,你成功隐身!

招式2:关闭“监控开关”(以抖音为例)

步骤:

  • 点击“我”→右上角三条杠→【设置】。
  • 找到【隐私设置】→ 关闭【个性化内容推荐】。
  • 【广告偏好设置】→ 关闭【程序化广告展示】。

副作用预警:

首页会变成无聊大杂烩(但至少你的数据安全了)。

招式3:定期“数据大扫除”

  • 清理缓存:设置→清理占用空间→勾选【临时文件】【资源文件】。
  • 重置广告ID(安卓/苹果通用):
  • 手机设置→隐私→广告→重置广告标识符 → 让平台重新“认识你”。

终极灵魂拷问:逃离算法,我们能赢吗?

现实很骨感:

  • 你可以关掉抖音,但外卖APP、购物平台、甚至导航软件都在给你画像 → “全网皆牢笼”。

清醒一点:

  • 完全抵制不现实,但可以“让算法为我所用”:
  • 想学习?主动搜索干货 → 让系统推荐知识类视频。
  • 想戒剁手?多刷差评揭露视频 → 算法帮你“拔草”。

你的人生不该被“标签”定义

  • “抖音里的你,是爱吃瓜的乐子人、是职场小白、是深夜emo怪……但那只是数据的‘碎片’。”
  • “真实的你,远比算法想象的更丰富、更自由——别让屏幕里的‘画像’,框住你的人生。”

“试过以上防坑招式的,评论区举手!没试过的……现在立刻去关掉个性化推荐,回来打卡!”

总结

把选择权“抢”回自己手里.

  • “刷抖音时,到底是你在找快乐,还是算法在‘投喂’快乐?”
  • 用算法看世界 → 世界越来越小,小到只剩你喜欢的“巴掌大”。
  • 自己探索世界 → 可能踩雷,但会发现更多意想不到的惊喜。

“别让算法替你决定‘该看什么’——你的人生,不该活成数据的提线木偶。”

“你是哪种抖音‘上头人’?对号入座👇
1️⃣ “永远在等下一个更香” 多巴胺赌徒
2️⃣ “不刷梗就社恐” 跟风焦虑党
3️⃣ “被算法PUA还夸它贴心” 清醒沉沦者
❤️ 评论区认领你的身份,找找同类!”

“关不掉抖音?至少可以‘反操控’它!”

  • 神操作:长按视频 → 【不感兴趣】 → 连点10次,算法会哭着求你别走。

“你在抖音的每一次滑动,都在为你的‘数字画像’描边——是时候告诉自己:我的人生,绝不止算法定义的这一个版本。”

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

MCP

在AI技术大火的当下,各个公司都在搞AI-Agent技术。对于Agent来说,其实还处于一个早期的阶段,没有所谓的最佳实践。更没有一些标准规则。可谓是一片蓝海。

大厂们也都在做着自己的一些尝试。但是总有人在建设底层规则。

MCP就是一个标准化的产物。它定义了一些标准

Model Context Protocol(MCP)是由Anthropic于2024年11月推出的开放标准,旨在为大型语言模型(LLM)应用提供统一的上下文交互接口,使其能够与外部数据源和工具进行无缝集成。​MCP采用客户端-主机-服务器架构,基于JSON-RPC 2.0协议,支持有状态连接和功能协商,允许AI模型访问文件、执行函数和处理上下文提示。

截至2025年4月,MCP已被多个AI平台和开发工具采纳,包括OpenAI、Google DeepMind、Replit、Sourcegraph等。​其应用场景涵盖了软件开发、企业助手、自然语言数据访问等领域。

MCP服务器开发

许多大模型目前没有能力获取预报和恶劣天气警报。让我们使用MCP 来解决这个问题!

我们将构建一个公开两个工具的服务器:get-alertsget-forecast。然后我们将服务器连接到 MCP 主机(在本例中,Claude for Desktop):

MCP本身提供了一些语言的SDK,包括:

  • Python
  • Node.js
  • Java
  • C#
  • Kotlin

以Java为例,要求如下:

  • Java 17 或更高版本
  • Spring Boot 3.3.x或更高版本

使用SDK可以大大简化我们的开发过程,比如JAVA里面的,我们只需要一个注解@Tool就可以搞定了。

需要添加Maven依赖:

  • spring-web:web开发依赖
  • spring-ai-starter-mcp-server:MCP服务器开发依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependencies>
    <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    </dependency>
    </dependencies>

我们增加一个Service类,该类有两个方法,这两个方法提供了我们上面说的两个工具的能力。

  • getWeatherForecastByLocation: 提供给Agent或者大模型的工具,输入参数是精度和纬度,根据经纬度获取天气预报。
  • getAlerts:提供给Agent或者大模型的工具,输入参数是地区,获取该地区的天气警报。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
@Service
public class WeatherService {
private final RestClient restClient;

public WeatherService() {
this.restClient = RestClient.builder()
.baseUrl("https://api.weather.gov")
.defaultHeader("Accept", "application/geo+json")
.defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
.build();
}

@JsonIgnoreProperties(ignoreUnknown = true)
public record Points(@JsonProperty("properties") Props properties) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record Props(@JsonProperty("forecast") String forecast) {
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
public record Forecast(@JsonProperty("properties") Props properties) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record Props(@JsonProperty("periods") List<Period> periods) {
}

@JsonIgnoreProperties(ignoreUnknown = true)
public record Period(@JsonProperty("number") Integer number, @JsonProperty("name") String name,
@JsonProperty("startTime") String startTime, @JsonProperty("endTime") String endTime,
@JsonProperty("isDaytime") Boolean isDayTime, @JsonProperty("temperature") Integer temperature,
@JsonProperty("temperatureUnit") String temperatureUnit,
@JsonProperty("temperatureTrend") String temperatureTrend,
@JsonProperty("probabilityOfPrecipitation") Map probabilityOfPrecipitation,
@JsonProperty("windSpeed") String windSpeed, @JsonProperty("windDirection") String windDirection,
@JsonProperty("icon") String icon, @JsonProperty("shortForecast") String shortForecast,
@JsonProperty("detailedForecast") String detailedForecast) {
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
public record Alert(@JsonProperty("features") List<Feature> features) {

@JsonIgnoreProperties(ignoreUnknown = true)
public record Feature(@JsonProperty("properties") Properties properties) {
}

@JsonIgnoreProperties(ignoreUnknown = true)
public record Properties(@JsonProperty("event") String event, @JsonProperty("areaDesc") String areaDesc,
@JsonProperty("severity") String severity, @JsonProperty("description") String description,
@JsonProperty("instruction") String instruction) {
}
}

@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(
double latitude, // Latitude coordinate
double longitude // Longitude coordinate
) {
// 使用 RestClient 发起 GET 请求,获取指定经纬度的点信息
var points = restClient.get()
.uri("/points/{latitude},{longitude}", latitude, longitude) // 构建请求 URI,替换占位符为具体的经纬度
.retrieve() // 执行请求并获取响应
.body(Points.class); // 将响应体解析为 Points 类型的对象

// 使用 RestClient 发起 GET 请求,获取点信息中的天气预报 URL 对应的天气预报数据
var forecast = restClient.get()
.uri(points.properties().forecast()) // 从点信息中提取天气预报 URL
.retrieve() // 执行请求并获取响应
.body(Forecast.class); // 将响应体解析为 Forecast 类型的对象

// 遍历天气预报的各个时间段,生成格式化的天气预报文本
String forecastText = forecast.properties().periods().stream().map(p -> {
return String.format("""
%s:
Temperature: %s %s
Wind: %s %s
Forecast: %s
""",
p.name(), // 时间段名称
p.temperature(), // 温度值
p.temperatureUnit(), // 温度单位
p.windSpeed(), // 风速
p.windDirection(), // 风向
p.detailedForecast()); // 详细天气预报
}).collect(Collectors.joining()); // 将所有时间段的天气预报文本拼接为一个字符串

// 返回生成的天气预报文本
return forecastText;
}

@Tool(description = "Get weather alerts for a US state")
public String getAlerts(
@ToolParam(description = "Two-letter US state code (e.g. CA, NY") String state
) {
// 使用 RestClient 发起 GET 请求,获取指定州的天气警报信息
Alert alert = restClient.get().uri("/alerts/active/area/{state}", state).retrieve().body(Alert.class);

// 遍历警报信息的特性列表,将每个特性格式化为字符串并拼接为最终的警报信息文本
return alert.features()
.stream()
.map(f -> String.format("""
Event: %s
Area: %s
Severity: %s
Description: %s
Instructions: %s
""",
// 获取警报事件名称
f.properties().event(),
// 获取警报影响区域描述
f.properties.areaDesc(),
// 获取警报严重程度
f.properties.severity(),
// 获取警报详细描述
f.properties.description(),
// 获取警报的操作指令
f.properties.instruction()))
// 将所有警报信息拼接为一个字符串,以换行符分隔
.collect(Collectors.joining("\n"));
}
}

再通过MCP提供的MethodToolCallbackProvider 工具类来将 @Tool 标记的方法转换成统一的方法,对外部提供工具。

这是因为MCP服务器规定要给外部提供统一的接口、返回值、参数。所以要转换成符合MCP规范的接口。

1
2
3
4
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}

接下来构建服务器,运行成功以后,会在target目录下生成一个jar包,运行该jar包应该能成功启动:

1
./mvnw clean install

接下来我们无需启动这个jar包,直接使用Claude 桌面版客户端即可。

可以在这里下载:claude 桌面版下载

下载以后,打开应用,登陆上去,选择 Claude -》 Settings

settings

选择Developer -〉Edit Config。会自动创建一个MCP的配置文件。如果已经存在了则不会创建。

Config

创建的配置文件名称如下:

1
claude_desktop_config.json

修改这个配置文件,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
{
"mcpServers": {
"spring-ai-mcp-weather": { // MCP服务器名称,可以随便输入
"command": "java", // 使用java命令启动MCP服务器
"args": [ // java后面跟的参数
"-Dspring.ai.mcp.server.stdio=true",
"-jar",
"/mcpServer/target/mcpServer-0.0.1-SNAPSHOT.jar" //jar包的绝对路径地址
]
}
}
}

接下来重新启动Claude桌面版即可。在启动Claude的时候,Claude会根据上述配置文件来启动MCP服务器。启动命令类似:

1
java -Dspring.ai.mcp.server.stdio=true -jar /mcpServer/target/mcpServer-0.0.1-SNAPSHOT.jar

如果通过jps命令,可以看到jar包启动成功。

1
jps -l

在Claude桌面版上可以看到我们刚刚配置的MCP服务器名称和提供的工具。

mcp server

mcp server tool

可以自主启用和禁用工具。

接下来我们和Claude大模型交流的时候,大模型会自己判断需不需要调用工具,调用哪个工具,如何调用。当调用的时候,会让你进行确认。

来试试输入:

1
纽约的天气怎么样?

mcp server tool

可以看到大模型自己调用了我们的工具,并展示了输入输出。

mcp server tool

看到这里我们可以得出MCP+大模型的运行流程图。

mcp server tool

日志

如果需要查看日志信息,可以到如下目录:~/Library/Logs/Claude

有多个文件:

  • mcp.log : 包含了MCP所有的日志
  • mcp-server-xxxxx.log: 一个MCP服务一个日志文件,记录了这个服务相关的所有日志

总结

我们主要介绍了MCP的概念,MCP的通信协议、通信方式、生命周期。MCP到底是什么东西,实现了哪些内容,使用场景以及为什么要使用MCP。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

MCP

在AI技术大火的当下,各个公司都在搞AI-Agent技术。对于Agent来说,其实还处于一个早期的阶段,没有所谓的最佳实践。更没有一些标准规则。可谓是一片蓝海。

大厂们也都在做着自己的一些尝试。但是总有人在建设底层规则。

MCP就是一个标准化的产物。它定义了一些标准

Model Context Protocol(MCP)是由Anthropic于2024年11月推出的开放标准,旨在为大型语言模型(LLM)应用提供统一的上下文交互接口,使其能够与外部数据源和工具进行无缝集成。​MCP采用客户端-主机-服务器架构,基于JSON-RPC 2.0协议,支持有状态连接和功能协商,允许AI模型访问文件、执行函数和处理上下文提示。

截至2025年4月,MCP已被多个AI平台和开发工具采纳,包括OpenAI、Google DeepMind、Replit、Sourcegraph等。​其应用场景涵盖了软件开发、企业助手、自然语言数据访问等领域。

架构

Model Context Protocol (MCP) 遵循客户端-主机-服务器架构,每个主机可以运行多个客户端实例。此架构使用户能够在应用程序中集成 AI 功能,同时保持明确的安全边界和隔离关注点。基于 JSON-RPC,MCP 提供了一个有状态的会话协议,专注于上下文交换和客户端与服务器之间的采样协调。

MCP架构

为什么使用MCP?

为什么要有MCP这个东西呢?我不用它是否可以?

当然可以。MCP更像是一个USB接口的作用。不使用的话会有一些麻烦而已。我们来看两个案例。

案例一:USB接口案例

我们先想象一下如果电脑没有USB接口,会怎么样?

每个品牌的鼠标键盘显示器接口都不一样,比如

  • 罗技鼠标键盘使用罗技接口
  • 牧马人鼠标键盘使用牧马人接口
  • 雷蛇鼠标键盘使用雷蛇接口
  • 等等,每个品牌有自己的接口

那现在我们买了一台戴尔的电脑,这个电脑只支持戴尔的接口和罗技的接口,这样就导致我们只能买这两个品牌的鼠标键盘显示器了。

像下面这个图,我如果家里是华硕显示器,但是我买了一个戴尔的电脑,就用不了了,很不方便。

MCP架构

案例二:充电接口案例

现在很现实的一个问题就是苹果手机的充电接口。它的充电接口和安卓的充电接口是不一样的,因此两个手机的充电器无法共用。

如果你从安卓换到苹果,你需要重新买一个充电器,如果你家里两个人用的是一个安卓一个苹果,那么你们出门需要带两个充电器。。。

MCP架构

案例三:Agent案例

经过上面两个案例,我们应该明白了MCP解决了什么问题,也明白了为什么要使用MCP这种标准化协议。

那么我们再看看对于Agent而言,MCP到底干了什么呢?

我们当前Agent的现状如下:
我们实现了一个查询天气信息,并根据下雨下雪刮风等进行邮件通知预警的Agent。我们的Agent需要调用2个接口:

  1. 获取天气信息
  2. 发送邮件

我们知道,这两个接口是不一样的,因此,我们需要实现两套调用逻辑

这样是很不方便的。甚至,如果说第一个获取天气信息的接口不好使了,我们要进行切换,其他的获取天气信息的接口也需要重新实现

但是,如果我们使用了MCP,那么我们只需要一套逻辑即可了。

MCP组件

接下来我们来了解一下MCP有哪些内容,包含什么东西。

  • 主机:也就是你的Agent,或者大模型。严格来说,这个并不属于MCP,因为这个是你必备的一个东西。只不过我们的Agent需要支持MCP才可以。
  • MCP服务器:轻量级程序,每个程序都通过标准化的模型上下文协议公开特定的功能。MCP服务器就是一个提供接口程序的服务器程序,可以简单的理解为HTTP服务器,提供一些接口。比如我们上面提到的两个接口都放到这个服务器里面。
  • MCP客户端:这个是重中之重。客户端程序负责调度主机MCP服务器进行交互。

资源

资源表示 MCP 服务器希望提供给客户端的任何类型的数据。这可以包括:

  • 文件内容
  • 数据库记录
  • API 响应
  • 实时系统数据
  • 屏幕截图和图像
  • 日志文件
  • 更多

资源主要用来读取文件内容,比如我们经常使用大模型的时候会上传文件,还有使用Idea的时候可以直接让大模型获取我们的代码文件内容。

资源可以包含两种类型的内容:

  • 文本资源
    • 源代码
    • 配置文件
    • 日志文件
    • JSON/XML数据
    • 纯文本
  • 二进制资源
    • 图像
    • PDF
    • 音频
    • 视频
    • 其他非文本格式

MCP服务器需要实现下面的接口来支持资源功能

  • 资源列表接口:接口地址必须是resources/list,返回内容需要包含4个信息,这个接口会告诉Agent我们拥有哪些资源
    1
    2
    3
    4
    5
    6
    {
    uri: string; //资源的URI
    name: string; //资源的名称,方便人类看
    description?: string; //可选的资源描述
    mimeType?: string; //可选的资源类型
    }
  • 获取资源接口:接口地址必须是resources/read, 返回内容必须是以下格式,这个接口就是当Agent需要获取资源内容的时候进行调用的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    contents: [
    {
    uri: string; //资源的URI
    mimeType?: string; //可选的资源类型

    // 根据资源类型进行返回的信息:
    text?: string; // 文本资源的内容
    blob?: string; // 二进制资源的内容
    }
    ]
    }
  • 资源列表更新接口:接口地址必须是notifications/resources/list_changed,这个接口是当我们能获取的资源列表有变化的时候,服务器来通知客户端说列表改变了,你需要重新获取一下。
  • 资源内容更新接口:
    • 客户端进行资源订阅,接口地址必须是resources/subscribe
    • 当资源内容改变的时候,服务器会发送notifications/resources/updated来通知客户端资源内容变化了
    • 客户端可以重新调用获取资源接口来获取最新内容。
    • 客户端可以取消资源订阅,接口地址必须是resources/unsubscribe

提示

提示这个功能也是比较常见的,当你输入/以后,会出现一些提示的命令,很多大模型都支持这么做了。

通过提示这个功能,我们自己的Agent也可以轻松支持这个功能了。

提示符使服务器能够定义可重用的提示模板和工作流,客户端可以轻松地向用户和 LLM 显示。它们提供了一种强大的方式来标准化和共享常见的 LLM 交互。

MCP服务器需要支持以下接口:

  • 提示列表接口:接口地址prompts/list.展示提示列表。返回内容如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
    prompts: [
    {
    name: "analyze-code", // 提示名称
    description: "Analyze code for potential improvements", //提示描述
    arguments: [ // 提示需要的参数列表
    {
    name: "language", //参数名称
    description: "Programming language", //参数描述
    required: true //是否必填
    }
    ]
    }
    ]
    }
  • 使用提示接口:接口地址prompts/get,服务器会返回一些信息如下,客户端可以将这个信息喂给服务器。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    description: "Analyze Python code for potential improvements",
    messages: [
    {
    role: "user",
    content: {
    type: "text",
    text: "Please analyze the following Python code for potential improvements:\n\n```python\ndef calculate_sum(numbers):\n total = 0\n for num in numbers:\n total = total + num\n return total\n\nresult = calculate_sum([1, 2, 3, 4, 5])\nprint(result)\n```"
    }
    }
    ]
    }

工具

工具功能是MCP的核心功能,作用就是我们一开始讲的那个。通过这个功能我们可以接入所有支持MCP的工具。

工具是模型上下文协议(MCP)中的一个强大的原语,它使服务器能够向客户端公开可执行功能。通过工具,LLM 可以与外部系统交互,执行计算,并在真实的世界中采取行动。

MCP服务器需要实现以下接口:

  • 工具列表接口:接口地址是tools/list,作用是获取MCP服务器支持的所有工具列表。可以返回如下内容:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    name: "github_create_issue", //工具名称
    description: "Create a GitHub issue", // 工具描述
    inputSchema: { // 输入参数
    type: "object", // 参数类型
    properties: { //详细的请求参数名称和类型
    title: { type: "string" },
    body: { type: "string" },
    labels: { type: "array", items: { type: "string" } }
    }
    }
    }
  • 工具调用接口:接口地址是tools/call,作用是调用指定的工具。客户端同样可以把调用工具的结果喂给大模型。

采样

采样是一个强大的 MCP 功能,允许服务器通过客户端请求 LLM 完成,在维护安全和隐私的同时实现复杂的代理行为。

采样流程如下:

  1. 服务器请求客户端的接口sampling/createMessage
  2. 客户端检查请求并可以修改它
  3. 客户端获取大模型的样本
  4. 客户端复审样本信息
  5. 客户端将结果返回给服务器

服务器可以指定要采样的大模型,如果客户端没有这个大模型,服务器还可以指定一些模型偏好,也就是一些优先级,如果没有这个大模型,就按照服务器指定的优先级来选择一些其他的大模型进行采样。

模型偏好设置

  • hits:模型名称建议的数组,客户端可以使用它来选择合适的模型,客户端可以将提示映射到来自不同提供程序的等效模型,多个提示按优先级顺序进行评估
  • costPriority: 降低成本的重要性,低成本的模型优先级更高
  • speedPriority: 低延迟响应的重要性,速度更快的模型优先级更高
  • intelligencePriority:高级模型功能的重要性,功能强大的模型优先级更高

是 MCP 中的一个概念,它定义了服务器可以操作的边界。它们为客户端提供了一种方法,可以将相关资源及其位置通知服务器。

根是客户端建议服务器应该关注的 URI。当客户端连接到服务器时,它声明服务器应该使用哪些根。虽然主要用于文件系统路径,但根可以是任何有效的 URI,包括 HTTP URL。

根有几个重要的用途:

  • 指导 :它们通知服务器相关资源和位置
  • 清晰度 :根目录清楚地表明哪些资源是您工作空间的一部分
  • 组织 :多个根允许您同时使用不同的资源

当客户端支持根时,它:

  • 在连接期间声明功能
  • 向服务器提供建议的根目录列表
  • 根目录更改时通知服务器(如果支持)

虽然根是信息性的,并不严格执行,但服务器应该:

  • 尊重提供的根
  • 使用根 URI 定位和访问资源
  • 优先考虑根边界内的操作

根通常用于定义:

  • 项目目录
  • 存储库位置
  • API 端点
  • 配置目录
  • 资源边界

客户端应该返回如下根信息

1
2
3
4
5
6
7
8
9
10
11
12
{
"roots": [
{
"uri": "file:///home/user/projects/frontend",
"name": "Frontend Repository"
},
{
"uri": "https://api.example.com/v1",
"name": "API Endpoint"
}
]
}

MCP服务器

MCP服务器作为接口的提供方。需要遵循MCP协议本身的一些规则。

需要支持上面的一些功能,主要是支持工具。至于资源和提示功能可以不支持。

MCP客户端

MCP客户端作为接口的调用方。也需要遵循MCP协议本身的一些规则。

可以选择支持采样功能。

MCP客户端还需要调用MCP服务器和大模型进行沟通。

MCP通信协议

所有传输都使用 JSON-RPC2.0 来交换消息。

JSON-RPC2.0

JSON-RPC 2.0是一个轻量级的远程过程调用(RPC)协议。它使用JSON格式的数据进行通信,这使得它非常易于理解和操作。下面是对JSON-RPC 2.0协议的详细解释以及案例说明:

在JSON-RPC 2.0中,通信的基本单位是请求和响应。客户端发送请求给服务器,服务器处理请求后返回响应。

一个典型的JSON-RPC 2.0请求格式如下:

1
2
3
4
5
6
7
8
9
{
"jsonrpc": "2.0", // 协议版本,固定2.0即可
"method": "methodName", //要调用的方法名称,可以简单理解为接口地址
"params": { //请求参数
"param1": "value1",
"param2": "value2"
},
"id": 1 // 请求的唯一标识
}

服务器处理请求以后,通常会返回以下两个格式:

  • 正确返回的格式。
    1
    2
    3
    4
    5
    {
    "jsonrpc": "2.0", // 协议版本,固定2.0即可
    "result": "Success", //成功的结果
    "id": 1 // 请求的唯一标识,要和请求的id是一样的,代表是这个请求的返回
    }
  • 如果出现错误,需要返回错误码和错误信息。
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "jsonrpc": "2.0", // 协议版本,固定2.0即可
    "error": { // 错误信息,包括错误码和错误描述
    "code": -32601,
    "message": "Method not found"
    },
    "id": 1 // 请求的唯一标识,要和请求的id是一样的,代表是这个请求的返回
    }

除了正常的请求和返回以外,还有一种通知类型。作为单向消息从客户端发送到服务器,反之亦然。接收方不得发送响应。

1
2
3
4
5
6
7
{
"jsonrpc": "2.0",
"method": "methodName",
"params?": { // 请求参数
[key: string]: unknown
}
}

MCP传输方式

MCP 使用JSON-RPC对消息进行编码。JSON-RPC消息必须是 UTF-8 编码的。

该协议目前为客户端-服务器通信定义了两种标准传输机制:

  • STDIO: 通过标准输入输出来进行传输,通常用在MCP客户端和服务器都在同一主机的情况下。
  • HTTP:通过HTTP来进行传输,这允许MCP客户端和服务器不在同一主机。

客户端应尽可能支持stdio传输。

STDIO

  • 客户端将 MCP服务器作为子进程启动。
  • 服务器从其标准输入(stdin)读取 JSON-RPC 消息,并将消息发送到其标准输出(stdout)。
  • 消息可以是 JSON-RPC 请求、通知、响应或 JSON-RPC 包含一个或多个请求和/或通知的批处理。
  • 消息由换行符分隔,并且不能包含嵌入的换行符。
  • 服务器可以将 UTF-8 字符串写入其标准错误(stderr)以进行日志记录。客户端可以捕获、转发或忽略此日志记录。
  • 服务器不能向它的 stdout 写入任何不是有效的 MCP 消息。
  • 客户端不得向服务器的 stdin 写入任何不是有效 MCP 消息的内容。

初始化

HTTP

HTTP 传输中,服务器作为一个独立的进程运行, 可以处理多个客户端连接。

此传输使用HTTPPOSTGET 请求。

服务器可以选择使用服务器发送的事件 (SSE)以流式传输多个服务器消息。

这允许基本的 MCP 服务器,以及支持流媒体和服务器到客户端通知和请求的功能更丰富的服务器。

MCP生命周期

模型上下文协议(MCP)为客户端-服务器连接定义了严格的生命周期,以确保适当的能力协商和状态管理。

初始化

初始化

初始化

  • 客户端发送包含协议版本功能初始化请求
  • 服务器返回服务器支持的协议版本功能进行响应
  • 客户端发送初始化通知作为确认

此阶段要确认的内容如下:

  • 建立协议版本兼容性
  • 交换和谈判能力
  • 分享实施细节

请求示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize", //初始化方法
"params": {
"protocolVersion": "2024-11-05", // MCP协议版本
"capabilities": { // 支持的能力
"roots": { //支持根
"listChanged": true // 根变化的时候可以通知
},
"sampling": {} // 不支持采样
},
"clientInfo": { //客户端基本信息
"name": "ExampleClient", //客户端名称
"version": "1.0.0" //客户端版本
}
}
}

对应的服务器接收到请求以后可以返回如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"jsonrpc": "2.0",
"id": 1,
"result": { // 返回结果
"protocolVersion": "2024-11-05", //MCP协议版本
"capabilities": { // 服务器支持的能力
"logging": {}, // 日志能力
"prompts": { // 提示能力
"listChanged": true
},
"resources": { // 资源能力
"subscribe": true,
"listChanged": true
},
"tools": { // 工具能力
"listChanged": true
}
},
"serverInfo": { // 服务器基本信息
"name": "ExampleServer",
"version": "1.0.0"
},
"instructions": "Optional instructions for the client"
}
}

成功初始化以后,客户端必须发送一个初始化通知来确认初始化完成。

1
2
3
4
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}

在初始化请求中,客户端必须发送它支持的协议版本。这应该是客户端支持的最新版本。

如果服务器支持请求的协议版本,它必须以相同的版本响应。否则,服务器必须使用它支持的另一个协议版本进行响应。这应该是服务器支持的最新版本。

如果客户端不支持服务器响应中的版本,它应该 断开连接。

操作阶段

这个阶段表示已经初始化完成了,可以双方开始通信了。

在这个阶段就可以开始上面说的交流了。

这里应该注意

  • 尊重协商的协议版本
  • 仅使用已成功协商的功能

关闭

在关闭阶段,一方(通常是客户端)干净地终止协议连接。没有定义特定的关闭消息-相反,应该使用底层传输机制来发出连接终止的信号:

​对于STDIO的传输方式来说,可以进行如下步骤关闭MCP:

  • 首先,关闭子进程(服务器)的输入流
  • 等待服务器退出,或者如果服务器没有在合理的时间内退出,则发送SIGTERM信号
  • 如果服务器SIGTERM之后的合理时间内没有退出,则发送SIGKILL信号
    服务器可以通过关闭到客户端的输出流并退出来启动关机。

对于 HTTP传输方式来说,通过关闭关联的 HTTP 连接就可以了。

总结

我们主要介绍了MCP的概念,MCP的通信协议、通信方式、生命周期。MCP到底是什么东西,实现了哪些内容,使用场景以及为什么要使用MCP。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

大厂中的Code Review

周末没事的时候,出来和朋友喝酒撸串,谈论国(chui)家(chui)大(niu)事(bi)。

喝到尽兴的时候,免不了一顿八卦盛宴。男人嘛,聚在一起吹牛逼的时候,无非就是江山美人这些事。

从中华上下五千年聊到他们公司新来了美女实习生

到这里,我已经脑补出来了下文,比如“领导强迫美女实习生”或是“我和美女不得不说的故事”这种狗血剧情。

朋友A说这个美女是挺漂亮,就是技术能力不太行。改代码的时候,误删除了一行代码,导致线上出现问题了。

这时候,朋友B估计也是喝上头了,开始为美女打抱不平了。

朋友B说,人家实习生,你们也不能要求太多吧,你们公司不要让她来我们公司好了。

好小子,算盘珠子都崩我脸上了。。。

我也奇怪的问了一句,你们厂没有代码Review吗?按理说,这种问题不应该发生吧。

朋友A说,有是有,可能大家都不太认真吧,谁都没发现这个问题。。。也就稀里糊涂的上线了。。。

这个问题,还是过了一个月才发现的。。。

朋友A继续吐糟。这代码Review啊,也不是个什么好活,感觉大家都不太乐意干。主要是

  • 增加了工作量
  • 出事了还要担责

比如这次,对于这两位代码Review的同事,肯定要被追问,为啥当时没有发现。

说实话,大部分人做代码Review的时候都不做不到很认真。这是没办法的事,毕竟你又要去了解需求逻辑,还要一行行代码的去过。那没几个小时是过不完的,同时你还要干自己的工作。哪有那么多时间去认真Review呢。

所以大部分人应该也就是粗略看一下,没啥问题就给过了。

朋友A也说,他们这次痛定思痛。决定优化代码Review的流程。

优化后的代码Review

角色定义如下:

  • 提交人:代码Review的发起人。写代码的人。
  • 审查人:负责进行代码Review工作的人。需要对代码进行Review。

对于提交人来说,需要提前拉会进行CR会议,正常需求来说至少提前1天拉会。并明确以下几点:

  • 需求文档
  • 技术方案
  • 代码CR地址
  • 主要修改逻辑点
  • 自测报告
  • 上线时间

这些要求可以帮助审查人快速了解自己需要做什么,重点放在哪里,避免浪费审查人的时间。

当然了这样做确实会增加一些提交人的工作量。但是还算好的。

毕竟,需求文档、技术方案、自测报告都是现成的。也就是写一下主要修改逻辑点而已。

对于提交人来说,还需要选择合适的审查人,至少有一个审查人是了解需求的。

对于审查人来说,同样有一些要求,主要为了让审查人能够认真的对待这件事情。

  • 代码逻辑是否正确(需要了解需求)
  • 并发处理是否正确
  • 单元测试是否覆盖了核心逻辑
  • 代码命名、格式、注释。是否能让人一下子就明白这个代码是干啥的。
  • 方法抽象是否合理
  • 异常处理
  • 性能问题
  • 安全问题

至少需要检查以上几点,如果有问题及时提出来,让提交人进行解决。

FAQ

Q:如果审查人没有时间怎么办?
A:审查人需要及时回复提交人,方便提交人选择新的审查人。

Q:如果审查人意见不一致怎么办?
A:建议选择奇数个审查人,少数服从多数。

Q:审查人是否会对技术方案提出问题?
A:审查人应该聚焦于技术实现,技术方案的问题应该在技术方案评审上提出而不是代码Review上提出。

Q:紧急需求没时间代码Review了怎么办?
A:紧急需求紧急处理,快速找人Review一下即可。

总结

对于所有人来说,代码Review好像都是一个巨大的问题,很多公司甚至难以推动执行。

我觉得根本的问题在于,代码Review的收益是一个长期的、利他的收益。对于大家来讲,最直观的就是工作量的增加,如果出问题了还要担责任。

大家的公司都是怎么做代码Review的呢?可以发出来大家一起聊聊。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

大厂中的Code Review

周末没事的时候,出来和朋友喝酒撸串,谈论国(chui)家(chui)大(niu)事(bi)。

喝到尽兴的时候,免不了一顿八卦盛宴。男人嘛,聚在一起吹牛逼的时候,无非就是江山美人这些事。

从中华上下五千年聊到他们公司新来了美女实习生

到这里,我已经脑补出来了下文,比如“领导强迫美女实习生”或是“我和美女不得不说的故事”这种狗血剧情。

朋友A说这个美女是挺漂亮,就是技术能力不太行。改代码的时候,误删除了一行代码,导致线上出现问题了。

这时候,朋友B估计也是喝上头了,开始为美女打抱不平了。

朋友B说,人家实习生,你们也不能要求太多吧,你们公司不要让她来我们公司好了。

好小子,算盘珠子都崩我脸上了。。。

我也奇怪的问了一句,你们厂没有代码Review吗?按理说,这种问题不应该发生吧。

朋友A说,有是有,可能大家都不太认真吧,谁都没发现这个问题。。。也就稀里糊涂的上线了。。。

这个问题,还是过了一个月才发现的。。。

朋友A继续吐糟。这代码Review啊,也不是个什么好活,感觉大家都不太乐意干。主要是

  • 增加了工作量
  • 出事了还要担责

比如这次,对于这两位代码Review的同事,肯定要被追问,为啥当时没有发现。

说实话,大部分人做代码Review的时候都不做不到很认真。这是没办法的事,毕竟你又要去了解需求逻辑,还要一行行代码的去过。那没几个小时是过不完的,同时你还要干自己的工作。哪有那么多时间去认真Review呢。

所以大部分人应该也就是粗略看一下,没啥问题就给过了。

朋友A也说,他们这次痛定思痛。决定优化代码Review的流程。

优化后的代码Review

角色定义如下:

  • 提交人:代码Review的发起人。写代码的人。
  • 审查人:负责进行代码Review工作的人。需要对代码进行Review。

对于提交人来说,需要提前拉会进行CR会议,正常需求来说至少提前1天拉会。并明确以下几点:

  • 需求文档
  • 技术方案
  • 代码CR地址
  • 主要修改逻辑点
  • 自测报告
  • 上线时间

这些要求可以帮助审查人快速了解自己需要做什么,重点放在哪里,避免浪费审查人的时间。

当然了这样做确实会增加一些提交人的工作量。但是还算好的。

毕竟,需求文档、技术方案、自测报告都是现成的。也就是写一下主要修改逻辑点而已。

对于提交人来说,还需要选择合适的审查人,至少有一个审查人是了解需求的。

对于审查人来说,同样有一些要求,主要为了让审查人能够认真的对待这件事情。

  • 代码逻辑是否正确(需要了解需求)
  • 并发处理是否正确
  • 单元测试是否覆盖了核心逻辑
  • 代码命名、格式、注释。是否能让人一下子就明白这个代码是干啥的。
  • 方法抽象是否合理
  • 异常处理
  • 性能问题
  • 安全问题

至少需要检查以上几点,如果有问题及时提出来,让提交人进行解决。

FAQ

Q:如果审查人没有时间怎么办?
A:审查人需要及时回复提交人,方便提交人选择新的审查人。

Q:如果审查人意见不一致怎么办?
A:建议选择奇数个审查人,少数服从多数。

Q:审查人是否会对技术方案提出问题?
A:审查人应该聚焦于技术实现,技术方案的问题应该在技术方案评审上提出而不是代码Review上提出。

Q:紧急需求没时间代码Review了怎么办?
A:紧急需求紧急处理,快速找人Review一下即可。

总结

对于所有人来说,代码Review好像都是一个巨大的问题,很多公司甚至难以推动执行。

我觉得根本的问题在于,代码Review的收益是一个长期的、利他的收益。对于大家来讲,最直观的就是工作量的增加,如果出问题了还要担责任。

大家的公司都是怎么做代码Review的呢?可以发出来大家一起聊聊。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

OpenAI发布新文生图模型,免费、逼真到难以分辨

25年3月底,OpenAI进行了技术直播,对GPT-4o和Sora进行了重大更新,提供了全新文生图(Text-to-Image)模型。

除了文生图之外,该模型还支持自定义操作、连续发问、风格转换、图像PPT等超实用功能,例如,更改图片的宽高比、图像物体中的角度,用十六进制代码指定精确颜色等。

尤其是生成图像的逼真度,肉眼几乎看不出任何破绽,在精细度、细节和文本遵循方面非常出色,可以媲美甚至在某些功能超过该领域的头部平台Midjourney。

OpenAI文生图的优势

ChatGPT的文生图功能自推出以来,迅速引起了广泛关注,其主要优势包括:​

  • 精准的文本渲染能力:​ChatGPT的文生图功能能够准确理解用户的文本描述,并在生成的图像中清晰地呈现文本内容。这对于需要在图像中包含特定文字信息的场景,如制作菜单、信息图表或徽标等,具有重要意义。
  • 严格遵循用户指令:​该功能能够精确执行用户的指令,生成符合预期的图像。例如,用户可以要求生成特定风格、构图或包含特定元素的图像,ChatGPT会尽力满足这些要求。 ​
  • 深度知识调用与创意拓展:​ChatGPT不仅能够利用其广泛的知识库生成图像,还能在创意上进行拓展。例如,用户可以要求生成具有特定艺术风格或融合多种元素的图像,ChatGPT能够提供多样化的创意选项。 ​
  • 高效的图像生成速度:​在实际测试中,ChatGPT能够在约30秒内生成高质量的图像,速度远超预期。这使得用户能够迅速获取所需的图像,提升了创作效率。 ​

需要注意的是,尽管ChatGPT的文生图功能在多个方面表现出色,但在处理非拉丁语系文字时仍存在一定局限性。

文生图功能的发展历史

文生图技术其实很早就有了,只是最近随着大模型的火热以及一些文生图的应用,迎来了一波爆发。

我们也来看一下文生图的历史。​

  1. 初期探索(2014年以前)
    在深度学习兴起之前,文生图的尝试主要依赖于基本的图像处理技术,如将现有图像素材拼贴在一起,形成类似拼贴画的效果。​

  2. 深度学习引入(2014年-2018年)
    随着卷积神经网络(CNN)等深度学习模型的成功应用,研究者开始尝试使用神经网络生成图像。​2015年,多伦多大学的研究人员提出了alignDRAW模型,这是第一个现代文生图模型,能够根据文本序列生成图像。​然而,这些早期模型生成的图像质量有限,通常较为模糊。​

  3. GAN和Transformer的应用(2016年-2021年)
    2016年,研究者开始将生成对抗网络(GAN)应用于文生图任务,取得了更好的生成效果。​2019年,Transformer架构被引入文生图模型,进一步提升了生成质量。​2021年,OpenAI发布了DALL·E模型,采用Transformer架构,能够根据文本描述生成高质量的图像。​

  4. 扩散模型的兴起(2021年至今)
    2021年,以扩散模型(Diffusion Model)为基础的文生图技术取得了显著进展。​这种模型通过逐步添加噪声并学习反向过程,能够生成更高质量的图像。​例如,Stable Diffusion模型在2022年发布,提供了高质量且多样化的图像生成能力。​

openAI文生图功能演示

提示词如下:

1
帮我生成一个图片,是一座科幻风格的城市,路上有一些未来的车辆,空中还有浮空设备,路上有一些行人,有机械改造的行人

来看一下生成的图片,很高清的一张图片,也符合描述。

ai

再换成其他的一些提示词。

1
帮我生成一个图片,是一座赛博朋克风格的城市,路上有一些车辆,空中还有浮空船,路上有一些行人,有的行人装了机械假肢等。夜晚。

看一下效果。

ai

可以看到能实现的效果还是很棒的,我们在看一下图里面加上文字呢?

提示词

1
帮我生成一个五一劳动节的海报,标题是“五一劳动节快乐”,背景是学校,学校门口有一些卖东西的学生

可以看到生成的图里面对于文字的支持还是比较好的,虽然有一些瑕疵,但是比其他的文生图软件要好一些。

ai

再来试试英文的文字呢?

提示词

1
帮我生成一个五一劳动节的海报,标题是“5.1 vacation happy”,背景是学校,学校门口有一些卖东西的学生

图片效果

ai

总结

ChatGPT的文生图功能的推出,标志着内容创作进入了一个新的时代。​无论是微信公众号运营者,还是自媒体创作者,都可以借助这一功能,提升内容质量和创作效率。​随着技术的不断发展,未来的文生图功能将更加智能化、多样化,为创作者提供更多可能性。

而且,OpenAI作为大模型界的老大,它的更新速度也是很快的,说不定过不了多久,其他的文生图软件就要被甩在后面了。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

MySQL零基础教程

本教程为零基础教程,零基础小白也可以直接学习,有基础的可以跳到后面的原理篇学习。
基础概念和SQL已经更新完成。

接下来是应用篇,应用篇的内容大致如下图所示。

概念学习

表设计

表设计可以聊的点其实是比较多的,这个也比较看具体的业务、流量等。

比如,某一个字段是否应该放在这张表里?一张表里应该有哪些字段?如何设计字段类型?

甚至于,如何设计字段的顺序?

这里很多人不知道的一个点在于,字段的顺序也会影响性能,至于为什么,这个就偏低层一些了,下面会讲到。

想要做表设计,那你首先需要知道是什么,所以我们先来看看表到底是什么东西。

表是什么?

有的人会说,表就是Navicat上看到的一张表呗,还能是什么啊?

还有的人说,表就是一行一行数据组成的。

其实说的都对,但是这是逻辑上的表,也就是mysql给我们展现出来的表。

大家有没有想过,表的物理形式是什么,msyql如何将它转化成逻辑上的表方便我们查看呢?

接下来进行揭秘吧!

如何表示磁盘上文件的数据

数据库的数据最终以文件的形式放在磁盘中。通过文件读写将数据读写到文件中。文件有特定的格式,具体的内容有数据库进行解析然后展示在数据库中。这就是storage manager or storage engine

比如MySQL数据库的存储引擎,常用的就是InnoDB存储引擎了,他就是负责干这个事情的。

storage manager负责文件的读写工作。所有的文件(不管是一个或者多个)以 page 的形式存储,管理多个 page 组成的集合。

一个page就是一个固定大小的数据块。page 可以保存任何东西,tupe, metadata, indexes, log等等。每个page有唯一的ID,是page ID

有些page要求是独立的,自包含的(self-contained)。比如mysql的InnoDB。因为这样的话一个表的元数据和本身的数据内容在一起,如果发生问题的话,可以找回元数据和数据。如果元数据和数据在不同的page中,如果发生问题导致元数据的page丢失,那么数据则恢复不了了。

indirection layer记录page ID的相对位置,方便找到对应的偏移量。这样page目录就能找到对应的page。

不同的DBMS对于文件在磁盘上的存储方式不一样,有下面几种

  • 堆存储
  • 树存储
  • 有序文件存储(ISAM)
  • hashing文件存储

像MySQL数据库使用的就是堆存储的方式了,所以我们主要看一下什么是堆存储。

堆存储具有以下特点:

  • 无序的,保存的顺序和存储的顺序无关。
  • 需要读写page
  • 遍历所有的page
  • 需要元数据记录哪些是空闲的page,哪些是已经使用的page。
  • 使用 page directory 方式来记录文件位置。

page directory:说白了就是目录,记录了一些映射关系,在代码里面其实就是个Map。

  • 存储page ID和所在位置的关系
  • 存储page的空闲空间信息

大体结构如图所示:
010

page header:每个page里面都有一段空间用来存储这个page的一些相关信息,这段空间就叫做page header。

  • page 大小
  • checksum 校验和
  • DBMS版本信息
  • 事务可见性
  • 压缩信息

看到这里,其实你就明白了,我们在聊索引的时候,很多时候会说索引的叶子节点是一个page。不知道大家当时有没有疑问,这个page到底是什么?

在这里我就给你讲明白,这个page到底是个什么东西。

每个page里面除了包含page header以外,就是page data了。

这些page里面的数据就是我们能看到的一行行的数据。

我们通常称这样按照一行行数据来存储的数据库叫做行式数据库。比如MySQL就是。

除此之外还有一些按照一列列数据存储的列式数据库

数据表示

我们在做表设计的时候还会考虑到字段的类型,那么如何选择类型呢?

这些类型的底层存储有什么区别?

上面我们已经知道了一个page里面存储了一行行的数据。那么一行数据是多长呢?

这个一行数据的长度自然就是这一行数据所有字段长度的总和了。

字段的长度分为两种类型

  • 固定长度的字段
  • 可变长度的字段

固定长度的字段有下面这些:

  • Int:整型,当然了这里也包括TinyIntSmallIntBigInt等。不同长度的Int类型只是所占的字节数不一样而已。
  • Char:字符型,Char类型就是个字符串,你创建的时候给了多长,就是一个多长的字符串。
  • Decimal:定点小数,也是最常用的小数类型。虽然运算速度慢一些,但是精度高。Flout和Double虽然也是小数,运算速度快但是会有精度丢失的问题。
  • Date:时间类型,包括DateTimeTime等。固定长度用来存储时间。TimeStamp类型已经快要达到上限了,不要再使用了。

可变长度的字段有下面这些,他们的长度会存储在header里面:

  • Varchar: 可变的字符串类型,存储时只占用实际需要的字节数,对比Char类型而言,更加节省空间。
  • VarBinary: 可变长度的二进制字符串类型,类似于 VARCHAR,但存储的是二进制数据。
  • Text:可变长度的长文本类型,存储时占用的字节数取决于实际内容。
  • BLOB:可变长度的二进制数据类型,存储时占用的字节数取决于实际内容。

表设计中尤其要注意的几点:

不要去使用TimeStamp类型了,使用DateTime或者Bigint来存储时间。
其次,避免去使用Flout和Double类型,而是使用Decimal来存储小数,或者使用Int类型来存储。
避免使用大值存储,比如Text和blob类型,而是使用Varchar来代替。

通常说的大值(large values)也就是里面存储的内容过多。

比如我们有一个字段是Text类型,这个字段的数据非常大,占据了Page的一半,再加上其他的数据,那么我们一个Page里面只能存储一行数据了,Page里面剩下的空间就会浪费掉了。

对于这种情况,数据库的设计者也考虑到了,所以他们实际存储的是一个指针,这个指针指向另外一个Page页面。将这个字段的内容存储到另外一个单独的Page里面。这个单独的Page页面叫做Overflow Page

虽然这样解决了上面的问题,但是也引入了新的复杂度,比如对于这个额外的Page页面的维护管理。

NULL存储,表设计中还有重要的一点处理就是NULL值,因此我们通常把字段设置为NOT NULL类型来避免NULL值。因为NULL值有如下问题:

  • 行数据库通常是在Header里面增加bit map来判断是否是null
  • 列数据库通常使用占位符来标识NULL
  • 在每个属性前面增加bit来标识是否是NULL,这么做会破坏对齐,或增加存储空间,MySQL曾使用这个方法,后来抛弃了这个方法。
  • NULL == NULL 是 NULL, NULL is NULL 是 true

page是什么样子的

上面我们讲完了一行数据是什么样子的,那么一个Page里面又是什么样子的呢?

一般想法,就是一行贴着一行直接存储,新的行数据直接在后面追加,但是对于可变数据长度很难管理。

  • 记录page数,也就是page内部可插入的偏移量
  • 一个一个tupe按照顺序存储

007

所以,page内部,通常不使用上面那种,而使用的是slotted pages

  • slotted pages
    • slot array 存储插槽信息的偏移量,通过他找到对应的行数据
    • 支持可变长度的行数据
    • 但是会产生一些碎片空间,因为太小,一行数据放不下。
    • 压缩可以去除碎片空间,但是压缩的时候这个page就不能读写了。

008

mysql innodb 压缩

innodb 在写入的时候可以不解压,但是读取的时候会先在buffer pool中解压在读取。因此Mysql innodb的压缩的好处是提升空间利用率,减少了磁盘IO,缺点是读取的时候需要解压,因此增加了这部分的时间和CPU功耗以及解压以后会占用更多的内存空间。
innodb 默认page 是 16KB,可以压缩到1/2/4/8KB。

016

数据对齐

现代CPU是64位对齐,创建表以后,DBMS会自动的将数据进行对齐存储,不过,如果在创建表的时候考虑对齐,可以优化速度和存储空间。

012

还记得我们上面说过的吗,字段的顺序也是会影响性能的,如果你的字段顺序能满足数据对齐的要求,那么就可以避免空间的浪费,同一个page里面就可以存储更多的行数据,也就意味着我们每次获取一个page的时候,能从磁盘拿到更多的数据,因此我们获取大量数据的时候,IO次数就会减少,从而起到提升性能的效果。

实战

表设计要考虑的其实是比较多的,相信你看完上面的内容,对于如何设计表,应该有了一些自己的方法论。

这里再讲一下实战中需要注意的事情吧。

再开始设计表之前,我们肯定要先分析需求,然后才能知道我们需要存储哪些数据。

比如,我们要做一个招聘网站,那么我们需要存储发布的职位信息。

职位信息都放到一个表里面吗?

职位信息其实还挺多的,不光是常见的职位名称、职位JD。还会涉及到比如职级信息、工资信息、面试轮次,是否支持视频面试、学历要求、学校要求、工作经历要求大厂等。

这里面有一个冷热数据的概念。

比如说,有一部分信息是经常要查询的,比如职位名称、学历要求、工作年限要求、工资、公司信息、招聘人头像、名称、标签等。

首先、这些数据肯定放在多个表里面的,比如招聘人头像、名称是用户表的数据、标签信息是标签表的数据、公司信息是公司表的数据。但是其中职位名称、学历要求、工作年限、工资是属于职位的基本信息。而且是在职位列表、IM聊天中的职位卡片、职位详情、职位浏览、职位收藏等多个维度高曝光的职位信息。

那么就代表这些职位信息属于热数据,会被经常一起查询。因此他们放到一个表里面是没有问题的。

剩下的职位信息我们可以分成两类。

  1. 在职位详情页面首屏展示的内容或者一些强依赖的内容,这些数据也可以和上面的放在一个表里面。
  2. 一些其他不经常查询的数据,这些数据可以放在另外一个表里面。

这样的话,每次查询热数据的时候从第一张职位信息表获取,查询到的page里面都是包含的有用的信息,就可以减少IO次数。

因此,我们可以设计两个表来存储职位信息。

  • 职位信息表:存储职位的主要数据、热数据、提升查询速度。
  • 职位扩展信息表:存储职位的次要数据、冷数据、只在需要的时候进行查询。

总结

授人以鱼不如授人以渔,相信经过上面的学习,你已经具备了一定的表设计的能力了。

这里讲的主要是表的设计,而不是整个数据的设计。因为还缺少了一些,比如索引该如何设计?

如何保证大量数据的查询?

其实对于MySQL来说上索引以后就可以查询百万级的数据了,但是对于非常要求速度和更高量级的数据而言。还可以使用一些其他的方法,比如使用列式数据库来进行查询。

这样的话可能还会涉及数据同步、数据清洗等等。

相信你学完我的整个系列以后、对于更高量级的数据设计也会有一定的经验的。

如果在面试中遇到类似的问题,你也可以游刃有余的回答面试官。

在学习的过程中,我们也要做到知其然也知其所以然

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习