数据库
1. 数据库的宏观架构:三大核心模块
这是我们理解数据库的最高层视角,它定义了数据库的三个主要职责:
- SQL层: 负责接收、解析并优化用户的SQL指令,是数据库的“大脑和嘴巴”。
- 事务层: 负责保证数据操作的ACID特性,处理并发控制,是数据库的“秩序和规则”。
- 存储层: 负责将数据真实地写入磁盘和从磁盘读入内存,是数据库的“仓库和双手”。
2. 深入存储层:数据的“仓库管理员”
-
核心使命: 负责数据在磁盘和内存上的存储、检索和管理,并向上层(事务层、SQL层)提供细粒度的数据操作接口。
-
可插拔的“心脏”:存储引擎
-
定义: 存储引擎是存储层的具体实现,它像一个可插拔的模块,决定了数据将以何种方式被组织和管理。
-
例子: MySQL支持多种存储引擎,以适应不同场景的需求。
- InnoDB: 最常用的,支持事务和行级锁。
- MyISAM: 不支持事务,但读取性能高。
- Memory: 数据存储在内存中,速度极快但断电即失。
- MyRocks: 基于LSM树,写性能非常出色。
-
3. 存储引擎的设计蓝图:两大组成部分
一个存储引擎的内部设计,可以被清晰地拆分为两个关键部分:
存储结构 = ① 数据文件组织形式 + ② 索引文件组织形式
- 数据文件组织形式: 决定了“一行行的数据”本身在磁盘上是如何排列的。
- 索引文件组织形式: 决定了“为了快速查找数据而建立的索引”是如何排列的。(这部分就是我们暂时移走的B+树、LSM树等内容)
4. 数据的物理布局:数据文件组织形式
这部分对性能有影响,但通常不如索引结构那么关键。
-
索引组织表 (Index-Organized Table)
- 特点: 数据文件本身就是按主键索引的顺序存储的,数据“长”在B+树的叶子节点上。
- 优点: 基于主键的查询和范围查询非常快,因为数据是物理连续的。
- 缺点: 写入时可能需要移动大量数据来维持顺序,二级索引查询需要“回表”。
- 代表: InnoDB 引擎。
-
堆组织表 (Heap-Organized Table)
- 特点: 数据行被无序地存放在一个“堆”文件里,插入数据时直接在文件末尾追加即可。索引和数据是分离存储的。
- 优点: 写入速度快,因为不需要维持任何顺序。
- 缺点: 读取时如果没有索引,就必须全表扫描。所有索引查询都需要“回表”。
- 代表: Oracle 和 PostgreSQL。
-
哈希组织表 (Hash-Organized Table)
- 特点: 通过哈希函数计算数据行的存储位置。
- 优点: 等值查询(WHERE id = ?)极快,理论上O(1)。
- 缺点: 不支持范围查询,哈希冲突时性能会下降。
- 场景: 较少作为主要的表组织形式,常用于某些特定场景。
5. 并发控制的基石:锁
无论数据如何组织,只要有多人同时访问,就需要“锁”来维持秩序。这是事务层和存储层都会深度依赖的基础机制。
- S锁 (共享锁 / Shared Lock) : 也叫读锁。多个事务可以同时持有S锁,大家都可以读取数据,但此时不允许任何事务获取X锁。
- X锁 (排他锁 / Exclusive Lock) : 也叫写锁。一旦某个事务持有了X锁,其他任何事务都不能再获取S锁或X锁,直到该锁被释放。
6. 索引文件组织形式
In-place update structure:就地更新结构,B树,B+树,写弱读强(机械硬盘)
Out-of-place update structure:异位更新结构,LSM树,写强读弱(SSD发展)
B树的性能瓶颈:SMO 分裂
第一幕:问题的起源 (你的笔记起点)
- 冲突: CPU和内存快如闪电,但数据最终要落到磁盘上,而磁盘(尤其是机械硬盘HDD)慢如蜗牛。
- 核心矛盾: 磁盘最憎恨的操作是随机I/O(磁头寻道),最喜欢的操作是顺序I/O。
- 目标: 我们设计的任何数据结构,首要目标就是尽可能减少昂贵的随机I/O次数。
第二幕:B+树的崛起 (In-place update 的解决方案)
- 天才构想: 既然一次I/O可以读一整页(例如16KB),那我们能不能让一个节点里多放一些“路标”(索引键),让树变得“矮胖”?这样从根节点走到叶子节点,经过的层数就少了,I/O次数也少了。—— B树诞生。
- 完美进化: 我们发现,查询经常是范围查询(WHERE id > 100)。如果在叶子节点之间拉一根“链条”(双向链表),范围查询就变成了顺序I/O!同时,把非叶子节点的数据都“推”到叶子节点,让非叶子节点只存路标,这样它就能存更多路标,树就更“矮胖”了。—— B+树封神。这就是为什么你的笔记里写B+树“读强”。
第三幕:B+树的“中年危机” (你提到的性能瓶颈)
-
新问题: B+树的写入是“就地更新 (In-place update) ”。当一个节点满了,再插入数据,就需要分裂 (SMO) 。这个分裂操作,可能会引发一连串的连锁反应(父节点也满了),这是一个随机写操作,而且可能加重锁的争用。在写入压力大的场景下,这里就成了瓶颈。
-
英雄登场: 这时,你笔记里那些B树的“变体”就出场了,它们都是为了缓解B+树的写入瓶GI颈:
- B-Link树: 解决的是高并发下的锁争用问题。通过给节点增加一个“右指针”,使得在节点分裂时,其他查找操作可以“绕路”过去,而不用等待分裂完成,大大减少了锁的持有时间。
- 惰性B树 (Lazy B-Tree) : 解决的是分裂太频繁的问题。在每个节点里搞个小“更新缓冲区”,写操作先进缓冲区,满了再批量处理,减少了SMO的频率。
- COW-B树 (写时复制) : 解决的是更新原子性和并发问题。不直接修改原节点,而是复制一份出来修改,改完后原子地把指针指向新节点。这避免了对数据结构的长时间加锁。
- Bw树: 这是更现代的“集大成者”,它用“增量更新 (delta record) ”的方式来记录修改,把随机写变成了对一个链表的顺序追加,思想上已经开始向LSM树靠近了。
第四幕:LSM树的“革命” (Out-of-place update 的解决方案)
-
颠覆性思想: 既然随机写这么痛苦,我们干脆彻底放弃“就地更新” !所有写操作,都变成顺序追加 (append-only) 。
-
具体做法:
- 写操作先写内存(MemTable),极快。
- 内存满了,排序后顺序地写到磁盘,形成一个不可变的SSTable文件。
- 读操作怎么办?因为数据散落在内存和多个SSTable里,所以读起来比较麻烦,需要合并多路结果。这就是LSM树“写强读弱”的原因。
-
与SSD的完美邂逅: 这个“放弃随机写,拥抱顺序写”的哲学,恰好完美契合了SSD的特性。SSD虽然随机读性能很强,但它的写入(尤其是修改)有“写放大”问题。LSM树的覆盖写(本质是追加写)天然地避免了这个问题,最大化了SSD的性能和寿命。
事务隔离级别笔记
核心:为什么要隔离?
为了解决多个事务并发时,可能出现的三大问题(也叫“并发异常”)。
-
脏读 (Dirty Read)
- 一句话理解:读到了另一个事务未提交的、最终可能被撤销的“脏”数据。
- 关键词:未提交
-
不可重复读 (Non-Repeatable Read)
- 一句话理解:在同一个事务内,两次读取同一行数据,结果值不同。
- 关键词:同一行,值变了 (被 UPDATE 了)
-
幻读 (Phantom Read)
- 一句话理解:在同一个事务内,两次查询同一个范围,返回的行数不同。
- 关键词:一个范围,行数变了 (被 INSERT 或 DELETE 了)
四大隔离级别:四堵不同强度的“隔离墙”
现在,我们来看看数据库为了解决上述“幽灵”问题,修建的四堵墙,从最 flimsy (薄弱) 到最 aolid (坚固)。
- 隔离墙强度:几乎是透明的,等于没有墙。
- 行为:允许你读到其他事务未提交的数据。
- 能解决的问题:无。
- 会遇到的“幽灵” :脏读、不可重复读、幻读。
- 一句话总结:性能最好,但最不安全,几乎不用。
- 隔离墙强度:一堵能挡住“脏东西”的墙。
- 行为:你只能读到其他事务已经提交的数据。
- 能解决的问题:脏读。
- 会遇到的“幽灵” :不可重复读、幻读。
- 与你的知识关联:在RC级别下,MVCC是这样工作的 -> 你的事务中,每一次SELECT都会生成一个新的ReadView(快照) 。所以,当其他事务在你两次查询之间COMMIT了,你的下一次SELECT会用新的快照,就能看到那个已提交的修改,导致了不可重复读。
- 一句话总结:主流选择之一 (Oracle, PG默认),平衡了性能和数据安全。
-
隔离墙强度:一堵让你产生“时间静止”错觉的墙。
-
行为:在一个事务开始后,你多次读取同一行数据,看到的结果永远和你第一次读到时一样,仿佛时间被冻结了。
-
能解决的问题:脏读、不可重复读。
-
会遇到的“幽灵” :理论上存在幻读。
-
与你的知识关联:
- 在RR级别下,MVCC是这样工作的 -> 你的事务中,只有第一条SELECT语句会生成一个ReadView(快照) ,之后该事务内所有的SELECT都会复用这同一个快照。因此,无论其他事务如何修改和提交,你看到的永远是你刚进事务时的那个“世界”。
- MySQL InnoDB的特殊之处:虽然标准RR级别有幻读问题,但InnoDB通过间隙锁(Gap Lock) 这种机制,在很大程度上也解决了幻读问题。这也是为什么MySQL敢于将RR作为默认级别。
-
一句话总结:MySQL InnoDB默认级别,提供了非常高的数据一致性保证。
- 隔离墙强度:一堵坚不可摧的钢筋混凝土墙。
- 行为:所有事务都必须排队,一个接一个地执行,完全不存在并发。
- 能解决的问题:脏读、不可重复读、幻读。
- 会遇到的“幽灵” :无。
- 与你的知识关联:它不再依赖MVCC,而是通过对所有读写操作都加锁的方式,强制事务串行执行。
- 一句话总结:最安全,但性能最差,只在对数据一致性有极端要求的场景下使用。
终极复习表格 (面试/复习必备)
| 隔离级别 (由低到高) | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| ① 读未提交 | ❌ (会发生) | ❌ (会发生) | ❌ (会发生) |
| ② 读已提交 | ✅ (解决) | ❌ (会发生) | ❌ (会发生) |
| ③ 可重复读 | ✅ (解决) | ✅ (解决) | ❌ (理论上) / ✅ (InnoDB) |
| ④ 可串行化 | ✅ (解决) | ✅ (解决) | ✅ (解决) |
记忆要点 & 核心思想
- 权衡关系:隔离级别越高,数据越安全(一致性越强),但并发性能越差。
- 实现核心:MVCC 是实现“读已提交”和“可重复读”这两个级别的幕后功臣。
- MySQL默认:牢记 MySQL InnoDB 默认使用可重复读 (RR) 级别,并且因为它有“间隙锁”这个大招,所以幻读问题也基本不用担心。