☰
Re:Mysql数据库基础篇(六):面试必问的MySQL基础:CRUD操作、执行顺序与Redo/Undo/Binlog
2026/10/26 17:00:56 网站建设 项目流程

◆ 博主名称: 小此方-CSDN博客
大家好,欢迎来到小此方的博客。
⭐️Linux系列个人专栏: 【主题曲】Mysql
⭐️此方的GitHub: github_此方
⭐️Re系列专栏:我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)

文章目录

  • 概要&序論
  • 一、Create 插入数据
    • 1.1 单行数据 + 全列插入
    • 1.2 多行数据 + 指定列插入
    • 1.3 插入否则更新
    • 1.4 替换
  • 二、Retrieve 数据查询
    • 2.1 基础列查询与表达式
      • 2.1.1 全列查询
      • 2.1.2 指定列查询
      • 2.1.3 查询字段为表达式
    • 2.2 别名与去重
      • 2.2.1 为查询结果指定别名
      • 2.2.2 结果去重
  • 三、WHERE 条件过滤
    • 3.1 基础条件查询实战
    • 3.2 复杂条件与表达式
    • 3.3 NULL 值的查询与安全比较
  • 四、结果排序与分页
    • 4.1 ORDER BY 结果排序
      • 4.1.1 单字段排序与 NULL 处理
      • 4.1.2 多字段排序
      • 4.1.3 使用表达式与别名排序
      • 4.1.4 结合 WHERE 排序
    • 4.2 LIMIT 筛选分页
      • 4.2.1 分页查询实战
  • 五、Update 数据更新
    • 5.1 基础单列与多列更新
    • 5.2 基于原值计算与排序限制更新
    • 5.3 全表更新与高危操作警告
  • 六、Delete 数据删除与截断
    • 6.1 条件删除
    • 6.2 删除整表数据与自增陷阱
    • 6.3 TRUNCATE 截断表
  • 七、进阶玩法:插入查询结果与去重实战
    • 7.1 准备测试数据
    • 7.2 核心思路与实操
  • 八、深入探究:SQL语句的执行先后顺序
    • 8.1 核心执行步骤拆解
    • 8.2 LIMIT 的显示本质与综合应用
  • 九、MySQL的三大核心日志
    • 9.1 Binlog:记录历史的“流水账”
    • 9.2 Redo Log:防止断电的“小黑账”
    • 9.3 Undo Log:时光倒流的“撤销键”

概要&序論

Hello大家好,我是此方,上两篇我们讲解了表的字段类型和表的约束,本文开始,我们要讲解的是表的增删查改,内容非常丰富。总体难度依旧不大。好,我们开始吧!

一、Create 插入数据

在数据库的日常操作中,最核心的动作可以概括为CRUD,也就是Create(创建)、Retrieve(读取)、Update(更新)和Delete(删除)。这一部分我们重点来学习Create操作,也就是如何向表中插入数据。

插入数据的基本语法如下。

INSERT[INTO]table_name[(column[,column]...)]VALUES(value_list)[,(value_list)]...value_list:value,[,value]...

为了方便演示,我们先创建一张学生表,其中包含主键自增的id、非空且唯一的学号sn、非空的姓名name以及可以为空的qq。

CREATETABLEstudents(idINTUNSIGNEDPRIMARYKEYAUTO_INCREMENT,snINTNOTNULLUNIQUECOMMENT'学号',nameVARCHAR(20)NOTNULL,qqVARCHAR(20));

1.1 单行数据 + 全列插入

全列插入是指不指定具体的列名,直接按照表中定义的列顺序插入数据。需要特别注意的是,value_list 的数量必须和定义表的列的数量及顺序一致。

INSERTINTOstudentsVALUES(100,10000,'唐三藏',NULL);INSERTINTOstudentsVALUES(101,10001,'孙悟空','11111');

在这里,我们在插入时指定了id的值。当然,如果遇到主键是自增的情况,在插入时也可以不用指定id,但这时候就需要明确指定插入数据到哪些列了,MySQL会自动使用默认值进行自增。

1.2 多行数据 + 指定列插入

