CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 使用Go从零开发一个数据库 说明
  • 1.文件与数据库
  • 2.索引
  • 3.B树:原理
  • 4.B树:实践(第一部分)
  • 5.B树:实践(第二部分)
  • 6.持久化到磁盘
  • 7.空闲列表:重用页面
  • 8.行与列
  • 9.范围查询
  • 10.二级索引
  • 11.原子事务
    • 11.1 键值对事务接口
    • 11.2 数据库事务接口
    • 11.3 实现键值对事务
  • 12.并发读写
  • 13.查询语言:解析器
  • 14.查询语言:执行
目录

11.原子事务

# 11. 原子事务

事务允许以原子性方式执行多个更新操作(要么全部执行,要么全部不执行)。在上一章中,更新一行数据可能会导致多个键值对(KV)更新(由于二级索引的存在),这些更新不具备原子性,如果被中断(不具备抗崩溃能力),可能会导致数据损坏。实现事务可以解决这个问题。

目前,我们只考虑顺序执行,并发问题将在下一章讨论。

# 11.1 键值对事务接口

第一步是添加键值对事务类型。

// 键值对事务
type KVTX struct {
    // 后续补充……
}
1
2
3
4

结束事务有两种方式:

  1. 通过提交事务来持久化更改。
  2. 或者通过中止事务来回滚操作。
// 开始一个事务
func (kv *KV) Begin(tx *KVTX)
// 结束一个事务:提交更新
func (kv *KV) Commit(tx *KVTX) error
// 结束一个事务:回滚
func (kv *KV) Abort(tx *KVTX)
1
2
3
4
5
6

读取和更新键值对存储的方法被移到了事务类型中。请注意,这些方法不再会失败,因为它们不执行输入/输出(IO)操作,IO操作由提交事务时执行,提交操作可能会失败。

// 键值对操作
func (tx *KVTX) Get(key []byte) ([]byte, bool) {
    return tx.db.tree.Get(key)
}
func (tx *KVTX) Seek(key []byte, cmp int) *BIter {
    return tx.db.tree.Seek(key, cmp)
}
func (tx *KVTX) Update(req *InsertReq) bool {
    tx.db.tree.InsertEx(req)
    return req.Added
}
func (tx *KVTX) Del(req *DeleteReq) bool {
    return tx.db.tree.DeleteEx(req)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 11.2 数据库事务接口

类似地,我们也会为数据库(DB)添加事务类型,它是对键值对事务类型的包装。

// 数据库事务
type DBTX struct {
    kv KVTX
    db *DB
}

func (db *DB) Begin(tx *DBTX) {
    tx.db = db
    db.kv.Begin(&tx.kv)
}
func (db *DB) Commit(tx *DBTX) error {
    return db.kv.Commit(&tx.kv)
}
func (db *DB) Abort(tx *DBTX) {
    db.kv.Abort(&tx.kv)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

读取和更新方法也被移到了事务类型中。

func (tx *DBTX) TableNew(tdef *TableDef) error
func (tx *DBTX) Get(table string, rec *Record) (bool, error)
func (tx *DBTX) Set(table string, rec Record, mode int) (bool, error)
func (tx *DBTX) Delete(table string, rec Record) (bool, error)
func (tx *DBTX) Scan(table string, req *Scanner) error
1
2
3
4
5

对数据库代码的修改主要是改变函数的参数,代码清单中省略这些修改内容。

# 11.3 实现键值对事务

事务类型保存了内存数据结构的副本:树的根节点指针和空闲列表头指针。

// 键值对事务
type KVTX struct {
    db *KV
    // 用于回滚
    tree struct {
        root uint64
    }
    free struct {
        head uint64
    }
}
1
2
3
4
5
6
7
8
9
10
11

这用于事务回滚。回滚事务只需将指针指向之前的树的根节点,即使在写入B树数据时出现IO错误,也能轻松完成。

// 开始一个事务
func (kv *KV) Begin(tx *KVTX) {
    tx.db = kv
    tx.tree.root = kv.tree.root
    tx.free.head = kv.free.head
}
1
2
3
4
5
6
// 回滚树和其他内存数据结构。
func rollbackTX(tx *KVTX) {
    kv := tx.db
    kv.tree.root = tx.tree.root
    kv.free.head = tx.free.head
    kv.page.nfree = 0
    kv.page.nappend = 0
    kv.page.updates = map[uint64][]byte{}
}

// 结束一个事务:回滚
func (kv *KV) Abort(tx *KVTX) {
    rollbackTX(tx)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

提交事务与我们之前持久化数据的方式没有太大区别,只是在提交的第一阶段出现错误时需要回滚。

// 结束一个事务:提交更新
func (kv *KV) Commit(tx *KVTX) error {
    if kv.tree.root == tx.tree.root {
        return nil // 没有更新?
    }

    // 阶段1:将页数据持久化到磁盘。
    if err := writePages(kv); err != nil {
        rollbackTX(tx)
        return err
    }

    // 页数据必须在主页面之前到达磁盘。这里使用fsync作为屏障。
    if err := kv.fp.Sync(); err != nil {
        rollbackTX(tx)
        return fmt.Errorf("fsync: %w", err)
    }

    // 此时事务已可见。
    kv.page.flushed += uint64(kv.page.nappend)

    kv.page.nfree = 0
    kv.page.nappend = 0
    kv.page.updates = map[uint64][]byte{}

    // 阶段2:更新主页面以指向新的树。
    // 注意:如果阶段2失败,无法将树回滚到旧版本。
    // 因为无法得知主页面的状态。从旧根节点更新可能会导致数据损坏。
    if err := masterStore(kv); err != nil {
        return err
    }
    if err := kv.fp.Sync(); err != nil {
        return fmt.Errorf("fsync: %w", err)
    }
    return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

本章的改动不多,因为我们省略了一个重要方面——并发,这将在下一章探讨。

上次更新: 2025/04/16, 02:04:00
10.二级索引
12.并发读写

← 10.二级索引 12.并发读写→

最近更新
01
第二章 关键字static及其不同用法
03-27
02
第一章 auto与类型推导
03-27
03
C++语言面试问题集锦 目录与说明
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式