3.数据库
2025.11.13 Day14
3.1 一条SQL查询语句是如何执行的?
连接器: 连接器负责跟客户端建立连接、获取权限、维持和管理连接。
查询缓存: MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。(备注:这个功能在 MySQL 8.0 中已被移除。)
分析器: 你输入的是由多个字符串和空格组成的一条SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。(这包括词法分析和语法分析)
优化器: 优化器是在表里面有多个索引的时候,决定使用哪个索引; 或者在一个语句有多表关联 (join) 的时候,决定各个表的连接顺序。
执行器: MySQL通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。(它会真正地去调用存储引擎的接口来获取数据。)
3.2 事务的四大特性有哪些?(CAI D 菜的)
原子性(Atomicity): 确保事务的所有操作要么全部执行成功,要么全部失败回滚,不存在部分成功的情况。
一致性(Consistency): 事务在执行前后,数据库从一个一致性状态转变到另一个一致性状态。
隔离性(Isolation): 多个事务并发执行时,每个事务都应该被隔离开来,一个事务的执行不应该影响其他事务的执行。
持久性(Durability): 一旦事务被提交,它对数据库的改变就是永久性的,即使在系统故障或崩溃后也能够保持。
3.3 数据库的事务隔离级别有哪些?
读未提交(Read Uncommitted):
- 允许一个事务读取另一个事务尚未提交的数据修改。
- 最低的隔离级别,存在脏读、不可重复读和幻读的问题。
读已提交(Read Committed):
- 一个事务只能读取已经提交的数据。其他事务的修改在该事务提交之后才可见。
- 解决了脏读问题,但仍可能出现不可重复读和幻读。
可重复读(Repeatable Read):
- 事务执行期间,多次读取同一数据会得到相同的结果,即在事务开始和结束之间,其他事务对数据的修改不可见。
- 解决了不可重复读问题,但仍可能出现幻读。(注:MySQL InnoDB 默认是这个级别,并很大程度上解决了幻读)
序列化(Serializable):
- 最高的隔离级别,确保事务之间的并发执行效果与串行执行的效果相同,即不会出现脏读、不可重复读和幻读。
| 隔离级别 | 比喻 | 脏读 | 不可重复读 | 幻读 | 效率 |
|---|---|---|---|---|---|
| 读未提交 | 实时打字 | ❌ (会) | ❌ (会) | ❌ (会) | 最高 |
| 读已提交 | 保存才刷新 | ✅ (解决) | ❌ (会) | ❌ (会) | 很高 |
| 可重复读 | 快照模式 | ✅ (解决) | ✅ (解决) | ❌ (会) | 较高 |
| 序列化 | 锁门模式 | ✅ (解决) | ✅ (解决) | ✅ (解决) | 最低 |
2025.11.14 Day15
3.4 MySQL的执行引擎有哪些?
MySQL的执行引擎主要负责查询的执行和数据的存储, 其执行引擎主要有 MyISAM、InnoDB、Memory 等。
InnoDB 引擎提供了对事务ACID的支持,还提供了行级锁和外键的约束,是目前MySQL的默认存储引擎,适用于需要事务和高并发的应用。
MyISAM 引擎是早期的默认存储引擎,支持全文索引,但是不支持事务,也不支持行级锁和外键约束,适用于快速读取且数据量不大的场景。
Memory 就是将数据放在内存中,访问速度快,但数据在数据库服务器重启后会丢失。
(事务 (ACID): 这是它最牛的地方。就像你去银行存取款,这个操作要么“完全成功”(钱存进去了,余额也对了),要么“完全失败”(像你没来过一样)。绝不会出现“钱扣了,但余额没加”的中间状态。这对于交易、订单等重要数据至关重要。
行级锁: 想象一下金库里有一排保险柜(数据行)。你(用户A)在用1号柜子时,只需要锁住1号柜子。别人(用户B)仍然可以同时使用2号、3号柜子。大家互不耽误,效率很高(高并发)。
外键: 有严格的登记制度,A房间的东西必须依赖B房间的某个登记(比如,订单里的用户ID必须在用户表里存在),保证了数据之间的关联性是正确的。)
3.5 MySQL为什么使用B+树来作索引
1. 单点查询
- B 树:进行单个索引查询时,时间代价为 O(logn)。从平均时间代价来看,可能会比 B+ 树稍快。
- B 树的缺点:查询波动会比较大,因为每个节点既存索引又存记录,所以有时候访问到了非叶子节点就可以找到索引,而有时需要访问到叶子节点才能找到。
- B+ 树:非叶子节点不存放实际的记录数据,仅存放索引。
- B+ 树的优点:数据量相同的情况下,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少。
2. 插入和删除效率
- B+ 树:有大量的冗余节点,删除一个节点的时候,可以直接从叶子节点中删除,甚至可以不动非叶子节点,删除非常快。插入时(如果节点饱和)可能存在节点的分裂,但是最多只涉及树的一条路径。
- B 树:没有冗余节点,删除节点的时候非常复杂,可能涉及复杂的树的变形。
3. 范围查询
- B+ 树:所有叶子节点间有一个链表进行连接。
- B 树:没有将所有叶子节点用链表串联起来的结构,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。
- 结论:存在大量范围检索的场景,适合使用 B+树(比如数据库)。而对于大量的单个索引查询的场景,可以考虑 B 树(比如 NoSQL 的 MongoDB)。
3.6 说一下索引失效的场景?
索引失效意味着查询操作不能有效利用索引进行数据检索,从而导致性能下降,下面一些场景会发生索引失效。
- 使用OR条件:当使用OR连接多个条件,并且每个条件用到不同的索引列时,索引可能不会被使用。
- 使用非等值查询:当使用
!=或<>操作符时,索引可能不会被使用,特别是当非等值条件在WHERE子句的开始部分时。 - 对列进行类型转换: 如果在查询中对列进行类型转换,例如将字符列转换为数字或日期,索引可能会失效。
- 使用LIKE语句:以通配符
%开头的LIKE查询会导致索引失效。 - 函数或表达式:在列上使用函数或表达式作为查询条件,通常会导致索引失效。
- 表连接中的列类型不匹配: 如果在连接操作中涉及的两个表的列类型不匹配,索引可能会失效。例如,一个表的列是整数,另一个表的列是字符,连接时可能会导致索引失效。
2025.11.15 Day16
3.7 什么是慢查询?原因是什么?可以怎么优化?
网课链接
数据库查询的执行时间超过指定的超时时间时,就被称为慢查询。
原因:
- 查询语句比较复杂:查询涉及多个表,包含复杂的连接和子查询,可能导致执行时间较长。
- 查询数据量大:当查询的数据量庞大时,即使查询本身并不复杂,也可能导致较长的执行时间。
- 缺少索引:如果查询的表没有合适的索引,需要遍历整张表才能找到结果,查询速度较慢。
- 数据库设计不合理:数据库表设计庞大,查询时可能需要较多时间。
- 并发冲突:当多个查询同时访问相同的资源时,可能发生并发冲突,导致查询变慢。
- 硬件资源不足:如果MySQL服务器上同时运行了太多的查询,会导致服务器负载过高,从而导致查询变慢。
优化:
- 运行语句,找到慢查询的sql
- 查询区分度最高的字段
- explain:显示mysql如何使用索引来处理select语句以及连接表,可以帮助选择更好的索引、写出更优化的查询语句
- order by limit形式的sql语句,让排序的表优先查
- 考虑建立索引原则
3.8 undo log、redo log、binlog 有什么用?
1. redo log (重做日志)
- 层面与类型: 是 Innodb存储引擎层 生成的物理日志 (Physical Log)。
- 记录内容: 它记录的是数据页 (Page) 级别的物理变更,例如:“在表空间 A 的第 X 个数据页的 Y 偏移量处,写入数据 Z”。它不关心逻辑操作(如
INSERT或UPDATE),只记录物理位置的变更。 - 特性: 这种记录方式紧凑,且支持幂等操作(崩溃恢复时重复执行也无害)。
- 核心作用: 主要用于崩溃恢复,保证持久性 (Durability)。
- 工作机制: 为了提高性能,InnoDB 在执行事务时,会先把日志条目写入内存中的 redo log buffer(重做日志缓冲区),然后再按照一定的策略(如事务提交时或定时)将其刷(flush)到磁盘上的 redo file(重做日志文件)中。
2. undo log (回滚日志)
- 层面与类型: 是 Innodb存储引擎层 生成的逻辑日志 (Logical Log)。
- 核心作用: 实现了事务中的原子性 (Atomicity),主要用于事务回滚和 MVCC。
- 记录内容: 它记录的是操作的逻辑“逆操作”。例如:
INSERT" 对应 "DELETE;UPDATE old TO new对应UPDATE new TO old。它不关心数据在磁盘的物理位置,只关心如何从逻辑上撤销操作。
3. binlog (归档日志)
- 层面与类型: 是 Server 层 生成的逻辑日志 (Logical Log)。
- 核心作用: 主要用于数据备份和主从复制。
- 记录内容: 它记录的是事务最终提交的变更事件 (Event)。内容可以是 SQL 语句(Statement 格式)或数据行(Row 格式)的变更详情。它同样不关心数据的物理存储位置,只记录数据库发生的逻辑变更。不记录select和show等。
2025.11.17 Day17
3.9 MySQL(冷库)和Redis(厨师手边的菜板子)的区别是什么
数据模型:
- Redis 是一种键值对(Key-Value)数据库,它的灵活性很高,支持如字符串(String)、列表(List)、哈希(Hash)等多种数据结构。
- MySQL 是一种关系型数据库,它使用规范的表(Table)来组织和存储数据,结构非常严谨。
存储位置:
- Redis 为了追求极致的速度,将数据主要存在内存中,读写极快,同时通过持久化机制将数据异步写入磁盘以防丢失。
- MySQL 的数据通常存储在磁盘上,这保证了数据的持久性和可靠性,但读写速度受限于磁盘I/O。
操作方式:
- Redis 不使用SQL,而是提供了一套丰富、轻量级的自有命令集(如
SET,GET)来操作数据。 - MySQL 使用大家熟知的SQL(结构化查询语言) 来进行数据查询和复杂的事务操作。
适用场景:
- Redis 凭借其内存特性,实现了高性能和低延迟,是高速、高并发数据访问(例如用作缓存、排行榜)的理想选择。
- MySQL 则适用于需要处理复杂查询、保证事务处理(ACID) 以及持久化存储大规模数据集的场景。
协同工作:
- 在现代的实际应用中,两者并非互斥,很多系统会同时使用 MySQL 和 Redis:MySQL 充当可靠的“数据仓库”,Redis 则作为“高速缓存”,两者配合,共同支撑高并发业务。
| 对比维度 | 🗄️ MySQL (关系型数据库) | 🔪 Redis (键值缓存数据库) |
|---|---|---|
| 通俗比喻 | 餐厅的“大冷库” | 厨师的“手边备菜台” |
| 数据存哪 | 磁盘(硬盘) | 内存 |
| 特点 | 永久、安全、空间大、慢 | 临时、易丢、空间小、快 |
| 数据结构 | 表 (Tables) (像严格规范的货架) | 键值对 (Key-Value) (像碗、盘、调料盒等容器) |
| 操作方式 | SQL 语言 (像填一张复杂的“领料单”) | 命令 (Commands) (像厨师的“本能反应”,如 GET, SET) |
| 核心目标 | 数据持久化与一致性 (保证“家底”绝对安全) | 高性能与低延迟 (保证“出菜”速度绝对快) |
| 适用场景 | 复杂查询、事务处理 (如:订单系统、用户注册信息) | 缓存、计数器、排行榜 (如:缓存商品信息、帖子点赞数) |
| 常见组合 | “家底”(数据最终存放地) | “加速器”(优先访问,没有才去MySQL) |
3.10 Redis有什么优缺点?为什么用Redis查询会比较快
[视频解析](什么是 Redis,有什么优缺点?_哔哩哔哩_bilibili)
(1) Redis 的优点与缺点
优点 (Advantages)
-
读写速度极快:Redis 是一个基于内存的数据库,所有操作都在内存中完成,彻底摆脱了磁盘 I/O 的性能瓶颈。
-
支持多种数据结构:它提供了丰富的数据结构,如字符串 (String)、哈希表 (Hash)、列表 (List)、集合 (Set) 和有序集合 (Sorted Set) 等,能满足多种复杂场景的需求。
-
功能丰富:基于其特性,Redis 通常被用作缓存、消息队列、分布式锁和高性能键值存储数据库。
(作缓存:最常用的用法,把“冷库”(MySQL)的热门菜拿出来放桌上。
作消息队列:把“订单夹子”(List)当成“待处理任务”列表。
作分布式锁:某个厨师(进程)在用“唯一的汤勺”(Lock)时,别人就不能用。)
- 高可用与可扩展:Redis 提供了分布式特性(如哨兵和集群),可以将数据分布在多个节点上,以提高系统的可扩展性和可用性。
缺点 (Disadvantages)
- 受限于物理内存:Redis 的数据量受限于物理内存的大小,因此不适合存储超大规模(如TB级别)的数据。
- 存储成本高:内存的成本远高于磁盘,导致使用 Redis 存储大量数据时,硬件成本会比较昂贵。
(2) 为什么 Redis 查询快?
Redis 的高性能主要归功于以下几个设计:
- 基于内存操作
- 这是最根本的原因。所有数据都存储在内存中,操作速度远快于传统的磁盘 I/O,减少了大量的I/O开销。
- 高效的数据结构
- Redis 为其支持的每种数据类型(STRING, LIST, HASH等)都设计了专门且高效的内部数据结构,这使得它在执行不同命令时都能保持极高的效率。
- 采用单线程模型
- 避免了多线程上下文切换带来的开销和 CPU 消耗。
- 不存在资源竞争(如加锁、解锁)的问题,从机制上避免了死锁现象的发生,使得逻辑非常简单。
- I/O 多路复用
- Redis 采用 I/O 多路复用机制(如 epoll)来处理网络连接。
- 这使得它的单线程也能够同时监听和处理大量并发的 Socket(客户端请求),根据事件来选择处理器,极大提升了并发处理能力。
2025.11.18 Day18
3.11 Redis的数据类型有哪些?
Redis 常见的五种数据类型
- String (字符串):
- 存储字符串数据,是 Redis 最基本的数据类型。
- Hash (哈希表):
- 存储字段和值的映射(field-value pairs),常用于存储对象。
- List (列表):
- 存储有序的字符串元素列表(元素可重复)。
- Set (集合):
- 存储唯一的字符串元素,特点是无序且元素唯一。
- Zset (Sorted Set:有序集合):
- 类似于集合(元素唯一),但每个元素都关联一个分数 (score),可以按分数进行排序。
Redis 新增的数据类型
- BitMap (位图):
- 存储位 (bit) 的数据结构,可以用于高效处理位运算操作(如用户签到、状态标记)。
- HyperLogLog:
- 用于基数估算的数据结构,能以极小的内存空间统计元素的唯一数量(如统计日活用户)。
- GEO (地理空间):
- 专门用于存储地理位置信息的数据结构(如存储坐标、计算两点距离)。
- Stream (流):
- 专门为消息队列 (MQ) 设计的数据类型,支持消息的持久化和消费组模式。
3.12 Redis是单线程的还是多线程的,为什么?(一个厨师还是多个厨师)
Redis 在其传统的实现中,核心是单线程的,这意味着它使用单个主线程来处理所有的客户端请求(包括命令的接收、执行和响应)。
这样的设计选择有几个关键原因:
- 简化模型: 单线程模型极大简化了并发控制,使其避免了复杂的多线程同步问题(例如繁琐的加锁、解锁)。
- 性能优化: Redis 绝大多数操作都在内存中完成(速度极快),其性能瓶颈通常不在 CPU,而在内存或网络 I/O。单线程的设计避免了线程间切换和锁竞争的开销。
- 原子性保证: 单线程执行天然确保了操作的原子性(一个命令在执行时不会被另一个命令打断),这简化了事务和持久化的实现。
- 顺序执行: 单线程也自然保证了所有请求的顺序执行。
单线程为何依然高效?
但是,单线程模型并不意味着低效。 恰恰相反,由于其操作主要在内存中进行,Redis 能够提供极高的吞吐量和低延迟的响应。
Redis 6.0 的多线程
此外,Redis 6.0 引入了多线程的功能。
重点在于:这个多线程并非用来执行命令,而是专门用来处理网络 I/O 部分(例如读取和解析请求、写回响应)。
这样做可以充分利用多核 CPU 资源,减少网络 I/O 阻塞带来的性能损耗,而真正执行命令的核心部分,依然是单线程的,从而保留了上述的所有优点。
3.13 说一说Redis持久化机制有哪些
AOF 日志 (Append-Only File): 每执行一条写操作命令(如 SET、DEL),Redis 就会将这条命令以追加(Append)的方式写入到一个日志文件(AOF 文件)里。恢复时,Redis 会重新执行一遍 AOF 文件中的所有命令。
RDB 快照 (Snapshot): 在某一时刻(例如定时或手动触发),将 Redis 内存中的所有数据生成一个“快照”,并以二进制的方式完整地写入磁盘上的一个 RDB 文件中。恢复时,直接加载这个快照文件即可。
混合持久化方式 (Mixed Persistence): 这是 Redis 4.0 新增的方式,它集成了 RDB 和 AOF 的优点。在持久化时,它会先将当前内存数据以 RDB 的形式写入文件开头,然后再将后续的写操作命令以 AOF 的形式追加到文件末尾。
2025.11.19 Day19
3.14 介绍一下Redis缓存雪崩和缓存穿透,如何解决这些问题?
1. 缓存雪崩 (Cache Avalanche)(多个物品同时下架无法满足正常访问)
- 定义: 指在某个时间点,大量缓存 Key 几乎同时失效(例如设置了相同的过期时间)。这导致海量的请求无法命中缓存,瞬间“雪崩”般地全部直接访问数据库或其他后端系统,从而导致系统负载剧增甚至崩溃。
- 解决方案:
- 分散过期时间:通过合理设置缓存的过期时间(例如,在基础时间上增加一个随机值),分散缓存的失效时间点,避免集体失效。
- 永不过期:采用逻辑过期(或永不过期)的策略,结合定时任务(或异步队列)来定期更新缓存,而不是依赖物理过期。
2. 缓存击穿 (Cache Breakdown)(一个物品下架,被1000个人恶意访问)
- 定义: 指某一个热点 Key(访问极其频繁的数据),在缓存中不存在(通常是刚刚过期或被清理),但数据库中存在。 当大量并发请求同时查询这个刚失效的热点 Key 时,缓存无法命中,导致这些请求全部“击穿”缓存,直接访问数据库,给数据库带来巨大压力。
- 解决方案:
- 互斥锁:采用互斥锁(例如分布式锁),只允许第一个请求去查询数据库,其他请求排队等待。当第一个请求将数据查询结果写入缓存后,释放锁,其他请求即可从缓存中获取数据。
- 热点数据永不过期:对极热点的数据采用逻辑过期策略。
3. 缓存穿透 (Cache Penetration)(没有个物品,被1000个人反复来访问)
- 定义: 指查询一个在缓存和数据库中都“不存在”的数据。 由于这个数据始终无法被缓存,导致每次请求都“穿透”缓存,直接访问数据库,增加了数据库的无谓负载。这种情况尤其容易被攻击者利用,构造大量不存在的 Key 来恶意访问系统。
- 解决方案:
- 布隆过滤器 (Bloom Filter):在缓存前置一个布隆过滤器,用于快速过滤掉这些必定不存在的 Key 的恶意请求,根本不让它们接触后端。
- 缓存空值 (Cache Null Value):当数据库查询确认数据不存在时,也将这个“空结果”(如
null)写入缓存(并设置一个较短的过期时间),防止后续请求再次穿透。 - 参数合法性校验:在查询数据库前,先对请求参数进行合法性校验(如ID是否合规),过滤掉非法请求。
3.15 如何保证数据库和缓存的一致性?
1. Cache Aside (旁路缓存模式)
这是最常用的模式,由应用层(调用方)来维护缓存和数据库的一致性。
- 原理:
- 读取 (Read):
- 先从缓存中读取数据。
- 如果缓存命中(有数据),则直接返回。
- 如果缓存未命中(没数据),则再去数据库里面读数据,读取成功后,把数据放回缓存中,然后再返回。
- 更新 (Write):
- 先把数据持久化到数据库(更新 DB)。
- 然后再让缓存失效(删除 Cache)。
- 读取 (Read):
- 问题:
- 问题1 (写-读并发):假如有两个操作(一个更新一个查询)。第一个更新操作先更新了数据库,但在还没来得及删除缓存时,另一个查询操作进来了,此时可能拿到的就是旧的数据。不过,更新操作马上会让缓存失效,所以后续的查询可以保证数据的一致性。
- 问题2 (读-写并发导致脏数据):一个读操作没有命中缓存,于是到数据库中取数据。此时,来了一个写操作,写完数据库后,让缓存失效。然后,之前的那个读操作(此时拿到的是旧数据)才执行完毕,并把老的数据放回了缓存,这也会造成脏数据。
- 可行性:
- 出现问题2的概率其实非常低。因为它需要同时达成“读缓存时缓存失效”并且“有并发写的操作”。
- 数据库的读写速度要比缓存慢得多,所以要让“读操作在写操作之前进入数据库,并且在写操作(及删除缓存)之后才更新缓存”,这种时序发生的概率比较低。
2. Read/Write Through (读/写穿透模式)
- 原理:
- 应用层认为后端是一个单一的存储,更新数据库(Repository)的操作由缓存服务自己来代理。应用层只与缓存交互,由缓存服务自己来维护缓存和数据库的一致性。
- Read Through (读穿透):
- 在查询操作中更新缓存。当缓存失效的时候,Cache Aside 策略是由调用方(应用层)负责把数据加载入缓存,而 Read Through 则是用缓存服务自己来加载数据,这个过程对调用方是透明的。
- Write Through (写穿透):
- 当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。
- 如果命中了缓存,则先更新缓存,然后再由缓存自己同步更新数据库(这是一个同步操作)。
3. Write Behind (异步回写模式)
- 原理:
- 在更新数据的时候,只更新缓存,不立即更新数据库。
- 缓存会异步地、批量地将更新操作同步回数据库。
- 优点:让数据的 I/O 操作非常快(因为只操作内存)。
- 缺点:数据不是强一致性的,而且如果缓存在异步刷盘前宕机,数据可能会丢失。
- 关于“第二步失效”问题的补充: (此条是针对 Cache Aside 模式的补充说明)
- “第二步失效”(即删除缓存)导致不一致的可能性极小。因为缓存删除(失效)只是标记一下无效的软删除,可以看作不耗时。
- 如果真的会出问题,一般是程序在写数据库那里就没有完成(例如发生了异常),或者有人故意在写完数据库后,休眠很长时间再来删除缓存。