当前位置: 首页 > article >正文

别再死记硬背了!用Go/Python写个玩具DB,亲手实现一遍MVCC

从零构建玩具数据库用Go/Python实战MVCC核心机制为什么我们需要亲手实现MVCC当你第五次在技术面试中被问到MVCC如何解决不可重复读问题却只能背出标准答案时当你在生产环境遇到事务隔离问题却不知如何精准排查时或许该换个学习方式了。理解数据库内核原理最高效的方法不是阅读无数篇解析文章而是亲自动手实现一个简化版本。我在第一次尝试实现存储引擎时才发现原来undo log版本链不是魔法般自动存在的Read View的可见性判断也绝非简单的ID比较。这些在理论文章中一笔带过的概念只有在代码中亲手构建时才会暴露出真正的复杂度。本文将带你用300行左右代码Go/Python版本任选实现一个支持MVCC的内存数据库涵盖以下核心机制事务ID分配与版本管理数据行的多版本存储结构Read View生成与可见性判断不同隔离级别的行为差异1. 搭建基础存储结构1.1 数据行的多版本表示我们首先定义数据行的存储结构。每个行记录需要包含业务数据外还需维护MVCC必需的元信息type Row struct { ID int // 主键 Data string // 业务数据 CreatedTx int // 创建该版本的事务ID ExpiredTx int // 使该版本过期的事务ID Previous *Row // 指向前一版本的指针 }对应的Python版本class Row: def __init__(self, id_, data, created_tx, expired_tx0, previousNone): self.id id_ self.data data self.created_tx created_tx # 创建事务ID self.expired_tx expired_tx # 过期事务ID self.previous previous # 前一版本指针1.2 内存存储引擎框架构建一个简单的内存存储引擎包含基本的CRUD操作type StorageEngine struct { tables map[string]map[int]*Row // 表名 - {主键: 最新行版本} txCounter int // 事务ID计数器 activeTx map[int]bool // 活跃事务集合 } func NewStorageEngine() *StorageEngine { return StorageEngine{ tables: make(map[string]map[int]*Row), activeTx: make(map[int]bool), } }关键设计要点每个表使用主键到最新行版本的映射通过Previous指针形成版本链事务ID全局单调递增2. 实现事务管理系统2.1 事务生命周期管理class Transaction: def __init__(self, tx_id, engine): self.tx_id tx_id self.engine engine self.read_view None def begin(self): self.read_view ReadView(self.tx_id, self.engine.active_transactions()) def commit(self): self.engine.commit_transaction(self.tx_id)事务启动时会创建Read View其核心属性包括属性描述creator_trx_id创建该Read View的事务IDmin_trx_id活跃事务最小IDmax_trx_id系统下一个将分配的事务IDactive_trx_ids创建时所有活跃事务ID集合2.2 Read View可见性判断算法判断某行版本对当前事务是否可见的逻辑func (rv *ReadView) IsVisible(row *Row) bool { if row.CreatedTx rv.minTrxID { return true // 版本在Read View创建前已提交 } if row.CreatedTx rv.maxTrxID { return false // 版本在Read View创建后生成 } if row.CreatedTx rv.creatorTrxID { return true // 自身事务修改可见 } if contains(rv.activeTrxIDs, row.CreatedTx) { return false // 版本由未提交事务创建 } return true // 版本在Read View创建时已提交 }3. MVCC核心操作实现3.1 数据更新流程当执行UPDATE操作时将当前行数据拷贝到undo log即创建新版本修改当前行数据并更新事务ID设置前一版本指针def update_row(self, table, id_, new_data, tx_id): if table not in self.tables: raise Exception(Table not exists) current self.tables[table].get(id_) # 创建新版本 new_version Row(id_, new_data, tx_id, previouscurrent) # 设置旧版本的过期事务ID if current: current.expired_tx tx_id # 更新当前版本 self.tables[table][id_] new_version3.2 数据查询流程查询时需要遍历版本链找到对当前事务可见的最新版本func (e *StorageEngine) QueryRow(table string, id int, tx *Transaction) *Row { current : e.tables[table][id] for current ! nil { if tx.readView.IsVisible(current) { return current } current current.Previous } return nil // 没有可见版本 }4. 隔离级别实现差异4.1 读已提交(RC) vs 可重复读(RR)两种隔离级别的核心区别在于Read View的生成时机特性RC级别RR级别Read View生成每条语句执行前生成新的事务开始时生成且复用不可重复读可能出现避免幻读可能出现快照读避免当前读可能出现实现差异体现在事务查询方法中def query(self, sql, tx): if self.isolation RC: tx.read_view ReadView(tx.tx_id, self.active_transactions()) # RR级别复用事务开始时的read_view return self.execute_query(sql, tx)4.2 幻读现象实验通过以下步骤可以验证RR级别下的幻读现象事务A执行范围查询SELECT * FROM users WHERE age 30事务B插入新记录INSERT INTO users (age) VALUES (35)并提交事务A再次执行相同查询在RR级别下快照读普通SELECT不会看到新插入的行当前读SELECT FOR UPDATE会看到新行并加锁5. 高级特性扩展5.1 垃圾回收机制随着版本链增长需要清理不再需要的旧版本func (e *StorageEngine) GC() { oldestActive : e.getOldestActiveTx() for _, table : range e.tables { for _, row : range table { for row.Previous ! nil row.Previous.ExpiredTx oldestActive { row.Previous row.Previous.Previous // 跳过不可见版本 } } } }5.2 性能优化技巧实际实现中的常见优化手段版本链索引为热点行维护版本索引批量Read View缓存活跃事务列表减少判断开销延迟清理异步执行版本回收class OptimizedRow(Row): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.version_index {} # {tx_id: version_ptr}6. 与真实数据库对比我们实现的玩具数据库与MySQL InnoDB的MVCC实现存在一些差异特性玩具数据库实现InnoDB实现版本存储内存链表undo log段事务ID管理全局计数器混合使用事务ID和回滚指针Read View生成显式控制由隔离级别隐式控制二级索引处理未实现包含主键引用通过这个对比可以理解虽然核心原理相同但生产级数据库需要考虑更多边界条件和优化场景。7. 常见问题解决方案在实现过程中我遇到了几个典型问题及解决方法版本链无限增长引入定期GC机制实现版本跳跃优化长时间事务导致内存膨胀添加事务超时机制实现部分版本加载热点行竞争采用乐观并发控制实现行级锁机制type LockManager struct { rowLocks map[string]map[int]*sync.Mutex } func (lm *LockManager) LockRow(table string, id int) { if _, ok : lm.rowLocks[table]; !ok { lm.rowLocks[table] make(map[int]*sync.Mutex) } lm.rowLocks[table][id].Lock() }8. 进一步学习建议完成基础实现后可以考虑以下扩展方向添加持久化存储支持实现WAL(Write-Ahead Logging)支持B树索引添加SQL解析层推荐测试用例验证你的实现交叉更新测试版本链压力测试隔离级别边界测试并发冲突测试我在GitHub上看到一个有趣的测试方法通过随机事务生成器验证实现的正确性。这能暴露出许多手工测试难以发现的问题。

相关文章:

别再死记硬背了!用Go/Python写个玩具DB,亲手实现一遍MVCC

从零构建玩具数据库:用Go/Python实战MVCC核心机制 为什么我们需要亲手实现MVCC? 当你第五次在技术面试中被问到"MVCC如何解决不可重复读问题"却只能背出标准答案时,当你在生产环境遇到事务隔离问题却不知如何精准排查时&#xff0c…...

别再死记硬背了!用华为eNSP模拟器实战拆解OSPF的5种网络类型(BMA/P2P/P2MP/NBMA)

华为eNSP模拟器实战:OSPF五种网络类型深度解析与避坑指南 刚接触OSPF协议的网络工程师,往往会被BMA、P2P、P2MP、NBMA这些术语搞得晕头转向。教科书上的定义总是抽象难懂,而实际网络环境又千变万化。本文将通过华为eNSP模拟器,带您…...

别再盲目memcpy!嵌入式C中模型权重加载的4种内存对齐误用,已致3起量产固件崩溃

更多请点击: https://intelliparadigm.com 第一章:嵌入式C中模型权重加载的内存对齐本质与危害全景 内存对齐的本质:硬件访问契约 在ARM Cortex-M系列或RISC-V嵌入式平台中,CPU对非对齐地址执行32位读写会触发硬故障&#xff08…...

【嵌入式AI落地黄金公式】:3类芯片(STM32H7/ESP32-C3/NXP RT1170)+4种C内存模型+1套LLM适配框架=工业级边缘智能

更多请点击: https://intelliparadigm.com 第一章:嵌入式AI落地黄金公式的整体架构解析 嵌入式AI的规模化落地并非单纯依赖模型压缩或硬件加速,而是一个融合算法、系统、工具链与场景闭环的协同工程。其“黄金公式”可抽象为:**精…...

CUDA 13.2新特性深度压测:为何92%的AI团队在启用Graph Capture后仍多花31%显存开销?

更多请点击: https://intelliparadigm.com 第一章:CUDA 13 编程与 AI 算子优化 成本控制策略 CUDA 13 引入了更精细的 GPU 资源调度机制与统一内存管理增强,为 AI 算子在训练/推理阶段的显存占用、带宽消耗和功耗成本提供了可量化的调控入口…...

C++26反射能否取代宏+CodeGen?实测37个工业级项目重构案例:平均节省21,400行胶水代码,但调试体验倒退2.8代——你敢上吗?

更多请点击: https://intelliparadigm.com 第一章:C26反射特性在元编程中的应用对比评测报告 C26 正式引入基于 std::reflexpr 的静态反射核心机制,标志着元编程从模板繁重范式迈向声明式、可读性优先的新阶段。相比 C20 的 constexpr 元编程…...

闲鱼数据猎手:自动化采集系统的智能进化之路

闲鱼数据猎手:自动化采集系统的智能进化之路 【免费下载链接】idlefish_xianyu_spider-crawler-sender 闲鱼自动抓取/筛选/发送系统,xianyu spider crawler blablabla 项目地址: https://gitcode.com/gh_mirrors/id/idlefish_xianyu_spider-crawler-se…...

英雄联盟客户端个性化定制:5分钟打造你的专属游戏界面

英雄联盟客户端个性化定制:5分钟打造你的专属游戏界面 【免费下载链接】LeaguePrank 项目地址: https://gitcode.com/gh_mirrors/le/LeaguePrank 还在为英雄联盟客户端千篇一律的界面感到乏味吗?想让你的游戏资料页和在线状态展现独特个性吗&…...

VSCode连接WSL2写C++代码,这几个调试和编译的‘骚操作’让你效率翻倍

VSCode连接WSL2写C代码的五个高阶技巧 在Windows系统下使用WSL2进行C开发已经成为越来越多程序员的选择。这种开发方式既保留了Windows系统的易用性,又能够充分利用Linux环境下的强大工具链。但仅仅完成基础配置还远远不够,真正的高效开发需要掌握一些进…...

3步解决魔兽争霸3兼容性问题:终极优化指南

3步解决魔兽争霸3兼容性问题:终极优化指南 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 魔兽争霸III作为经典RTS游戏,在现代…...

从Metasploitable2靶场实战:一次完整的Telnet漏洞利用、提权与加固复盘

Metasploitable2靶场实战:Telnet漏洞攻防全流程拆解与加固指南 在网络安全领域,Telnet协议就像一位年迈的守门人——它诞生于互联网的黎明时期,却因设计缺陷成为攻击者最爱的突破口。Metasploitable2靶机作为经典的渗透测试实验环境&#xff…...

零基础玩转Qwen3语义雷达:手把手教你构建自定义知识库

零基础玩转Qwen3语义雷达:手把手教你构建自定义知识库 1. 从关键词到语义:为什么你需要一个“懂你”的搜索工具? 想象一下这个场景:你正在整理一份关于“健康饮食”的文档库,里面包含了“苹果富含维生素”、“香蕉能…...

别再自己造轮子了!用Boost.Geometry库5分钟搞定SLAM中的几何计算(附避坑指南)

用Boost.Geometry库5分钟搞定SLAM中的几何计算(附避坑指南) 在SLAM和机器人开发中,几何计算无处不在——从点云边界框的碰撞检测到地图多边形的区域划分,开发者常常需要处理点、线、面之间的空间关系。传统做法是手动实现这些算法…...

Python基础之常用库常用方法整理

一、os12345678__file__ 获取当前运行的.py文件所在的路径(D:\PycharmProjects\My_WEB_UI\ConfigFiles\ConfigPath.py)os.path.dirname(__file__)上面正在运行的.py文件的上一级(D:\PycharmProjects\My_WEB_UI\ConfigFiles)os.path.join(xxx,uConfi…...

告别浏览器控制台:手把手教你用Node.js在命令行里直接运行JavaScript代码

从浏览器到终端:Node.js命令行交互完全指南 当你在浏览器控制台里反复调试一段JavaScript代码时,有没有想过其实可以完全脱离浏览器环境?想象一下这样的场景:你正在开发一个需要处理本地文件的脚本,或者需要快速验证某…...

nli-MiniLM2-L6-H768作品分享:高校科研项目申报书→‘人工智能,生物医药,新材料’领域识别

nli-MiniLM2-L6-H768作品分享:高校科研项目申报书→人工智能,生物医药,新材料领域识别 1. 项目背景与价值 在高校科研管理工作中,每年需要处理大量项目申报书。传统的人工分类方式效率低下,且容易因主观判断产生误差。本项目基于cross-enco…...

PIM与CXL-PIM架构对比:性能优化与应用场景

1. PIM与CXL-PIM架构深度解析:从理论到实践近内存计算(Processing-in-Memory, PIM)正在重塑现代计算架构的格局。作为一名长期跟踪内存计算技术发展的从业者,我见证了这项技术从学术论文走向商业产品的全过程。本文将基于最新研究…...

为什么 Agent 还要分成多个?多 Agent 到底在解决什么问题

为什么 Agent 还要分成多个?多 Agent 到底在解决什么问题前面我们已经顺着一条很清晰的线往下走:先讲 Agent 为什么会跑偏,再讲怎么下任务、怎么做规划、怎么管理状态、怎么评估和调试;接着又进入框架层,讲了 LangChai…...

免费NHSE存档编辑器:快速打造完美动物森友会岛屿的终极指南 [特殊字符]️

免费NHSE存档编辑器:快速打造完美动物森友会岛屿的终极指南 🏝️ 【免费下载链接】NHSE Animal Crossing: New Horizons save editor 项目地址: https://gitcode.com/gh_mirrors/nh/NHSE 你是否曾为《集合啦!动物森友会》中的稀有物品…...

LangChain 到底是什么?为什么一讲 Agent 就会先提它

LangChain 到底是什么?为什么很多人一讲 Agent,就会先提它前面我们已经连续讲了 Agent 为什么会跑偏、怎么下任务更稳、为什么需要规划、记忆、评估和调试。讲到这里,很多人就会自然进入下一个问题:如果我要真的开始搭一个 Agent&…...

技术评估中的成果检验与价值判断

技术评估中的成果检验与价值判断 在科技快速发展的今天,技术评估成为衡量创新成果的重要工具。无论是科研项目、企业研发还是政策制定,成果检验与价值判断都直接影响资源的分配与决策的方向。如何科学、客观地评估技术的实际效果与社会价值,…...

AEA框架实战:构建自主经济智能体,实现去中心化交易与协作

1. 项目概述:当智能体学会“自主”交易与协作 如果你关注过AI与区块链、去中心化金融的交汇点,那么“智能体”这个词一定不陌生。但大多数时候,我们谈论的智能体,更像是一个个孤立的、执行预设脚本的机器人。今天要聊的这个项目—…...

PyTorch光流实战:从双向光流、遮挡掩码到一致性检查的完整流程解析

1. 光流基础与PyTorch环境搭建 光流估计是计算机视觉中的经典问题,简单来说就是计算视频中相邻两帧之间每个像素的运动矢量。想象一下你在看一群蚂蚁搬家,光流就是用来量化每只蚂蚁从上一帧到当前帧移动了多少距离和方向的技术。在PyTorch中实现光流处理…...

CAN总线数据抓包逆向分析:用can-utils和Wireshark破解汽车ECU通信协议

CAN总线数据逆向实战:从抓包到协议解析的全链路拆解 在汽车电子和工业控制领域,CAN总线如同神经脉络般连接着各种电子控制单元(ECU)。当我们需要诊断车辆故障、开发后装设备或进行安全研究时,逆向分析CAN协议就成为必备…...

中国土地利用数据CLCD(1985-2023年)

01、数据介绍CLCD_classificationsystem是专门为CLCD数据集设计的分类系统,它基于遥感图像处理技术和地理信息系统(GIS)的应用,将中国地区的土地覆盖划分为多个类别,并通过色彩编码进行区分。该系统旨在为用户提供清晰…...

golang如何实现API压测工具_golang API压测工具实现攻略

用 net/http 并发请求时须自定义 http.Client:设 Timeout(如10s)、MaxIdleConns 与 MaxIdleConnsPerHost(建议≥2000)、调整 IdleConnTimeout;并发控制用 sync.WaitGroup channel,避免默认配置…...

FLUX.1-Krea-Extracted-LoRA实操手册:Streamlit前端CSS美化与交互优化

FLUX.1-Krea-Extracted-LoRA实操手册:Streamlit前端CSS美化与交互优化 1. 模型概述与快速部署 FLUX.1-Krea-Extracted-LoRA 是一款基于 FLUX.1-dev 基础模型的风格迁移工具,通过提取的 LoRA 权重为生成的图像注入专业摄影级别的真实感。相比普通AI生成…...

STM32F103实战:用TCA9548A扩展I2C接口,轻松连接8个相同地址的传感器

STM32F103实战:用TCA9548A扩展I2C接口,轻松连接8个相同地址的传感器 在嵌入式开发中,I2C总线因其简单的两线制接口和灵活的寻址方式而广受欢迎。然而,当我们需要连接多个相同型号的传感器时,I2C地址冲突就成为一个棘手…...

原神帧率解锁完全指南:如何安全突破60FPS限制,畅享高刷新率游戏体验

原神帧率解锁完全指南:如何安全突破60FPS限制,畅享高刷新率游戏体验 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 对于追求极致流畅游戏体验的《原神》PC玩家来…...

终极指南:如何快速实现多平台直播弹幕数据采集

终极指南:如何快速实现多平台直播弹幕数据采集 【免费下载链接】BarrageGrab 抖音快手bilibili直播弹幕wss直连,非系统代理方式,无需多开浏览器窗口 项目地址: https://gitcode.com/gh_mirrors/ba/BarrageGrab 想要实时获取抖音、快手…...