在实际开发中,为了提高效率,我们通常会一次性插入多条记录,并且可以只指定部分列。同样的,value_list 的数量必须和指定列的数量及顺序一致。

INSERTINTOstudents(id,sn,name)VALUES(102,20001,'曹孟德'),(103,20002,'孙仲谋');

在这个例子中,我们没有指定qq列,MySQL会自动将其填充为NULL。

1.3 插入否则更新

在实际业务中,经常会遇到由于主键或者唯一键对应的值已经存在而导致插入失败的情况。比如主键冲突或唯一键冲突。

-- 主键冲突INSERTINTOstudents(id,sn,name)VALUES(100,10010,'唐大师');-- ERROR 1062 (23000): Duplicate entry '100' for key 'PRIMARY'

面对这种冲突,如果我们希望发生冲突时进行同步更新,可以选择性的使用ON DUPLICATE KEY UPDATE语法。

INSERTINTOstudents(id,sn,name)VALUES(100,10010,'唐大师')ONDUPLICATEKEYUPDATEsn=10010,name='唐大师';

执行上述语句后,MySQL 会根据受影响的行数返回不同的结果,我们可以通过SELECT ROW_COUNT();函数来获取。

  1. 返回 0 表示表中有冲突数据,但冲突数据的值和 update 的值相等。
  2. 返回 1 表示表中没有冲突数据,数据被正常插入。
  3. 返回 2 表示表中有冲突数据,并且数据已经被更新。

1.4 替换

除了更新,MySQL 还提供了一种替换的语法REPLACE INTO。它的逻辑非常直接:如果主键或者唯一键没有冲突,则直接插入;如果主键或者唯一键发生冲突,则先删除冲突的记录,然后再执行插入。

REPLACEINTOstudents(sn,name)VALUES(20001,'曹阿瞒');

由于学号 20001 已经存在(曹孟德),这条语句会先删除原来的记录,再插入新的“曹阿瞒”记录,因此返回的受影响行数为 2。

二、Retrieve 数据查询

在数据库操作中,查询(Retrieve)是我们使用最频繁的功能。SELECT 语句的完整语法相对复杂,包含条件过滤、排序和分页等,其基本结构如下。

