English

A database management system organizes shared data and provides operations with specified query, concurrency, and recovery semantics. Its guarantees depend on the system and configuration. A database can enforce a uniqueness constraint, for example, but cannot infer an unstated business rule or correct an application that updates the wrong account.

Two useful starting concepts are the query and the transaction. A declarative query states a desired result; the system chooses a physical execution plan. A transaction groups changes under atomicity and isolation rules. Query optimization need not find the globally fastest plan, and transactions need not use one particular combination of locking and versioning. The interfaces remain useful because they separate the requested behavior from many implementation choices.

The relational model describes data as relations and supports operations whose results can themselves be queried. SQL draws on this foundation but also includes duplicates, NULL, ordering, and other features that require their own semantics. Understanding that distinction prevents mathematical set reasoning from being applied to SQL without checking its assumptions.

Start with files and memory (§4.2), data structures (§2.6), and sets and relations (§2.2). A small schema with two related tables is enough to explore constraints, joins, indexing, and concurrent updates.

From Hierarchical Files to the Relational Model and Beyond

Before relational systems, data management already included shared files, indexes, hierarchical databases, and network databases. An important difficulty was coupling applications to access paths and record relationships: reorganizing storage or changing a navigation path could require changing application code. Data independence became a central objective, alongside concurrency and recovery.

IBM’s IMS developed in the 1960s from work supporting the Apollo program. It organized related records hierarchically and exposed navigational access. Such organization served many predictable access patterns well; other questions could require awkward traversals or additional structures. The relational proposal addressed this coupling between the programmer’s access procedure and the organization of the data, rather than inventing shared data management from nothing.

Codd’s 1970 relational-model paper argued for greater independence between applications and data representation. Relations and operations on them described a logical view, allowing a database implementation to choose access paths. A programmer could ask for matching records without hard-coding a pointer traversal. This separation made changes to storage and indexing less likely to require rewriting every application query.

System R at IBM and Ingres at Berkeley investigated whether relational ideas could support practical systems. System R developed SQL and influential cost-based optimization techniques; Ingres used QUEL and contributed another major implementation and research tradition. Optimizers translated logical queries into alternative physical plans, making data independence practical rather than merely a notation change. Transaction and recovery research, including work by Jim Gray and many collaborators, developed alongside this effort.

Bayer and McCreight introduced B-trees in work published around 1970–1972. Their high branching factor matches block-oriented storage: one page access examines many separator keys, reducing the number of pages on a search path. A tree with fan-out near 100 can cover about a million leaf positions in roughly three branching levels. Real occupancy, key size, cached levels, and duplicate handling change the details. The important insight is to count block transfers as well as comparisons.

Commercial relational systems, including Oracle and IBM DB2, helped SQL become a common interface for business data. Standardization supported shared skills and tools, while implementations continued to differ in types, functions, transaction behavior, and optimization. A shared query language did not make all databases operationally interchangeable.

Large online services increased interest in partitioning data across machines and keeping replicas near users. Partitions introduced the coordination questions developed in §4.6. CAP does not say to choose any two features from a menu: if communication is partitioned, a system cannot guarantee both linearizable behavior and a response to every request at every nonfailed node. Data models, SQL support, and transaction interfaces are separate design dimensions.

Bigtable used a distributed sorted-map abstraction with atomic operations on a single row. Chubby supplied a strongly consistent coordination service; it was not an eventually consistent alternative chosen to keep every partition available. Amazon’s Dynamo explored high availability with version reconciliation under different requirements. These systems motivated different kinds of NoSQL stores, but the label itself specifies neither consistency nor transaction guarantees. Dynamo and the later DynamoDB service should also not be treated as identical designs.

Spanner combined replication, distributed transactions, and bounded clock uncertainty to provide external consistency across geographically distributed data. This respects real-time ordering as well as transaction serializability. It does not overturn CAP: when the required quorum or communication is unavailable, operations can wait or fail. The result showed how a particular combination of timing assumptions, consensus, and coordination could make a strong interface useful at scale. The costs include communication latency and the mechanisms needed to maintain those assumptions.

