leetcode 解题总结:二叉树:相同、对称、平衡、右视图

刷题范围

  • 基础题
      1. 相同的树
      1. 对称二叉树
      1. 平衡二叉树
      1. 二叉树的右视图
  • 扩展题
    • 965、954、226、617、2331、508、1026、1372、1080

一、核心认知:二叉树递归的本质

1. 递归遍历 ≈ 访问每一个节点

二叉树的递归遍历,本质上就是:

对每一个节点做一次访问,并在访问过程中完成题目要求的逻辑

我对「递归拆分子问题」的理解:

  • 对当前节点的处理逻辑抽出来
  • 对子树的处理交给“规模更小的同一问题”

2. 递归的核心模板

1
2
3
4
5
6
7
8
9
10
11
12
13
dfs(node):
if node == null:
return base

// 1. 处理当前节点(可选)
...

// 2. 递归访问左右子树
left = dfs(node.left)
right = dfs(node.right)

// 3. 归并左右子树结果
return combine(left, right)

是否“处理当前节点”在前还是在后,决定了是自顶向下还是自底向上


二、“递” 与 “归”的清晰区分(今天最大的收获)

1. 什么是「递」?

递 = 分解问题、向下传递信息

  • 从根节点向下
  • 信息是已知的、确定的
  • 常见传递内容:
    • 路径和
    • 最大值 / 最小值
    • 深度、层数
    • 父节点信息

典型特征: 函数参数在变,状态向下传


2 什么是「归」?

归 = 汇总结果、向上返回信息

  • 从叶子节点向上
  • 信息是子树计算后的结果
  • 常见返回内容:
    • 高度
    • 是否平衡
    • 最大/最小路径
    • 子树统计信息

典型特征:返回值在变,状态向上合并


3. 自顶向下 vs 自底向上

方式 特点 类比遍历 适合场景
自顶向下 先处理当前节点,再递 先序 路径和、区间限制
自底向上 先递归子树,再归 后序 高度、平衡性

自顶向下更符合直觉,自底向上是动态规划的基础


三、做复杂题时的通用策略

1. 简化递归逻辑

当题目逻辑变复杂时:

  • 不要把所有判断写在一层
  • 可以:利用 返回值 或 **全局变量 **或 多返回信息(包装类 / 数组)

递归只负责“计算”,答案在递归结束后统一处理


2. map.merge 的实战体会

1
map.merge(key, 1, Integer::sum);

含义总结:

  • key 不存在 → 新建 (key, newValue)
  • key 存在 → (oldValue, newValue) 传入函数,返回新值

在:

    1. 出现次数最多的子树元素和

    这类「统计 + DFS」题中非常合适


四、典型题目的方法论总结

1. 1026. 节点与其祖先之间的最大差值

核心体会:

  • 分开体现了 递(向下传 max / min) 与 **归(返回结果)**两种方法都可行
  • 是理解「递归双向信息流」的经典题

2. 1372. 二叉树中的最长交错路径

关键点:

  • 递归过程中需要重置变量
  • 每个节点都可能作为新的起点
  • 状态不是简单继承,而是条件性重置

3. 1080. 根到叶路径上的不足节点(重要)

这是今天**“递 + 归”**结合体会最深的一题。

  • **递(向下)**传递:从根到当前节点的路径和
  • **归(向上)**汇总:包含该节点的所有根到叶路径
  • 中间逻辑
    • 判断当前节点是否是「不足节点」

真正体会到:

递负责携带历史信息,归负责判断整体价值


五、关于递归与迭代的一个重要认知

只有“递下去”的过程,才可能转成迭代
“归上来”的过程,本质依赖系统栈,无法直接用迭代模拟

  • BFS / DFS(只向下)→ 可迭代
  • 后序遍历 / 动态规划式递归 → 难以完全转迭代

这是理解「为什么有些递归不能改成 while」的关键。


六、今日整体收获总结

  • 二叉树问题的本质是:节点访问 + 信息流动
  • 真正重要的不是“会不会写递归”,而是:
    • 信息是 向下传 还是 向上收
    • 答案是在 途中产生 还是 最终汇总
  • 自顶向下更直观,自底向上是进阶算法(DP、树 DP)的必经之路

七、相关代码

本文涉及的所有代码与笔记,均已同步至我的 GitHub 算法仓库,作为 Java 后端校招过程中的学习记录。

