掀起你的盖头来之《数据库揭秘》-1-数据库三大组成 + 存储层解析

71 阅读10分钟

数据库

1. 数据库的宏观架构:三大核心模块

这是我们理解数据库的最高层视角,它定义了数据库的三个主要职责:

  • SQL层: 负责接收、解析并优化用户的SQL指令,是数据库的“大脑和嘴巴”。
  • 事务层: 负责保证数据操作的ACID特性,处理并发控制,是数据库的“秩序和规则”。
  • 存储层: 负责将数据真实地写入磁盘和从磁盘读入内存,是数据库的“仓库和双手”。

2. 深入存储层:数据的“仓库管理员”

  • 核心使命: 负责数据在磁盘和内存上的存储、检索和管理,并向上层(事务层、SQL层)提供细粒度的数据操作接口。

  • 可插拔的“心脏”:存储引擎

    • 定义: 存储引擎是存储层的具体实现,它像一个可插拔的模块,决定了数据将以何种方式被组织和管理。

    • 例子: MySQL支持多种存储引擎,以适应不同场景的需求。

      • InnoDB: 最常用的,支持事务和行级锁。
      • MyISAM: 不支持事务,但读取性能高。
      • Memory: 数据存储在内存中,速度极快但断电即失。
      • MyRocks: 基于LSM树,写性能非常出色。

3. 存储引擎的设计蓝图:两大组成部分

一个存储引擎的内部设计,可以被清晰地拆分为两个关键部分:

存储结构 = ① 数据文件组织形式 + ② 索引文件组织形式

  • 数据文件组织形式: 决定了“一行行的数据”本身在磁盘上是如何排列的。
  • 索引文件组织形式: 决定了“为了快速查找数据而建立的索引”是如何排列的。(这部分就是我们暂时移走的B+树、LSM树等内容)

4. 数据的物理布局:数据文件组织形式

这部分对性能有影响,但通常不如索引结构那么关键。

  1. 索引组织表 (Index-Organized Table)

    • 特点: 数据文件本身就是按主键索引的顺序存储的,数据“长”在B+树的叶子节点上。
    • 优点: 基于主键的查询和范围查询非常快,因为数据是物理连续的。
    • 缺点: 写入时可能需要移动大量数据来维持顺序,二级索引查询需要“回表”。
    • 代表: InnoDB 引擎。
  2. 堆组织表 (Heap-Organized Table)

    • 特点: 数据行被无序地存放在一个“堆”文件里,插入数据时直接在文件末尾追加即可。索引和数据是分离存储的。
    • 优点: 写入速度快,因为不需要维持任何顺序。
    • 缺点: 读取时如果没有索引,就必须全表扫描。所有索引查询都需要“回表”。
    • 代表: OraclePostgreSQL
  3. 哈希组织表 (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发展)

image.png

B树的性能瓶颈:SMO 分裂

image.png

第一幕:问题的起源 (你的笔记起点)

  • 冲突: 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)

  • 具体做法:

    1. 写操作先写内存(MemTable),极快。
    2. 内存满了,排序后顺序地写到磁盘,形成一个不可变的SSTable文件。
    3. 读操作怎么办?因为数据散落在内存和多个SSTable里,所以读起来比较麻烦,需要合并多路结果。这就是LSM树“写强读弱”的原因。
  • 与SSD的完美邂逅: 这个“放弃随机写,拥抱顺序写”的哲学,恰好完美契合了SSD的特性。SSD虽然随机读性能很强,但它的写入(尤其是修改)有“写放大”问题。LSM树的覆盖写(本质是追加写)天然地避免了这个问题,最大化了SSD的性能和寿命。

事务隔离级别笔记

核心:为什么要隔离?

为了解决多个事务并发时,可能出现的三大问题(也叫“并发异常”)。

  1. 脏读 (Dirty Read)

    • 一句话理解:读到了另一个事务未提交的、最终可能被撤销的“脏”数据。
    • 关键词:未提交
  2. 不可重复读 (Non-Repeatable Read)

    • 一句话理解:在同一个事务内,两次读取同一行数据,结果值不同。
    • 关键词:同一行,值变了 (被 UPDATE 了)
  3. 幻读 (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)
可串行化✅ (解决)✅ (解决)✅ (解决)

记忆要点 & 核心思想

  1. 权衡关系:隔离级别越高,数据越安全(一致性越强),但并发性能越差。
  2. 实现核心MVCC 是实现“读已提交”和“可重复读”这两个级别的幕后功臣。
  3. MySQL默认:牢记 MySQL InnoDB 默认使用可重复读 (RR) 级别,并且因为它有“间隙锁”这个大招,所以幻读问题也基本不用担心。