Database designs continued to specialize for transactions, analytical scans, time series, graphs, search, and vector similarity. Some products combine several models, and an extension can add a capability without making the host system a separate category. Compare query patterns, consistency, durability, data layout, and operational requirements before choosing a design.

The Relational Model, Transactions, and Storage

Keep the logical data model, transaction semantics, and physical storage design separate. The same relation can be stored by rows or columns; a key-value interface can provide strong consistency; a SQL interface can expose weaker isolation. Product labels cannot replace the specific guarantee required by an application.

The Relational Model and Query Processing

Relational algebra combines selection, projection, joins, and set operations, producing relations that can be used in further operations. For example, join Orders to Customers through a customer identifier, then select the required region. Keeping customer details in one place avoids changing many orders when an address changes. Keys, foreign keys, and suitable normalization express relationships and reduce update anomalies. SQL often uses bag semantics rather than sets: a join can multiply rows, and NULL affects predicates through three-valued logic. Check these details before applying algebraic rewrites.

The orders example also explains an optimizer’s freedom. For an inner join, if the filter is simply Customers.region = 'East', one valid plan first joins all customers to their orders and then filters. Another filters the customers first and joins only those rows to orders. Every surviving joined row must contain a customer from that region, so the two plans produce the same result, including duplicate multiplicities under bag semantics. Filtering first can reduce intermediate work. This selection pushdown follows from the filter depending only on customer fields; outer joins and other predicates need their own justification. Equivalence establishes that a plan is allowed, while cost estimates help choose among allowed plans.

A query optimizer searches for a useful execution plan: access paths, join order, and join algorithms. Statistics estimate row counts and costs, and search heuristics limit how many alternatives are considered. Suppose a filter is estimated to return ten rows but actually returns a million; a nested-loop plan chosen for the estimate may perform far more work than expected. Compare estimates with measured rows, data distributions, and available indexes before rewriting the query or forcing a plan.

Indexes exchange extra storage and update work for faster selected access paths. A B+ tree supports ordered lookup and ranges; hash structures support equality lookup without inherently providing order. Neither is universally faster. A composite index on (customer_id, created_at) can help find one customer’s orders in a time range, while a query on time alone may need a different path depending on the engine. An index containing all required columns can reduce table access, subject to visibility and storage rules. Use the actual plan and workload to assess the benefit, including write cost.

Transactions: ACID and Concurrency Control

Atomicity makes a transaction’s covered changes commit or abort as a unit. Consistency concerns preservation of the declared invariants; the application must express the right ones. Isolation specifies which concurrent histories are allowed. Serializable isolation makes committed results equivalent to some serial execution, while weaker levels permit more histories. Durability protects committed effects against the failures covered by the storage and replication contract, not every imaginable loss of hardware and backups.

Two-phase locking separates an acquisition phase from a release phase; strict variants retain write locks until transaction completion. MVCC stores multiple versions and lets readers use an appropriate snapshot. The snapshot may be chosen per statement or per transaction, depending on the isolation level. Versioning reduces some blocking but does not eliminate conflicts or make every execution serializable. Old versions also need reclamation.

Write skew gives a concrete example. Two doctors are on call, and at least one must remain. Each transaction reads a snapshot showing both on call, then takes its own doctor off call. If the writes affect different rows, snapshot isolation can allow both commits, leaving nobody on call. A serializable execution could not produce that result from these checks. Options include serializable transactions with retry, or a locking protocol that protects the shared invariant. Making each row update atomic is insufficient.

Isolation names require product-specific interpretation. PostgreSQL’s documentation explains its statement snapshots, transaction snapshots, and serialization failures. An application must handle a rejected transaction by retrying the appropriate unit of work; irreversible external effects such as sending an email need separate coordination. Serializable execution need not physically run all transactions one at a time.

Write-ahead logging requires recovery records to reach durable storage before the affected data pages are flushed. Changes may already exist in memory. Commit acknowledgment requires the durability conditions promised by the configuration. ARIES uses analysis, redo, and undo, with redo reconstructing relevant history before undo removes incomplete transactions; other engines use different recovery designs. Logging, checksums, write ordering, and backups solve related but distinct failure problems.