MySQL 索引与 SQL 优化全梳理:从 InnoDB 存储结构到执行计划

一句话导读
本文系统梳理了 MySQL 中视图、触发器、锁机制以及 InnoDB 事务与 MVCC 的底层原理,重点理解数据库在并发与事务场景下“如何保证正确性与性能”。


一、视图 / 存储过程 / 触发器概览

1. 视图(View)

视图本质是一张虚拟表

  • 只保存 SQL 查询逻辑
  • 不保存真实数据
  • 查询视图时,结果是动态生成的

我的理解:可以把视图视为“可复用的 SQL 查询封装”。

视图的核心作用

  1. 简化查询:常用复杂 SQL 封装成视图
  2. 权限控制:屏蔽敏感字段,只暴露需要的数据
  3. 数据独立性:表结构变化对上层影响更小(封装思想)

WITH CHECK OPTION

  • 用于限制通过视图进行 DML 操作
  • 确保插入 / 更新后的数据仍满足视图定义条件

两种检查范围:

  • CASCADED(默认):级联检查当前视图及其依赖视图
  • LOCAL:只检查当前视图

注意
包含 group by、聚合函数、distinct、join 的视图,通常不可更新(视图与基础表中行一一对应才可以更新)


2. 存储过程与存储函数(了解)

阿里开发手册明确不推荐在业务系统中使用存储过程

原因:

  • 可维护性差
  • 调试困难
  • 与业务代码耦合过深

但从原理上:

  • 存储过程 ≈ SQL 层函数封装
  • 存储函数 = 有返回值的存储过程

3. 触发器(Trigger)

触发器是表级别的自动回调机制

  • INSERT / UPDATE / DELETE
  • BEFOREAFTER
  • 自动执行一段 SQL

使用 OLD / NEW 引用数据变化前后内容。

典型场景

  • 数据校验
  • 日志记录
  • 约束补充

二、MySQL 锁机制全景理解

1. 锁的分类(按粒度)

锁类型 作用范围
全局锁 整个数据库
表级锁 整张表
行级锁 行(本质是索引项)

2. 全局锁

1
flush tables with read lock;
  • 整个数据库只读
  • 常用于全库逻辑备份

InnoDB 推荐使用:

1
mysqldump --single-transaction

在不加锁的情况下获得一致性快照。


3. 表级锁

表锁(手动)

  • read lock:可读不可写
  • write lock:读写都阻塞

元数据锁(MDL)

  • 系统自动加锁
  • 防止 DML 与 DDL 并发冲突:表有未提交事务时,无法修改表结构

意向锁(InnoDB)

用来“告诉表锁:我表里有行锁”

  • IS(意向共享锁)
  • IX(意向排他锁)

目的:
避免表锁逐行检查是否存在行锁,提高效率


4. 行级锁(InnoDB 核心)

重点理解一句话:

InnoDB 的行锁,本质是加在索引上的,而不是记录本身

三种行级锁

  • Record Lock:锁定单条索引记录
  • Gap Lock:锁定索引间隙(防幻读)
  • Next-Key Lock:Record + Gap

索引失效 = 行锁退化为表锁


三、InnoDB 存储引擎架构

1. 核心内存结构

Buffer Pool

  • 缓存数据页与索引页
  • Page 级别管理
  • 脏页异步刷盘

Change Buffer

  • 针对 非唯一二级索引
  • 先缓存修改,后合并
  • 减少随机 IO

Adaptive Hash Index

  • InnoDB 自动生成
  • 优化等值查询
  • 无需人工维护

Log Buffer

  • redo / undo 日志缓冲区
  • 提高日志写入效率

2. 磁盘结构要点

  • Redo Log:保证事务持久性
  • Undo Tablespace:存放 undo log
  • Doublewrite Buffer:防止部分写失败

四、事务原理:ACID 是如何实现的?

1. ACID 对应关系

特性 实现机制
原子性 undo log
一致性 redo + undo
隔离性 锁 + MVCC
持久性 redo log

2. redo log:保证“提交不丢”

  • 物理日志
  • 事务提交时先写 redo log,即 WAL(Write-Ahead Logging)

redo 解决的是:
事务成功了,但数据页还没刷盘就宕机的问题