SELECT[DISTINCT]{*|{column[,column]...}[FROMtable_name][WHERE...][ORDERBYcolumn[ASC|DESC],...]LIMIT...

为了后续更好地演示查询效果,我们先创建一张成绩表并插入一些测试数据。

-- 创建表结构CREATETABLEexam_result(idINTUNSIGNEDPRIMARYKEYAUTO_INCREMENT,nameVARCHAR(20)NOTNULLCOMMENT'同学姓名',chinesefloatDEFAULT0.0COMMENT'语文成绩',mathfloatDEFAULT0.0COMMENT'数学成绩',englishfloatDEFAULT0.0COMMENT'英语成绩');-- 插入测试数据INSERTINTOexam_result(name,chinese,math,english)VALUES('唐三藏',67,98,56),('孙悟空',87,78,77),('猪悟能',88,98,90),('曹孟德',82,84,67),('刘玄德',55,85,45),('孙权',70,73,78),('宋公明',75,65,30);

2.1 基础列查询与表达式

2.1.1 全列查询

全列查询使用星号(*)来代表所有列。

SELECT*FROMexam_result;

虽然全列查询写起来很方便,但在通常情况下并不建议使用*进行全列查询。主要有两个原因:一是查询的列越多,意味着需要传输的数据量越大,增加网络开销;二是可能会影响到索引的使用。

2.1.2 指定列查询

指定列查询可以提高查询效率,直接写明需要的字段即可。

SELECTid,name,englishFROMexam_result;

需要说明的是,指定列的顺序不需要按定义表的顺序来,你可以根据自己的需要任意调整。

2.1.3 查询字段为表达式

在 SELECT 语句中,查询的字段不仅可以是从表中读出的列,还可以是表达式。

表达式可以不包含字段,比如直接查询一个常量。

SELECTid,name,10FROMexam_result;

表达式也可以包含一个字段,比如给所有人的英语成绩加上10分显示。

SELECTid,name,english+10FROMexam_result;

表达式还可以包含多个字段,比如计算每个人的总分。

SELECTid,name,chinese+math+englishFROMexam_result;

2.2 别名与去重

2.2.1 为查询结果指定别名

当查询结果是一个表达式时,列名会显示为表达式的字符串,不太美观。我们可以使用 AS 关键字为列起一个别名,语法为SELECT column [AS] alias_name [...] FROM table_name;。其中 AS 可以省略。

SELECTid,name,chinese+math+english 总分FROMexam_result;

2.2.2 结果去重

在使用 SELECT 查询时,有时会遇到重复的数据。比如我们单独查询 math 字段,会发现有两个人都是 98 分。

SELECTmathFROMexam_result;

如果我们需要对结果进行去重,可以使用 DISTINCT 关键字。加上 DISTINCT 后,查询结果就会去掉重复的记录。

SELECTDISTINCTmathFROMexam_result;

三、WHERE 条件过滤

前面的查询都是把整张表的数据都拉取出来,但在实际业务中,我们往往需要根据特定的条件筛选出符合要求的数据。这就需要在 SELECT 语句中使用 WHERE 子句。

在编写条件之前,我们需要熟悉 MySQL 中的比较运算符和逻辑运算符。比较运算符包括大于>、大于等于>=、小于<、小于等于<=、等于=、不等于!=和<>。还有用于范围匹配的BETWEEN a0 AND a1(包含边界),用于多选匹配的IN (option, ...),用于空值判断的IS NULL和IS NOT NULL,以及用于模糊匹配的LIKE。

在使用等于号=时需要注意,它是 NULL 不安全的,例如NULL = NULL的结果是 NULL,而不是 TRUE。如果要进行 NULL 安全的比较,需要使用<=>,例如NULL <=> NULL的结果是 TRUE(1)。在模糊匹配中,%表示任意多个(包括 0 个)任意字符,而_表示严格的任意一个字符。

逻辑运算符主要有三个:AND表示多个条件必须同时满足才是 TRUE;OR表示任意一个条件满足即为 TRUE;NOT表示对条件取反。

3.1 基础条件查询实战

我们通过几个具体的案例来演示这些条件的使用方法。

  1. 查询英语不及格的同学及英语成绩,可以直接使用基本比较运算符。
SELECTname,englishFROMexam_resultWHEREenglish<60;
  1. 查询语文成绩在 [80, 90] 分的同学及语文成绩,可以使用 AND 连接条件,也可以使用 BETWEEN 简化。
SELECTname,chineseFROMexam_resultWHEREchinese>=80ANDchinese<=90;SELECTname,chineseFROMexam_resultWHEREchineseBETWEEN80AND90;
  1. 查询数学成绩是 58 或者 59 或者 98 或者 99 分的同学及数学成绩,可以使用 OR 连接,或者使用 IN 更加简洁。
SELECTname,mathFROMexam_resultWHEREmath=58ORmath=59ORmath=98ORmath=99;SELECTname,mathFROMexam_resultWHEREmathIN(58,59,98,99);
  1. 查询姓孙的同学,可以使用 LIKE 加上百分号进行前缀匹配,查询孙某同学,则使用 LIKE 加上下划线。
SELECTnameFROMexam_resultWHEREnameLIKE'孙%';SELECTnameFROMexam_resultWHEREnameLIKE'孙_';
  1. 查询语文成绩好于英语成绩的同学,WHERE 条件中的比较运算符两侧都可以是表的字段。
SELECTname,chinese,englishFROMexam_resultWHEREchinese>english;

3.2 复杂条件与表达式

除了简单的字段比较,WHERE 条件中还可以使用表达式,甚至结合多个逻辑运算符进行综合查询。

比如查询总分在 200 分以下的同学。这里有一个非常重要的细节:别名不能用在 WHERE 条件中。虽然我们在 SELECT 中给总分起了别名,但在 WHERE 中必须老老实实把表达式写全。

SELECTname,chinese+math+english 总分FROMexam_resultWHEREchinese+math+english<200;

查询语文成绩 > 80 并且不姓孙的同学,这是 AND 与 NOT 的经典结合。

SELECTname,chineseFROMexam_resultWHEREchinese>80ANDnameNOTLIKE'孙%';

最后来看一个综合性查询:要求孙某同学,否则要求总成绩 > 200 并且语文成绩 < 数学成绩 并且英语成绩 > 80。这里利用了 OR 和 AND 的组合,注意利用括号来保证逻辑的优先级。

SELECTname,chinese,math,english,chinese+math+english 总分FROMexam_resultWHEREnameLIKE'孙_'OR(chinese+math+english>200ANDchinese<mathANDenglish>80);

3.3 NULL 值的查询与安全比较

在表数据中,经常会存在一些 NULL 值。要查询某个字段已知的记录,不能使用等于号,必须使用IS NOT NULL。

SELECTname,qqFROMstudentsWHEREqqISNOTNULL;

我们前面提到,=和<=>在处理 NULL 时的表现不同,可以通过直接 SELECT 来验证。

-- 结果为 NULLSELECTNULL=NULL,NULL=1,NULL=0;-- 结果为 1, 0, 0SELECTNULL<=>NULL,NULL<=>1,NULL<=>0;

四、结果排序与分页

在查询数据时,如果没有指定排序规则,数据库返回的顺序往往是未定义的,永远不要依赖默认的返回顺序。如果我们希望查询结果按照特定的规则排列,就需要使用 ORDER BY 子句。如果结果集太大,还需要配合 LIMIT 进行分页。

4.1 ORDER BY 结果排序

ORDER BY 的基本语法为ORDER BY column [ASC|DESC], [...]。其中 ASC 代表升序(从小到大),是默认的排序方式;DESC 代表降序(从大到小)。

4.1.1 单字段排序与 NULL 处理

我们来看一个简单的例子,查询同学及数学成绩,并按数学成绩升序显示。

SELECTname,mathFROMexam_resultORDERBYmath;

当排序的字段中存在 NULL 值时,MySQL 在排序时会把 NULL 视为比任何值都小。所以在升序排序时,NULL 会出现在最上面;在降序排序时,NULL 会出现在最下面。

-- NULL 视作比任何值都小,升序出现在最上面SELECTname,qqFROMstudentsORDERBYqq;-- NULL 视作比任何值都小,降序出现在最下面SELECTname,qqFROMstudentsORDERBYqqDESC;

4.1.2 多字段排序

在实际场景中,我们往往需要根据多个字段进行排序。多字段排序的优先级随书写顺序决定。比如查询同学各门成绩,依次按数学降序、英语升序、语文升序的方式显示。

SELECTname,math,english,chineseFROMexam_resultORDERBYmathDESC,english,chinese;

4.1.3 使用表达式与别名排序

ORDER BY 子句中不仅可以使用表中已有的列,还可以直接使用表达式,或者使用 SELECT 中定义的列别名。查询同学及总分,由高到低,以下两种写法都是可行的。

-- 使用表达式SELECTname,chinese+english+mathFROMexam_resultORDERBYchinese+english+mathDESC;-- 使用别名SELECTname,chinese+english+math 总分FROMexam_resultORDERBY总分DESC;

4.1.4 结合 WHERE 排序

ORDER BY 通常与 WHERE 子句结合使用,先过滤出符合条件的数据,再对结果进行排序。查询姓孙的同学或者姓曹的同学数学成绩,结果按数学成绩由高到低显示。

SELECTname,mathFROMexam_resultWHEREnameLIKE'孙%'ORnameLIKE'曹%'ORDERBYmathDESC;

4.2 LIMIT 筛选分页

当表中的数据量非常庞大时,一次性查询出所有结果可能会导致数据库卡死或网络传输超时。这时就需要使用 LIMIT 子句来限制返回的行数。起始下标是从 0 开始的。

语法有三种写法:LIMIT n表示从 0 开始筛选 n 条结果;LIMIT s, n表示从 s 开始筛选 n 条结果;LIMIT n OFFSET s同样表示从 s 开始筛选 n 条,但比第二种用法更明确,建议使用。

这里有一个非常实用的建议:在对未知表进行查询时,最好加上一条LIMIT 1,这样可以避免因为表中数据过大,查询全表数据导致数据库卡死。

4.2.1 分页查询实战

假设我们需要按 id 进行分页,每页 3 条记录,分别查询第 1、2、3 页。

-- 第 1 页SELECTid,name,math,english,chineseFROMexam_resultORDERBYidLIMIT3OFFSET0;
-- 第 2 页SELECTid,name,math,english,chineseFROMexam_resultORDERBYidLIMIT3OFFSET3;
-- 第 3 页,如果结果不足 3 个,不会有影响SELECTid,name,math,english,chineseFROMexam_resultORDERBYidLIMIT3OFFSET6;

五、Update 数据更新

在实际业务中,数据往往不是一成不变的,我们需要对已经存在的记录进行修改。UPDATE 语句就是用来对查询到的结果进行列值更新的。它的基本语法如下。

UPDATEtable_nameSETcolumn=expr[,column=expr...][WHERE...][ORDERBY...][LIMIT...]

5.1 基础单列与多列更新

最常见的更新操作是根据特定的条件,将某个字段修改为具体的值。比如我们将孙悟空同学的数学成绩变更为 80 分。

-- 查看原数据,数学成绩为 78SELECTname,mathFROMexam_resultWHEREname='孙悟空';-- 数据更新UPDATEexam_resultSETmath=80WHEREname='孙悟空';

执行更新后,MySQL 会返回Rows matched: 1 Changed: 1,表示匹配到了一行且成功修改了一行。

我们也可以在一次更新中修改多个列的值,中间用逗号隔开即可。比如将曹孟德同学的数学成绩变更为 60 分,语文成绩变更为 70 分。

UPDATEexam_resultSETmath=60,chinese=70WHEREname='曹孟德';

5.2 基于原值计算与排序限制更新

有时候我们并不是想把字段更新为一个固定的值,而是希望在原值的基础上进行增减。比如将总成绩倒数前三的 3 位同学的数学成绩加上 30 分。

这里有一个非常重要的语法细节:MySQL 不支持math += 30这种语法,必须写成math = math + 30。同时,UPDATE 语句也可以结合 ORDER BY 和 LIMIT 使用。

UPDATEexam_resultSETmath=math+30ORDERBYchinese+math+englishLIMIT3;

需要注意的是,在进行带有 ORDER BY 的 UPDATE 时,更新的逻辑是先排序找出符合条件的行,然后对这些行进行更新。由于总分在更新数学成绩后会发生变化,如果我们想看更新后的总成绩,必须重新查询,不能直接依赖刚才排序的结果。

5.3 全表更新与高危操作警告

如果我们在 UPDATE 语句中没有写 WHERE 子句,那么更新操作将会作用于整张表的所有记录。

比如将所有同学的语文成绩更新为原来的 2 倍。

UPDATEexam_resultSETchinese=chinese*2;

执行这条语句后,全表 7 条记录的语文成绩全部翻倍。这里要特别警告:更新全表的语句一定要慎用!在实际开发中,如果忘记加 WHERE 条件或者条件写错,极易造成严重的生产事故(比如误把全公司员工的工资都清零了)。在执行 UPDATE 之前,最好先用同样的 WHERE 条件写一个 SELECT 语句确认一下影响范围。

六、Delete 数据删除与截断

说完了新增、查询和更新,最后我们来看看删除操作。DELETE 语句用于从表中删除数据,其基本语法与 UPDATE 类似,可以结合 WHERE、ORDER BY 和 LIMIT 使用。

DELETEFROMtable_name[WHERE...][ORDERBY...][LIMIT...]

6.1 条件删除

最常见的操作是根据特定条件删除指定的记录。比如我们想要删除孙悟空同学的考试成绩,先查看原数据确认存在,然后执行删除。

-- 查看原数据SELECT*FROMexam_resultWHEREname='孙悟空';-- 删除数据DELETEFROMexam_resultWHEREname='孙悟空';Query OK,1rowaffected(0.17sec)

执行成功后,再次查询该条记录,就会返回Empty set。

6.2 删除整表数据与自增陷阱

如果我们不带 WHERE 子句,直接写DELETE FROM for_delete;,就会删除整张表的所有数据。这里有一个非常关键的细节需要大家注意。

我们准备一张测试表for_delete,插入 3 条数据后全部删除,然后再插入一条新数据。

CREATETABLEfor_delete(idINTPRIMARYKEYAUTO_INCREMENT,nameVARCHAR(20));INSERTINTOfor_delete(name)VALUES('A'),('B'),('C');DELETEFROMfor_delete;-- 再插入一条数据INSERTINTOfor_delete(name)VALUES('D');

查看数据你会发现,新插入的 ‘D’ 其 id 是 4,而不是 1。如果我们查看表结构SHOW CREATE TABLE for_delete\G,会发现里面记录了AUTO_INCREMENT=5。这说明DELETE 操作虽然删除了数据,但并不会重置 AUTO_INCREMENT 计数。

6.3 TRUNCATE 截断表

如果我们需要快速清空一张表,并且让自增主键重新从 1 开始,就可以使用 TRUNCATE 语句。

TRUNCATE[TABLE]table_name

与 DELETE 相比,TRUNCATE 有三个显著的注意事项。

  1. 只能对整表操作,不能像 DELETE 一样针对部分数据进行删除。
  2. 实际上 MySQL 不对数据操作,所以比 DELETE 更快,但是 TRUNCATE 在删除数据的时候,并不经过真正的事物,所以无法回滚。
  3. 会重置 AUTO_INCREMENT 项。

我们同样用for_truncate表验证一下。插入数据后执行TRUNCATE for_truncate;,注意看返回结果,显示0 rows affected,因为它并没有逐行去删数据。此时再插入数据,id 就会从 1 重新开始。

七、进阶玩法:插入查询结果与去重实战

前面我们学到的 INSERT 都是手动指定 VALUES,其实 INSERT 还可以配合 SELECT 语句,将查询到的结果直接插入到另一张表中。

INSERTINTOtable_name[(column[,column...])]SELECT...

这个语法在数据迁移和处理重复数据时非常有用。我们来看一个经典的案例:如何删除表中的重复记录,并且只保留一份。

7.1 准备测试数据

我们创建一张duplicate_table表,并故意插入一些重复的记录。

CREATETABLEduplicate_table(idint,namevarchar(20));INSERTINTOduplicate_tableVALUES(100,'aaa'),(100,'aaa'),(200,'bbb'),(200,'bbb'),(200,'bbb'),(300,'ccc');

7.2 核心思路与实操

要在原表上直接删除重复记录比较复杂,我们采取一种“偷梁换柱”的原子操作。

首先,创建一张结构完全一样的空表no_duplicate_table,这里可以使用LIKE关键字复制表结构。

CREATETABLEno_duplicate_tableLIKEduplicate_table;

接着,我们将原表去重后的数据插入到这张新表中。

INSERTINTOno_duplicate_tableSELECTDISTINCT*FROMduplicate_table;

此时新表中只有 3 条干净的数据。最后,我们通过重命名表,将旧表改名,把新表改成原本的名字。

RENAMETABLEduplicate_tableTOold_duplicate_table,no_duplicate_tableTOduplicate_table;

查看最终的duplicate_table,重复记录就被彻底清理干净了。这种利用 RENAME 语句实现替换的做法,在数据库运维中被称为原子操作,可以最大程度降低对业务的影响。

八、深入探究:SQL语句的执行先后顺序

在学习查询的过程中,很多同学都会有一个疑问:为什么在查询总分的时候,我们在ORDER BY子句中能使用SELECT里定义的别名,但在WHERE子句中却不行呢?

比如下面这条语句,我们给总分起了别名total,在ORDER BY中使用total是合法的,但如果在WHERE后面写total > 200就会报错。这其实涉及到了 SQL 语句各个组成部分的底层执行先后顺序问题。

8.1 核心执行步骤拆解

我们先写下一条完整的查询语句:select name, math+chinese+english as total from exam_result order by total;。在 MySQL 内部,它的执行顺序并不是按照我们书写代码的先后顺序来的。

  1. 首先执行的是FROM阶段,系统需要先找到exam_result这张表,确认数据源。
  2. 接着执行WHERE阶段,根据条件对数据进行过滤。此时SELECT还没有执行,别名total还不存在,所以在WHERE中自然无法使用别名。
  3. 然后执行SELECT阶段,提取需要的列,并计算表达式(math+chinese+english),此时才真正为计算结果生成了别名total。
  4. 随后进入ORDER BY阶段进行排序。因为上一步SELECT已经执行完毕,所以此时可以直接使用别名total进行排序。这就是为什么“先要有合适的数据,才能排序”。
  5. 最后执行LIMIT阶段。因为它的本质功能是“显示”数据,只有数据准备好了,才能进行最后的截取和展示。

8.2 LIMIT 的显示本质与综合应用

理解了这个顺序,我们再来看LIMIT。无论是limit 3、limit 2,4还是limit 5 offset 0,它们的作用都是在数据准备完毕、排序完成之后,在最终结果的层面进行筛选展示。

我们来看一条综合了所有子句的复杂查询语句:

selectname,english+math+chinese totalfromexam_resultwhereenglish+math+chinese>200orderbytotaldesclimit2offset2;

这条语句的完整生命周期如下:

  1. FROM exam_result:定位数据源。
  2. WHERE english+math+chinese>200:过滤出总分大于200的同学。注意这里必须写完整的表达式,不能用别名。
  3. SELECT name, english+math+chinese total:提取姓名,并计算总分同时赋予别名total。
  4. ORDER BY total desc:使用别名total对查出来的结果进行降序排列。
  5. LIMIT 2 offset 2:跳过前2条,取出接下来的2条作为最终展示结果。

九、MySQL的三大核心日志

在 MySQL 的底层运行机制中,日志扮演着极其重要的角色。如果没弄懂日志,就没办法真正理解数据库是如何保证数据不丢、出错了怎么恢复,以及怎么做读写分离的。结合图片中的信息,我们今天用最浅显的方式把三个核心日志(bin log、redo log、undo log)讲清楚。

9.1 Binlog:记录历史的“流水账”

bin log 是默认关闭的,需要我们通过配置开启。它的核心作用有两个:一是记录历史 SQL 语句,二是用于主从同步。

你可以把 Binlog 想象成一本菜谱或流水账。只要你对数据库做了任何修改(比如插入、更新、删除),Binlog 就会按顺序把这条具体的 SQL 语句记录下来。

它最大的用处就是主从同步。主库把 Binlog 发给从库,从库照着这本流水账重新执行一遍,就能保证两边的数据一致了。另外,如果你的数据库不小心被误删了,只要有历史备份,配合 Binlog 就能把数据恢复到误删前的状态。

9.2 Redo Log:防止断电的“小黑账”

redo log 是持久化的方式,但其实更准确地说,它是保证数据持久性(不丢失)的关键。

想象一下,如果每次修改数据都要直接写到磁盘里,速度会非常慢。于是 InnoDB 引擎想了个办法:当你要修改数据时,先在内存里改,同时把这次修改记在一个小黑账本(Redo Log)上。等系统空闲的时候,再把内存里的数据慢慢写到磁盘上。

如果这时候突然停电了怎么办?不用担心,重启之后,数据库会翻开这个“小黑账本”,把那些还没来得及写到磁盘的修改重新执行一遍(这就是所谓的重做)。因此,Redo Log 是循环写的,它保证了就算发生宕机,已经提交的数据也绝对不会丢。

9.3 Undo Log:时光倒流的“撤销键”

undo log 它主要服务于事务的回滚和 MVCC(多版本并发控制)。

最通俗的理解,它就是浏览器里的撤销键。在事务执行的过程中,比如你把 A 同学的分数扣了 10 分,但在提交之前,事务突然报错失败了,或者你主动执行了 ROLLBACK。这时候数据库怎么把 A 同学的分数变回去呢?就是通过 Undo Log。

你在做任何修改前,数据库会悄悄把原来的数据(反向操作)记在 Undo Log 里。一旦需要回滚,它就会照着 Undo Log 把数据改回原样,以此保证事务的原子性——要么全做,要么全不做。


好的本文内容到此结束,我是此方,我们下期再见。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询