Storage: B-Trees, LSM-Trees, and the Column Revolution

Storage engine design determines which workloads a database handles efficiently. The choice of data structure encodes assumptions about read-to-write ratio, key distribution, required operation types, and acceptable tradeoffs between read and write amplification.

B+ trees keep ordered keys in a balanced page structure. Internal pages guide navigation, and leaf pages hold entries, which may be rows or references depending on the design. High fan-out limits page reads; updates can modify pages and sometimes split them. Some engines combine B-trees with copy-on-write rather than updating every page in place. Evaluate range access, locality, caching, and update cost together.

LSM designs buffer updates in memory and flush sorted runs, commonly supported by a write-ahead log for recovery. Compaction merges runs and can discard obsolete versions when it is safe to do so. Tombstones represent deletions until older copies can no longer reappear. Large sequential transfers help amortize writes, but compaction rewrites data and can compete with reads. Bloom filters reduce unnecessary lookups in runs; they do not replace reading a possible match. Read, write, and space amplification depend on compaction policy and workload, not merely the LSM label.

Column-oriented storage groups values by column, so a scan of date and amount can avoid reading unrelated address and description fields. Similar values often compress well, reducing data movement further. Reconstructing many columns for a single record can require extra work, while indexes, caches, and hybrid layouts can improve point access. Row and column layouts favor different patterns; neither label alone determines query speed.

What Studying This Changes

Begin a database design with relationships and invariants: which values identify an entity, which references must remain valid, and which concurrent changes can violate a rule? Then select the transaction boundary and isolation behavior that preserve those rules.

For practice, model customers and orders, compare a join before and after adding an index, and inspect estimated versus actual row counts in the plan. Construct the two-doctor write-skew schedule and explain why per-row atomicity does not protect the cross-row requirement. These exercises separate data modeling, execution cost, and concurrency correctness.

Choose storage and deployment from workload requirements: point queries or scans, update rate, latency, recovery objectives, and operational capacity. A new product name does not remove these questions, and a fast benchmark on one access pattern does not establish suitability for another.

Resources

Books and Texts

Ramakrishnan and Gehrke, Garcia-Molina and colleagues, and Silberschatz and colleagues offer broad database texts covering models, queries, storage, and transactions. Choose one as a primary route and use the others for alternative explanations. Designing Data-Intensive Applications connects these ideas to distributed services. Hellerstein, Stonebraker, and Hamilton’s Architecture of a Database System, the Red Book, and Petrov’s Database Internals add architecture, original papers, and storage implementation perspectives.

Book Role Type
Ramakrishnan & Gehrke, Database Management Systems (3rd ed.) Database models and mechanisms Entry
Garcia-Molina, Ullman & Widom, Database Systems: The Complete Book Broader foundational entry Entry
Silberschatz, Korth & Sudarshan, Database System Concepts (7th ed.) Traditional comprehensive alternative Entry
Database chapters in Kleppmann, Designing Data-Intensive Applications Distributed data systems for practitioners Depth
Hellerstein, Stonebraker & Hamilton, “Architecture of a Database System” (free) Architectural consolidation Depth
Bailis, Hellerstein & Stonebraker (eds.), Readings in Database Systems (free) Foundational paper anthology Reference
Petrov, Database Internals Storage engine implementation Practice
Gray & Reuter, Transaction Processing: Concepts and Techniques Deep transaction reference Reference

Courses and Lectures

CMU 15-445/645 and Berkeley CS 186 connect database mechanisms to implementation exercises. Depending on the offering, projects include buffer management, indexes, execution, and concurrency. For each project, state an invariant and a recovery requirement before coding, then use the tests to examine specific cases rather than treating a passing score as complete correctness.

Course Platform Type
CMU 15-445/645 Database Systems (Pavlo, free) YouTube / CMU course site Entry
Berkeley CS 186 Introduction to Database Systems (free) Berkeley course site Entry

Practice, Tools, and Current Sources