3. undo log:保证“失败可撤销”

  • 逻辑日志
  • 记录“相反操作”
    • delete ↔ insert
    • update ↔ old value
  • 用于:
    • rollback
    • MVCC 快照读

我的理解: 物理日志是照搬,逻辑日志需要理解其语义


五、MVCC:并发读写的核心机制

1. 当前读 vs 快照读

  • 当前读:加锁,读最新数据
  • 快照读:不加锁,读的版本由 readview 匹配规则决定

2. 隐藏字段

InnoDB 每行都有:

  • DB_TRX_ID:最近修改事务 ID
  • DB_ROLL_PTR:指向 undo log 旧版本
  • DB_ROW_ID(无主键时)

3. 版本链 + ReadView

  • 多次修改 → 形成 undo log 版本链
  • ReadView 决定:
    • 哪个版本对当前事务可见

不同隔离级别:

  • RC:每次 select 生成 ReadView
  • RR:事务第一次 select 生成一次

六、学习总结(个人理解)

今天的学习让我对 MySQL 的理解从“会用 SQL”提升到“知道数据库在底层如何保障正确性”:

  1. 视图本质是 SQL 封装 + 权限控制
  2. 锁机制复杂但核心是 粒度与索引
  3. InnoDB 的行锁是加在索引上的
  4. redo log 保证“已提交一定成功”
  5. undo log 保证“失败一定能回滚”
  6. MVCC = 隐藏字段 + undo log + ReadView
  7. ACID 不是概念,而是一整套工程实现

二叉树递归解题方法论整理(学习笔记)

在二叉树相关题目中总结出了,二叉树题目本质是“在递归中拆分问题,在回溯中合并结果”,答案往往产生于递的过程归的过程


  • 一、什么是递归(结合二叉树理解)

    递归 = 递 + 归

    • 递(递进)
      将原问题不断拆分为规模更小的子问题

      递的过程通常可以传递信息,更新答案……

      在二叉树中,通常表现为:

      “当前节点的问题 = 左子树的问题 + 右子树的问题”

    • 归(回溯)
      当到达边界条件(递归终点)后,逐层向上返回结果
      在这个阶段,常常会:更新答案、将子树结果向上传递……

    关键理解
    不要把递归想成“函数调用细节”,更多的关注树整体结构与子树的关系。


    二、二叉树递归的正确思考顺序(非常重要)

    推荐思考顺序

    1. 先想整体结构(递)

      当前节点的问题能否拆成:左子树问题、右子树问题

      在往下递的过程是否需要传递信息(可以后面用到的时候再补上)

    2. 再想递归终点(边界条件)

      递的终点是什么?空节点怎么办?叶子节点怎么办?

    3. 最后想回溯时做什么(归)

      返回什么值?是否需要更新答案?

    结论

    二叉树递归,先“递”,再考虑“归”,而不是一开始就纠结细节。


    三、答案一般出现在哪里?

    二叉树题目的答案,通常出现在以下两个位置之一

    出现在「递的过程」

    例如:

    • 从根到叶路径
    • 累加路径和
    • 携带父节点信息向下传递

    特点:

    • 信息是自顶向下
    • 常通过参数传递或全局变量实现

    出现在「归的过程」

    • 例如:
      • 最大深度
      • 平衡二叉树
      • 子树高度 / 子树和

    特点:

    • 信息是自底向上
    • 当前节点的结果依赖左右子树返回值

    四、最小深度 vs 最大深度

    这是一个非常容易写错的点,我在做题时思考了许久两者的区别。

    最大深度(Max Depth)

    逻辑非常自然:

    • 左右子树谁深选谁
    • 不存在歧义
    1
    maxDepth = max(leftDepth, rightDepth) + 1

    最小深度(Min Depth)

    关键区别点在于:非叶子节点

    如果一个节点只有一个非空子节点最小深度只能来自非空的那一侧

    常见错误

    直接对左右子树取 min

    会错误地选择到 null 的那一侧

    正确理解

    空子树不能参与“最小路径”,路径问题重点一定是叶子节点,必须强制走到叶子节点

    这也是为什么最小深度的代码逻辑比最大深度复杂


    五、二叉树递归的通用解题模板

    二叉树递归通常包含三个阶段

    递(拆问题)

    向左右子树递归调用

    归(合结果)

    利用左右子树返回值计算当前结果

    记录答案

    更新全局变量 或 返回当前节点的计算结果

    很多题目其实只是这三个步骤的不同组合


    六、父节点信息如何传递?

    当「判断当前节点是否满足条件」需要父节点信息时,有两种常见方式:

    方式一:递归参数向下传递

    将父节点相关信息作为参数传入子递归,逻辑清晰

    方式二:使用全局变量

    实现简单

    但要注意:重置问题


    七、垂序遍历中的 Map 知识点整理

    Map 按 key 排序

    使用 TreeMap

    自动按 key 升序维护

    集合自定义排序

    使用 Comparator

    今天用在:List 排序

    map.computeIfAbsent 用法

    1
    map.computeIfAbsent(key, k -> newValue);

    含义是:

    • 如果 key 已存在 → 直接返回对应的 value

    • 如果 key 不存在 →

      • 使用函数计算一个 value

      • 放入 map

      • 并返回该 value

