跳转至

MySQL⚓︎

为什么数据库系统(如 MySQL InnoDB)通常倾向于实现自己的 Page Pool,而非依赖操作系统的 Page Cache?⚓︎

  • 保障 WAL 写入顺序:数据库需要精确控制脏页的刷盘时机(确保日志先于数据落盘),操作系统的异步刷盘机制无法保证此顺序,容易导致崩溃恢复失败。
  • 避免缓存污染:操作系统采用通用 LRU 淘汰策略。数据库可以通过定制化淘汰算法(如冷热隔离),防止全表扫描导致热点数据被置换。
  • 避免双重缓存与内存浪费:数据库通过 O_DIRECT 标志位绕过内核缓存,直接进行磁盘 I/O,节省物理内存供自身 Buffer Pool 使用。
  • 并发控制:数据库可对内存页实施细粒度的并发控制(如 Latch、Pin)。

B+ 树并发控制中的 Crabbing 协议,悲观策略与乐观策略有何不同?⚓︎

Crabbing 协议通过自顶向下逐级加锁、释放锁来保障结构一致性。

  • 悲观策略:假设每次写入均导致结构分裂。全程获取排他锁(X Lock)。如果子节点属于安全节点(有剩余空间),则释放祖先节点锁。并发度极低。
  • 乐观策略:假设分裂是小概率事件。全程仅获取共享锁(S Lock),直达叶子节点再获取排他锁。如果叶子节点满载需分裂,则回滚操作,重新以悲观策略执行。并发度高。
安全节点(Safe Node)定义

安全节点是指在当前操作下,不会发生结构分裂的节点。

对于插入(Insert)操作,如果一个节点还没满(有空闲空间),那它就是安全的。因为插入新数据不会导致它分裂。

对于删除(Delete)操作,如果一个节点的数据量大于半满(> 50%),那它就是安全的。因为删除一条数据不会导致它与兄弟节点合并。


悲观锁与乐观锁的区别及各自的适用场景是什么?⚓︎

  • 悲观锁:假设冲突概率极高,采取“先加锁、后操作”策略(如 SELECT FOR UPDATE)。彻底消除冲突,但引起线程阻塞和上下文切换开销,可能死锁。适用于写多读少或强冲突场景。
  • 乐观锁:假设冲突概率低,无锁读取并计算,仅在写回时进行冲突检测(如比对 Version 字段或利用底层的 CAS 指令)。无阻塞开销性能高,但若冲突密集会导致大量自旋重试消耗 CPU。适用于读多写少的场景。

  • 结构改造:B-link Tree 在所有节点引入右兄弟指针(Right Link)与最高键值(High Key)。
  • 无锁分裂逻辑:叶子节点分裂时,仅获取当前节点写锁,新节点通过右指针挂载,随后释放原节点写锁。父节点指针的更新可异步延迟处理(Lazy Update)。
  • 读查询补偿: 读操作若在目标节点未发现数据且目标键大于 High Key,则沿右指针向右侧遍历(While 循环),从而在未更新父节点的情况下仍能正确定位数据,彻底消灭死锁条件与乐观回滚。

为什么在并发环境下,B 树的节点合并比节点分裂更危险?PostgreSQL 如何解决?⚓︎

危险点:直接释放节点内存可能导致正在沿旧指针或右兄弟指针遍历的读线程访问无效内存(悬空指针),引发进程崩溃。合并操作需跨节点甚至跨层级获取排他锁,极易引发死锁。

软删除机制:PostgreSQL 引入半死状态(Half-Dead)。空节点不立即回收物理内存,仅标记为废弃,读线程遇此标记按右指针继续遍历。随后通过后台 VACUUM 进程,在确认无读事务关联时执行安全的物理清理与指针重组。


扩展哈希、线性哈希与 Split-Ordered Hash 各自的设计理念、优缺点与适用场景是什么?⚓︎

  • 扩展哈希(Extendible Hashing): 引入目录结构(基于全局/局部深度)。查找仅需 1 次 I/O。缺陷是在触发全局深度增加时,目录需连续内存翻倍,可能导致系统阻塞。
  • 线性哈希(Linear Hashing): 无目录结构,维护一个分裂指针 S 顺序触发桶分裂,扩容平滑。缺陷是冲突时产生溢出链表,影响现代 NVMe 存储的并发读取性能。
  • Split-Ordered Hash: 基于二进制反转排序的无锁链表结构。通过插入书签指针进行扩容,无需数据搬运,高度优化了并发修改性能。
  • 现代融合(如 NVMe 优化): 结合多级前缀树目录(避免目录全局翻倍)与后台线性顺序合并(消除溢出链表),实现无锁化的高并发引擎。
哈希类型 设计理念 优点 缺点 适用场景
扩展哈希 目录结构(全局/局部深度) 查找仅需 1 次 I/O 全局深度增加时目录翻倍,可能阻塞系统 适合内存受限且读写负载较均衡的场景
线性哈希 无目录结构,分裂指针顺序触发 扩容平滑,无全局翻倍 冲突时产生溢出链表,影响并发性能 适合写入频繁且对扩容平滑性要求高的场景
Split-Ordered Hash 二进制反转排序的无锁链表结构 无锁化高并发性能,无需数据搬运 适合高并发写入且对性能要求极高的场景

数据库 SQL 标准定义的四大隔离级别及其解决的异常问题是什么?⚓︎

  • 读未提交 (Read Uncommitted): 最低级别,允许脏读、不可重复读、幻读。
  • 读已提交 (Read Committed): 事务仅能读取已提交的数据。阻止了脏读。
  • 可重复读 (Repeatable Read): 确保事务内多次读取结果一致。阻止了脏读和不可重复读。(注:MySQL InnoDB 通过 MVCC 和 Next-Key Lock 在此级别下实际也解决了绝大部分幻读问题)。
  • 串行化 (Serializable): 事务强制排队串行执行。阻止了脏读、不可重复读和幻读,并发性能最低。
并发读取异常
  1. 脏读 (Dirty Read):事务 A 修改了数据但还未提交,事务 B 读到了这个修改后的数据。如果事务 A 后来回滚了,那么事务 B 读到的就是根本不存在的脏数据。
  2. 不可重复读 (Non-Repeatable Read):事务 A 在执行过程中,第一次读取了某行数据。接着,事务 B 把这行数据给修改了并提交了。当事务 A 第二次去读同一行时,发现数据居然变了。同一个事务里两次读取结果不一致。
  3. 幻读 (Phantom Read):事务 A 执行了一次范围查询,结果返回了满足条件的若干行。接着,事务 B 插入了一行满足事务 A 查询条件的新数据并提交了。当事务 A 再次执行同样的范围查询时,发现多了一行数据。这种现象就叫幻读。

评论