PostgreSQL EXPLAIN shows a planned execution; EXPLAIN ANALYZE actually runs the statement and reports execution measurements. Use controlled data when a statement changes state. Compare estimated and actual rows as well as elapsed time. SQLite documentation and source provide a compact-engine perspective; Use The Index, Luke! develops indexing examples. Jepsen reports connect a named system version and configuration to observed consistency behavior under particular failures.

Resource Platform Type
PostgreSQL EXPLAIN / EXPLAIN ANALYZE PostgreSQL / psql Practice
PostgreSQL Internals documentation (free) postgresql.org/docs Reference
SQLite source code and documentation (free) sqlite.org Reference
Use The Index, Luke! (free) use-the-index-luke.com Reference
Jepsen database consistency analyses (free) jepsen.io Reference
CMU BusTub educational DBMS (free) github.com/cmu-db/bustub Practice
DuckDB internals documentation and DiDi course (free) duckdb.org Reference
CMU 15-445 programming projects (free) CMU course site Practice

Traps

Trap Why it misleads Better response
Stopping at SQL syntax Query results do not reveal plan cost, isolation behavior, or recovery. Study an execution plan and a concurrent transaction schedule alongside SQL.
Treating an ORM as the database model Generated queries and transaction boundaries still determine behavior. Inspect generated SQL, round trips, selected columns, and isolation settings.
Choosing by SQL/NoSQL label The label does not establish queries, consistency, recovery, or scaling costs. Compare the required operations and failure guarantees of specific systems.
Assuming ACID enforces every business rule Isolation levels and application constraints differ. Write the invariant and test whether the chosen isolation level prevents its violating schedule.
Adding indexes without measuring An index adds write and storage costs and may not suit the query. Compare plans and elapsed time, including cases where a scan is cheaper; account for write cost.
Implementing storage without recovery tests Normal-operation tests do not cover partial writes and restart ordering. Use a small storage exercise and inject failures around log and data persistence points.

中文

数据库管理系统组织共享数据,并提供具有指定查询、并发与恢复语义的操作。保证依赖系统与配置。例如,数据库可以强制唯一性约束,却不能推断没有写出的业务规则,也不会纠正更新了错误账户的应用。

两个有用的起点是查询与事务。声明式查询陈述预期结果,系统选择物理执行计划;事务按照原子性和隔离规则组织一组改变。查询优化未必找到全局最快计划,事务也不必使用某一种固定的锁与版本组合。这些接口的价值,在于把所求行为与许多实现选择分开。

关系模型把数据描述为关系,并支持其结果仍可继续查询的操作。SQL 借助这个基础,但也包含重复项、NULL、排序等需要各自语义的特性。理解这个区别,可以避免未经检查就把数学集合推理用于 SQL。

从文件与内存(§4.2)、数据结构(§2.6)、集合与关系(§2.2)开始。具有两张相关表的小型模式,就足以探索约束、连接、索引与并发更新。

从层级文件到关系模型及其之后

关系系统出现之前,数据管理已经包含共享文件、索引、层次数据库与网状数据库。一个重要困难是应用与访问路径、记录关系耦合:重新组织存储或改变导航路径,可能要求修改应用代码。数据独立性与并发、恢复一起成为核心目标。

IBM 的 IMS 在二十世纪六十年代从支持 Apollo 项目的工作中发展起来。它把相关记录组织为层次结构,提供导航式访问。这种组织适合许多可预测的访问模式,其他问题则可能需要复杂遍历或额外结构。关系模型试图处理程序访问过程与数据组织之间的耦合,并非从无到有地发明共享数据管理。

Codd 在 1970 年的关系模型论文中,主张增加应用与数据表示之间的独立性。关系及其操作描述逻辑视图,让数据库实现能够选择访问路径。程序员可以请求匹配记录,而不把指针遍历写死。这种分离减少了存储或索引变化时重写所有应用查询的需要。

IBM 的 System R 与 Berkeley 的 Ingres 研究关系思想能否支持实用系统。System R 发展了 SQL 和有影响的基于成本的优化技术,Ingres 使用 QUEL,贡献了另一条主要实现与研究传统。优化器把逻辑查询转成不同物理计划,使数据独立性不只是记号改变,而能实际工作。事务与恢复研究,包括 Jim Gray 及许多合作者的工作,与之共同发展。