​ 在树遍历中非常常用


八、相关代码

本文涉及的所有代码与笔记,均已同步至我的 GitHub 算法仓库,作为 Java 后端校招过程中的学习记录。

MySQL 索引与 SQL 优化全梳理:从 InnoDB 存储结构到执行计划

一、MySQL 存储引擎与体系结构

1. MySQL 体系结构概览

MySQL 整体可以分为四层:

  • 连接层:负责连接处理、权限认证和安全校验
  • 服务层:SQL 解析、优化、缓存、执行等核心逻辑
  • 引擎层:真正负责数据的存储和读取(通过统一 API)
  • 存储层:将数据、索引、日志等持久化到文件系统

MySQL 的核心优势在于 存储引擎插件化

查询处理与数据存储解耦,可以根据业务场景选择最合适的引擎。


2. InnoDB 存储引擎特性

InnoDB 是 MySQL 5.5 之后的默认存储引擎,兼顾高可靠性与高性能,主要特性包括:

  • 支持事务(ACID)
  • 行级锁,支持高并发
  • 支持外键约束

每张 InnoDB 表都对应一个 .ibd 文件,用于存储 表结构 + 数据 + 索引


3. InnoDB 逻辑存储结构

从大到小依次为:

  • 表空间(Tablespace).ibd 文件本身
  • 段(Segment):数据段、索引段、回滚段
  • 区(Extent):1MB,一个区 = 64 个页
  • 页(Page):16KB,InnoDB 磁盘 I/O 的最小单位
  • 行(Row):真实数据记录,包含隐藏字段(事务 ID、回滚指针)

InnoDB 是按页而不是按行进行磁盘读写的。


二、索引原理与结构

1. 索引是什么

索引是一种 有序的数据结构,用于加速数据检索。

无索引时:

查询只能全表扫描,从第一行扫到最后一行,性能极低。

索引的优缺点:

优点

  • 降低 I/O 成本
  • 减少排序开销(索引中的值是有序的),降低 CPU 消耗

缺点

  • 占用额外空间
  • 增删改时需要维护索引结构

2. 为什么 InnoDB 使用 B+Tree

B+Tree 相比其他结构的优势:

  • 相比二叉树:层级更低
  • 相比 B-Tree:
    • 非叶子节点不存数据,单页可存更多索引
    • 树高度更低
  • 相比 Hash:
    • 支持范围查询
    • 支持排序

InnoDB 在 B+Tree 基础上增加了 双向链表,极大提升了区间查询效率。


三、聚集索引与回表查询

1. 聚集索引 & 二级索引

  • 聚集索引:叶子节点存的是整行数据(主键)
  • 二级索引:叶子节点存的是主键值

通过二级索引查到主键,再回到聚集索引查整行数据,这个过程称为 回表查询


2. B+Tree 高度估算(理解索引为什么快)

假设:

  • 一行数据 ≈ 1KB
  • 一页 16KB ⇒ 16 行
  • 主键 bigint:8 字节
  • 指针:6 字节

高度为 2 时:

1
2
n * 8 + (n + 1) * 6 = 16 * 1024 // n 代表一节点中存储值的个数
n ≈ 1170

可存储记录数:

1
1170 * 16 ≈ 1.8 万

高度为 3:

1
1171 * 1171 * 16 ≈ 2200 万

现实中索引高度通常 ≤ 3


四、索引使用与失效场景

1. 最左前缀法则