Bayer 与 McCreight 在 1970—1972 年前后发表的工作中提出 B 树。较高分支数适合块存储:一次页面访问检查多个分隔键,减少搜索路径上的页数。分支数接近 100 的树,大约三个分支层就能覆盖一百万个叶位置。实际填充率、键大小、已缓存层数和重复键处理会改变细节。重要思想是同时计算块传输次数与比较次数。

Oracle、IBM DB2 等商业关系系统,帮助 SQL 成为业务数据的常见接口。标准化支持共享技能与工具,实现则仍在类型、函数、事务行为和优化上存在差别。共同查询语言并不使所有数据库在运行上可以互换。

大型在线服务增加了对跨机器分区和在用户附近保存副本的需求。网络分区带来了 §4.6 将讨论的协调问题。CAP 不是让系统从菜单中任选两项功能:通信发生分区时,系统不能同时保证线性一致行为,以及每个无故障节点都回答每个请求。数据模型、SQL 支持与事务接口是另外的设计维度。

Bigtable 使用分布式有序映射抽象,提供单行原子操作。Chubby 则提供强一致的协调服务,并不是为了使每个网络分区都可用而选择的最终一致系统。Amazon 的 Dynamo 在另一组需求下探索高可用性与版本协调。这些系统推动了不同类型的 NoSQL 存储,但标签本身不规定一致性或事务保证。Dynamo 与后来的 DynamoDB 服务,也不能当作相同设计。

Spanner 结合复制、分布式事务和有界时钟不确定性,为跨地域数据提供外部一致性,既满足事务可串行化,也遵守实时顺序。它没有推翻 CAP:所需法定人数或通信不可用时,操作可以等待或失败。这个结果展示了特定的时间假设、共识与协调组合,怎样使强保证接口能在较大规模上实用。成本包括通信延迟,以及维持这些假设所需的机制。

数据库设计继续针对事务、分析扫描、时间序列、图、搜索与向量相似度等工作专门发展。有些产品结合多种模型,扩展也可以增加能力,而不使宿主系统成为另一种独立类别。选择设计前,应比较查询模式、一致性、持久性、数据布局与运行要求。

关系模型、事务与存储

区分逻辑数据模型、事务语义与物理存储设计。同一个关系可以按行或按列存储,键值接口可以提供强一致性,SQL 接口也可以提供较弱隔离。产品标签不能代替应用实际需要的保证。

关系模型与查询处理

关系代数组合选择、投影、连接与集合操作,产生仍可继续运算的关系。例如,通过客户标识连接 Orders 与 Customers,再选择所需地区。把客户信息保存在一处,可以避免地址改变时修改许多订单。键、外键与合适的规范化表达关系,并减少更新异常。SQL 常采用多重集而非集合语义:连接可能增加重复行,NULL 则通过三值逻辑影响谓词。应用代数改写前,应检查这些细节。

订单例子也能说明优化器可以改变什么。对于内连接,如果过滤条件只是 Customers.region = 'East',一种合法计划是先把全部客户与订单连接,再过滤;另一种是先筛选客户,再只用这些客户行连接订单。最终保留的每个连接行都必须包含该地区的客户,因此两种计划产生相同结果,在多重集语义下也保留相同的重复次数。先过滤可以减少中间工作。这种选择下推利用了过滤条件只依赖客户字段这一点;外连接和其他谓词需要各自的论证。等价性确定哪些计划可用,成本估计则帮助从可用计划中选择。

查询优化器搜索有用的执行计划,包括访问路径、连接顺序与连接算法。统计信息估计行数和成本,搜索启发式限制考虑的候选数量。假设过滤条件预计返回十行,实际却返回一百万行;按估计选择的嵌套循环计划,就可能做远多于预期的工作。改写查询或强制计划之前,应比较估计与实测行数、数据分布及可用索引。