联合索引 (a, b, c)

  • ✅ a
  • ✅ a, b
  • ❌ b, c

查询条件顺序无关,优化器会自动调整。


2. 范围查询导致右侧索引失效

1
where a = 1 and b >= 10 and c = 5
  • a、b 可用
  • c 只能过滤,不能继续缩小扫描区间

key_len 不能判断是否完全走索引,必须结合 type = ref / range


3. 常见索引失效原因

  • 左 / 左右模糊匹配:%xx
  • 对索引列使用函数或表达式
  • 隐式类型转换
  • OR 条件中有非索引列
  • 数据分布导致优化器放弃索引

五、SQL 优化总结

1. 插入优化

  • 批量插入(本质上是减少事务的数量)
  • 手动控制事务
  • 大数据量使用 LOAD DATA

2. 主键优化

  • 顺序插入 > 乱序插入
  • 避免 UUID
  • 减少页分裂 / 页合并(通过顺序插入)

3. 排序、分组、分页优化

  • ORDER BY 优先 using index
  • 大分页使用子查询 + 覆盖索引
  • GROUP BY 遵循最左前缀

4. COUNT 优化

1
2
效率排序:
count(字段) < count(主键) < count(1) = count(*)

六、总结

今天主要系统学习了 InnoDB 存储结构、索引原理以及 SQL 优化方法,核心结论如下:

  1. InnoDB 是面向事务与高并发的存储引擎
  2. B+Tree 是最适合磁盘索引的数据结构
  3. SQL 优化本质是:减少全表扫描(索引查询)、减少回表查询(索引覆盖)、减少文件排序(索引排序)
  4. explain 是定位性能问题的核心工具
  5. DML 优化要避免索引失效导致锁升级

删除链表中的元素:从基础操作到 O(n) 优化思路(Java)

在链表相关题目中,“删除节点”是一个看似简单但非常容易出错的操作。
本文结合我在刷题过程中的总结,梳理了链表删除的通用思路,并通过多个典型场景说明如何避免常见坑点,以及一些特殊处理


一、链表删除的本质

单链表中,节点只知道自己的 next并不知道前驱节点

因此,删除一个节点的核心操作永远是:

找到要删除节点的前一个节点 prev,然后:

1
prev.next = prev.next.next;

这也是为什么:

  • 头节点删除
  • 倒数第 N 个节点
  • 条件删除多个节点

几乎都会用到 哨兵节点(dummy node)


二、为什么要使用哨兵节点(Dummy Node)

以删除头节点为例:

1
1 -> 2 -> 3

如果要删除 1,你会发现:

  • 它没有前驱节点
  • 需要单独写 if 判断

引入哨兵节点后:

1
dummy -> 1 -> 2 -> 3

这样就能统一删除逻辑

1
2
3
4
5
6
7
8
9
10
11
12
ListNode dummy = new ListNode(0);
dummy.next = head;

ListNode cur = dummy;
while (cur.next != null) {
if (cur.next.val == target) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return dummy.next;
  • 不需要特判头节点
  • 删除逻辑始终一致

三、典型删除场景总结

1. 删除倒数第 N 个节点(双指针)

核心思想:
让快指针先走 N 步,使快慢指针保持固定距离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ListNode dummy = new ListNode(0);
dummy.next = head;

ListNode fast = dummy;
ListNode slow = dummy;

// fast 先走 N 步
for (int i = 0; i < n; i++) {
fast = fast.next;
}

// 同时移动
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}

// slow 指向待删除节点的前一个
slow.next = slow.next.next;

return dummy.next;

关键点:

  • 删除的是 slow.next
  • dummy 能处理删除头节点的情况

2. 删除排序链表中的重复元素

在删除重复节点时,一定要注意空指针问题