索引用额外存储与更新工作,换取选定访问路径的加速。B+ 树支持有序查找和范围,哈希结构支持相等查找,却不天然提供顺序;两者都不是普遍更快。(customer_id, created_at) 上的复合索引可以帮助查找某客户在指定时段的订单,而仅按时间查询可能需要不同路径,具体取决于引擎。包含全部所需列的索引,可以在可见性与存储规则允许时减少表访问。应根据实际计划和工作负载判断收益,并计入写入成本。

事务:ACID 与并发控制

原子性使事务覆盖的改变作为整体提交或中止。一致性关注已声明不变式的保持,应用需要表达正确的不变式。隔离性规定允许哪些并发历史。可串行化隔离使已提交结果等价于某种串行执行,较弱级别则允许更多历史。持久性针对存储与复制约定覆盖的故障保护已提交结果,并不抵御所有可以想象的硬件与备份损毁。

两阶段锁把加锁阶段与释放阶段分开,严格变体把写锁保留到事务完成。MVCC 保存多个版本,让读取使用合适快照。快照可能按语句或按事务选定,取决于隔离级别。版本机制减少部分阻塞,却不会消除冲突,也不会使每种执行都可串行化。旧版本还需要回收。

写偏差提供一个具体例子。两位医生在值班,要求至少留下一位。两个事务各自从快照读到两人都在,再分别把自己对应的医生设为离岗。如果修改不同的行,快照隔离可能允许二者提交,导致无人值班。对这些检查进行可串行化执行,不会得到该结果。可以采用可串行化事务并处理重试,或用加锁协议保护共享不变式。仅使每行更新原子化并不够。

隔离名称需要按具体产品解释。PostgreSQL 文档说明了其语句快照、事务快照与序列化失败。事务被拒绝时,应用必须重试适当的工作单元;发送邮件等不可逆外部效果需要额外协调。可串行化执行并不要求物理上把所有事务一个接一个运行。

预写日志要求恢复记录先于受影响数据页的刷盘,到达持久存储。改变可以已经存在于内存中。确认提交时,必须满足配置所承诺的持久条件。ARIES 使用分析、重做与撤销,先通过重做重建相关历史,再撤销未完成事务;其他引擎采用不同恢复设计。日志、校验和、写入顺序与备份解决相关但不同的故障问题。

存储:B-Trees、LSM-Trees 与列式革命

存储引擎设计决定了一个数据库能高效处理哪些工作负载。数据结构选择编码了关于 read-to-write ratio、key distribution、所需操作类型,以及 read amplification 和 write amplification 之间可接受权衡的假设。

B+ 树在平衡的页面结构中保存有序键。内部页引导导航,叶页保存条目,具体可以是行或引用,取决于设计。较高分支数限制读取页数;更新可能修改页面,有时触发分裂。一些引擎把 B 树与写时复制结合,并非每个页面都原位更新。应共同评估范围访问、局部性、缓存和更新成本。

LSM 设计在内存中缓冲更新,再刷出有序段,通常用预写日志支持恢复。压实合并有序段,并在安全时丢弃过时版本。删除标记在旧副本不再可能重现之前表示删除。较大的顺序传输有助于摊销写入,但压实会重写数据,也会与读取争用资源。布隆过滤器减少对有序段的不必要查询,却不能代替读取可能匹配的结果。读、写和空间放大依赖压实策略与工作负载,不只依赖 LSM 标签。

列式存储按列组织值,使扫描日期与金额时可以避免读取无关地址和描述字段。相近的值通常容易压缩,进一步减少数据移动。为单条记录重建许多列可能需要额外工作,而索引、缓存与混合布局可以改善点访问。行与列布局有利于不同模式,任何一个标签都不能单独决定查询速度。

学习这一部分会改变什么

数据库设计应从关系与不变式开始:哪些值标识实体,哪些引用必须有效,哪些并发改变可能违反规则?随后选择能够保持这些规则的事务边界与隔离行为。

可以建立客户与订单模型,比较加索引前后的连接,并检查计划中的估计行数与实际行数。构造两位医生的写偏差调度,解释为什么逐行原子性无法保护跨行要求。这些练习分开了数据建模、执行成本与并发正确性。

根据点查询或扫描、更新率、延迟、恢复目标与运维能力等需求选择存储和部署。新的产品名称不会消除这些问题,一个访问模式下的快速基准测试,也不证明适合另一种模式。

资源

书籍与文本

Ramakrishnan 与 Gehrke、Garcia-Molina 等人,以及 Silberschatz 等人的数据库教材,广泛覆盖模型、查询、存储与事务。可以选择一本作为主线,其他书用于比较解释。Designing Data-Intensive Applications 把这些思想连接到分布式服务。Hellerstein、Stonebraker 与 Hamilton 的 Architecture of a Database System、Red Book 与 Petrov 的 Database Internals,补充系统架构、原始论文与存储实现视角。

书籍 作用 类型
Ramakrishnan & Gehrke, Database Management Systems(第 3 版) 数据库模型与机制 入门
Garcia-Molina, Ullman & Widom, Database Systems: The Complete Book 更宽泛的基础入口 入门
Silberschatz, Korth & Sudarshan, Database System Concepts(第 7 版) 传统综合替代 入门
Kleppmann, Designing Data-Intensive Applications 中的数据库章节 面向实践者的分布式数据系统 深入
Hellerstein、Stonebraker 与 Hamilton,”Architecture of a Database System”(免费) 架构图景整合 深入
Bailis, Hellerstein & Stonebraker 编,Readings in Database Systems(免费) 奠基论文选集 参考
Petrov, Database Internals 存储引擎实现 实践
Gray & Reuter, Transaction Processing: Concepts and Techniques 深入事务参考 参考

课程与讲座

CMU 15-445/645 与 Berkeley CS 186 把数据库机制连接到实现练习。依学期而定,项目涉及缓冲管理、索引、执行与并发。每个项目开始编码前,可以先说明一个不变式与一项恢复要求,再用测试检查具体情况,而不是把通过评分当作完整正确性。

课程 平台 类型
CMU 15-445/645 Database Systems(Pavlo,免费) YouTube / CMU course site 入门
Berkeley CS 186 Introduction to Database Systems(免费) Berkeley course site 入门

实践、工具与当前资料

PostgreSQL EXPLAIN 显示计划执行,EXPLAIN ANALYZE 则实际运行语句并报告执行测量。语句会改变状态时,应使用受控数据。除了耗时,也比较估计行数与实际行数。SQLite 文档与源码提供紧凑型引擎视角,Use The Index, Luke! 展开索引示例。Jepsen 报告把明确的系统版本和配置,连接到特定故障下观察到的一致性行为。

资源 平台 类型
PostgreSQL EXPLAIN / EXPLAIN ANALYZE PostgreSQL / psql 实践
PostgreSQL Internals documentation(免费) postgresql.org/docs 参考
SQLite source code and documentation(免费) sqlite.org 参考
Use The Index, Luke!(免费) use-the-index-luke.com 参考
Jepsen database consistency analyses(免费) jepsen.io 参考
CMU BusTub educational DBMS(免费) github.com/cmu-db/bustub 实践
DuckDB internals documentation and DiDi course(免费) duckdb.org 参考
CMU 15-445 programming projects(免费) CMU course site 实践

陷阱

误区 为什么会误导 更好的做法
停留在 SQL 语法 查询结果不揭示计划成本、隔离行为或恢复。 在学习 SQL 时,同时研究执行计划与并发事务调度。
把 ORM 当作数据库模型 生成的查询与事务边界仍然决定行为。 检查生成 SQL、往返次数、所选列与隔离设置。
按 SQL/NoSQL 标签选择 标签不建立查询、一致性、恢复或扩展成本。 比较具体系统所需操作与故障保证。
认为 ACID 执行所有业务规则 隔离级别与应用约束不同。 写出不变式,检查选定隔离级别能否排除破坏它的调度。
不测量就增加索引 索引增加写入与存储成本,也可能不适合查询。 比较计划与耗时,包括扫描更便宜的情况,并考虑写入成本。
实现存储却不测试恢复 正常操作测试不覆盖部分写入与重启顺序。 用小型存储练习,在日志与数据持久化位置附近注入故障。