1
2
3
4
5
6
7
while (cur.next != null && cur.next.next != null) {
if (cur.next.val == cur.next.next.val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}

在操作节点 node.nextnode.val 时一定确保节点不为空


3. 删除“在数组中出现过”的节点(HashSet)

当删除条件来源于一个数组时,最优解是:

  • 先把数组放进 HashSet(优化:预分配空间)
  • 再遍历链表,O(1) 判断是否需要删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Set<Integer> set = new HashSet<>(nums.length);
for (int x : nums) set.add(x);

ListNode dummy = new ListNode(0);
dummy.next = head;

ListNode cur = dummy;
while (cur.next != null) {
if (set.contains(cur.next.val)) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return dummy.next;

时间复杂度: O(n)
空间复杂度: O(n)


四、从 O(n²) 到 O(n):正难则反的思想

在一道题中,我遇到了这样一个需求:

删除链表中 右侧存在比当前节点值更大元素的节点

直觉做法(超时)

  • 对每个节点向右扫描
  • 判断是否存在更大的值
    👉 O(n²),直接超时

优化思路:正难则反

  1. 反转链表

  2. 问题转化为:

    删除“左侧存在比当前节点更大的节点”

  3. 删除比最大值小的节点

  4. 再反转回来

最终复杂度:

  • 时间:O(n)
  • 空间:O(1)

五、总结

  • 链表删除的核心永远是:操作前驱节点
  • 需要操作头节点时,哨兵节点可以极大简化代码逻辑
  • 双指针是处理“倒数第 N 个节点”的标准解法
  • 当正向思路复杂时,要学会 反转链表 + 条件转换,运用之前写过的函数,拆解问题

六、相关代码

本文涉及的所有代码与笔记,均已同步至我的 GitHub 算法仓库,作为 Java 后端校招过程中的学习记录。

链表算法学习总结:反转链表与快慢指针(Java)

一、为什么链表题离不开指针技巧?

链表和数组最大的区别在于:链表无法通过下标随机访问,只能通过指针逐个遍历节点。
因此,大多数链表算法的核心并不是“复杂逻辑”,而是指针之间的关系变化

本次主要总结两个最常见、也是最重要的技巧:

  • 反转链表
  • 快慢指针

二、反转链表:从指针关系入手

1. 核心思想

反转链表的本质是:改变节点之间 next 指针的指向关系

经典做法是使用三个指针:

  • pre:当前处理节点的前一个节点
  • cur:当前处理的节点
  • next:用于暂存 cur 原来的下一个节点

每一轮循环只做一件事:

cur.next 指向 pre

循环结束后:

  • pre 指向新的链表头
  • cur 指向 null

2. 常见易错点

  • 在修改 cur.next 之前,一定要先保存原来的 next
  • 循环结束条件是 cur == null
  • 新的链表头是 pre,而不是原来的 head

3. 反转链表的扩展应用

反转链表不仅是单独的一道题,还常用于:

  • 判断回文链表
  • 从中间节点开始反转链表
  • 配合快慢指针拆分链表

三、快慢指针:用“速度差”解决定位问题

1. 查找链表中间节点

通过两个指针:

  • slow:每次走一步
  • fast:每次走两步

fast 到达链表末尾时,slow 正好位于中间节点。

这种方法可以在 一次遍历内 找到中间节点,非常高效。


2. 环形链表判定

如果链表存在环:

  • 快指针和慢指针一定会在环内相遇
  • 如果不存在环,快指针会先到达 null

这是判断链表是否成环的经典方法。


四、总结

链表题的关键在于:

  • 准确定义每个指针的含义
  • 在纸上演化指针变化的情况
  • 修改 next 时格外注意防止丢失原本的 next

五、相关代码

本文相关代码与学习笔记已同步至 GitHub:

https://github.com/tingfeng-work/algorithm-learning/tree/main/linkedList

MySQL 学习笔记 Day01:基础概念与常用命令

本文基于我在 MySQL 入门阶段的系统学习整理,主要记录数据库与 SQL 的核心概念、常用语法分类以及一些容易忽视的细节,为后续索引、事务与性能优化打基础。

一、数据库的基本认识

1. 数据库与 DBMS

  • 数据库(DB):是有组织的存储数据的仓库
  • 数据库管理系统(DBMS):则是用来管理数据库的软件(MySQL、Oracle)
  • SQL:操作关系型数据库的统一语言(类比机器码:计算机识别的统一语言)

尽管关系型数据库产品众多(MySQL、Oracle、PostgreSQL 等),但SQL 语法是统一标准,这也是关系型数据库易于学习和迁移的重要原因。

2. 关系数据及其特点

  • 数据以二维表(行列)的形式组织,可以想象为 excel 的形式
  • 表与表之间可以通过外键建立关系
  • 结构清晰、规范统一、易于维护

二、SQL 语句

SQL 语句分为四类:DDL、DML、DQL、DCL

分类 全称 说明
DDL Data Definition Language 数据定义语言,用来定义数据库对象(数据库,表,字段)
DML Data Manipulation Language 数据操作语言,对数据库表中数据进行增删改
DQL Data Query Language 数据查询语言,查询数据库中表的记录
DCL Data Control 数据控制语言,用来创建数据库用户、控制数据库访问权限

注意:

  • SQL 不区分大小写,每个语句以分号结尾

  • DML 中对数据进行删除用的是 delete,而 DDL 中的删除是 drop

三、表结构设计与数据类型选择

1. 常见数据类型

  • 数值类型intbigintdecimal
  • 字符串类型char(定长,性能更优)、varchar(变长,更灵活)
  • 日期时间类型datedatetime

2. 表结构设计原则

  • 主键明确,通常使用自增 id

  • 字段大小合理,避免浪费

  • 年龄等字段避免负数(无符号或约束)

  • 尽量为字段添加 comment,提高可读性

四、DML:数据增删改

插入数据

  • 字段顺序必须和插入值一一对应
  • 字符串和日期类型需要使用引号

修改/删除数据

  • UPDATE / DELETE 一定要注意 WHERE 条件,不加条件会影响整张表,这是开发中的高危操作

五、DQL:数据查询

1. 查询的基本语法

1
2
3
4
5
6
7
SELECT 字段
FROM
WHERE 条件
GROUP BY 分组字段
HAVING 分组后条件
ORDER BY 排序
LIMIT 分页

执行顺序:选表、where 过滤、分组、分组过滤、选择字段、排序、分页

2. where 和 having 的区别

  • WHERE:在分组 之前 过滤数据
  • HAVING:在分组 之后 过滤数据
  • WHERE 不能直接使用聚合函数,HAVING 可以

3. 分页查询

1
LIMIT 起始索引, 查询条数
  • 起始索引从 0 开始

  • 计算公式:
    (页码 − 1) × 每页条数

  • 超出范围不会报错,只会返回空结果集

六、约束与外键:保证数据正确性

常见约束

  • NOT NULL:非空
  • UNIQUE:唯一
  • PRIMARY KEY:主键
  • FOREIGN KEY:外键

外键的意义

  • 保证表与表之间的数据一致性
  • 防止“脏数据”出现(如员工引用不存在的部门)

实际项目中,是否使用物理外键需要权衡性能与一致性,很多互联网项目选择逻辑外键。

七、多表关系与查询方式

表关系

  • 一对多(部门-员工)
  • 多对多(学生-课程,需要中间表)
  • 一对一(表拆分优化)

多表查询方式

  • 内连接 / 左外连接 / 右外连接
  • 自连接
  • 子查询(标量 / 列 / 行 / 表子查询)

八、事务基础(为后续深入做铺垫)

事务的特性(ACID)

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

并发事务问题

  • 脏读
  • 不可重复读
  • 幻读

MySQL 默认隔离级别是 Repeatable Read,在安全性和性能之间取得平衡。

九、今日小结

  • 建表过程中,最后一个字段后没有 , 其余字段均有 ,
  • 每条语句以 ; 结尾
  • 对于数据行的删除用关键字 delete,对于表、数据库、视图的删除用关键字 drop
  • 约束条件在数据类型后
  • DQL 的执行顺序:选表、where 过滤、分组、分组后过滤、选择字段、排序、分页
  • 联合查询是合并两个结果集,子查询是在结果集的基础上在进行查询,子查询的结果集可以作为条件也可以作为表,也可以作为选择字段
  • 涉及子查询的复杂查询,可以分步查询,实现目标。
  • 并发事务的三大问题:脏读、不可重复读、幻读

第一篇博客

为什么搭建这个博客?

本博客用于记录我在准备 Java 后端开发实习与校招过程中的学习内容与项目实践,
现阶段主要包括 MySQL、Spring Boot、Redis 以及相关工程经验。

博客规划

  • MySQL:数据库基础、索引、事务、性能优化
  • Java:核心知识
  • Spring:Spring Boot / Spring MVC
  • 项目:完整后端项目实践
  • 面试:面试复盘

写作原则

  • 每天至少一篇学习记录或总结
  • 所有代码均同步维护在 GitHub
  • 博客内容以「面试可复述」为目标