惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 司徒正美
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
MIT News - Artificial intelligence
腾讯CDC
IT之家
IT之家
Microsoft Azure Blog
Microsoft Azure Blog
M
Microsoft Research Blog - Microsoft Research
阮一峰的网络日志
阮一峰的网络日志
H
Help Net Security
L
LangChain Blog
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
U
Unit 42
Recent Announcements
Recent Announcements
S
SegmentFault 最新的问题
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
T
The Blog of Author Tim Ferriss
罗磊的独立博客
宝玉的分享
宝玉的分享
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
雷峰网
雷峰网
D
DataBreaches.Net
爱范儿
爱范儿
Schneier on Security
Schneier on Security
P
Palo Alto Networks Blog
Spread Privacy
Spread Privacy
Hugging Face - Blog
Hugging Face - Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
K
Kaspersky official blog
P
Privacy & Cybersecurity Law Blog
博客园_首页
T
Threat Research - Cisco Blogs
I
InfoQ
有赞技术团队
有赞技术团队
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recorded Future
Recorded Future
量子位
H
Hackread – Cybersecurity News, Data Breaches, AI and More
GbyAI
GbyAI
Cyberwarzone
Cyberwarzone
B
Blog
C
Check Point Blog
P
Proofpoint News Feed
S
Securelist
A
Arctic Wolf

博客园_首页

DeepSeek V4 + Claude Code thinking mode 400 错误修复方案 云原生 CI/CD 平台架构设计 模板方法模式实战:重构Agent工具审批,告别重复代码 Ubuntu修改主机名操作指南 [MAF的Agent管道详解-03]连接LLM的IChatClient对象 《HelloGitHub》第 122 期 AI Agent 到底是做什么的?优势在哪里? 完整学习LLM(六):上下文窗口是什么,为什么模型会忘东西 和AI一起搞事情#6. 如何实现AI生图文字可编辑? 洛谷-P11105 [ROI 2023] 解密 题解 入门:我的第一个Vibe Coding实践程序 【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (2)--- On-Policy Distillation OpenHuman、OpenClaw、Hermes Agent 傻傻分不清楚?一篇说清三者定位 一个前端股票行情 SDK 的开源进化:从周刊收录到 v1.10.0 Claude Code 装了一堆 Skill,用了三个月,我删掉了 80% Claude Code Skill的介绍与使用 AI 漫剧账号运营教程 Hadoop(CDH6、CDP7)在Qwen3.7大模型训练中的作用,(含部署、运行操作步骤) Dify — 创建聊天机器人 -- 知识库 未来十年的数据工程:从 Modern Data Stack 到 Data Engineering Harness Java 泛型解析太痛苦?你可能需要一枚「蛋」 RAG系列:#5 RAG中的11种分块策略 看完《低智商犯罪》,学习Cypher构建知识图谱 临时邮箱的实现原理 记录一下我的 Gradle 开发环境配置过程 使用容器提供postgresql RESTful API服务 在Vue/Nuxt、React/Next/TanstackStart、RazorPages折腾一圈后,还是回到了Blazor,但这回有SSR+HTMX+Alpine的加持 把坏运气关在门外:哈希的随机化之路 agent工作模式之ReAct实战 元数据驱动开发 - 面向对象编程思想的补充 计算机科学/数据科学/人工智能/安全笔记 2026.3 前端包管理咋选?我从npm叛逃到pnpm的血泪史(附避坑指南) 深入 .NET AI Agent 开发:利用 Microsoft.Agents.AI 提取思考、调用工具与执行脚本 vibe coding(二)Where you go:一个微型 windows 桌面覆盖工具 [MAF的Agent管道详解-02]IChatClient管道如何完美连接大模型? [送码] 用 AI Coding 做了一个 App,谈谈 AI Coding 的真实体验 Claude Code 9 大神级 Skills,开发效率直接翻倍(安装、使用场景、踩坑经验) Claude Code 如何压缩上下文:Microcompact、Prompt Cache 与 cache_edits 工程拆解 Docker--Docker引擎与镜像相关命令 AScript定制left/right join查询语法 - rockey627 【学习笔记】《Python编程 从入门到实践》第3章:Python列表完全指南——创建、修改、删除与排序 - lunzi_fly PolyMarket Ghost Fills(幽灵订单)探究 - ACai_sec 面试官:说一下 Agent 的常见范式,如何选型? - 一枫说码 基于ONNXRuntime C#实现的高性能YOLO推理框架 基于 SkiaSharp 的 WPF & AvaloniaUI 极简动图播放方案 贩卖焦虑的时代,我终于接住了真实的焦虑 西安交大最新综述!一文带你读懂大模型智能体及其组网与安全 【Application Insights】采样率对Function App日志收集的影响和解决方法 Excel考勤公式-上班与休息日 完整学习LLM(五):Embedding是什么,为什么文本能变成向量 深度拆解 OpenCoWork:一个本地多智能体桌面平台的架构设计与实现 在影子里验证比较对象:随机指纹和哈希的数学原理 mysql备份恢复详解 HAProxy 学习总结 Mysql事物的持久性及原子性 应用内隐私信息被窥视?防窥保护自动感知一键防护 uni-app 实现视频聊天、屏幕分享,支持Android、HarmonyOS、iOS 做共享目录实时同步,踩过这些坑 华为公司发布半导体演进新范式 - “韬(τ)定律”(Tau Law) Linux时区修改为CST Go 语言入门学习笔记基础版 给热水器装上“电量显示”:用 Shelly Gen4 脚本实现零改装水量预测 踩坑实录:接口正常Feign调用字段值为空 耿同学学术打假,就是学术版《狂人日记》;学术打假,就是清扫垃圾 浙江事业编笔试上周出分!面试进入倒计时,该如何高效冲刺 - 里奥不吃奥利奥 FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示? 我写了 50 个 Claude Code Skill 才发现,前 30 个都白写了 告别 "cd /var/log" !用 journalctl 统一掌控 Linux 日志 我用自己的微信聊天记录,微调了一个“数字分身” AI运动APP开发的常见问题集锦一 复盘梳理-如何深入并抽象 告别手动复制!公众号文章批量导出工具,极致提升内容运营效率 【学习笔记】《Python编程 从入门到实践》第2章:变量命名规则、字符串操作与数值类型详解 Docker--Docker简介及系统架构 别再瞎搞 AI 了!大厂AI业务落地的五个关键环节!(建议新手直接照搬) [MAF的Agent管道详解-01]塑智能体边界,从AIAgent抽象类开始 平台智能化到了分水岭:为什么配置代码化才是 AI Coding 的下一代接口 P.4文本统计工具 高光谱拼接算法(二)Harris 角点探测 - 哥布林学者 Claude Code “悄悄”装了 Python 包?别再让它“投错胎”了 - only赟 影刀 vs 八爪鱼 RPA:到底选哪个?一篇讲透 AI Coding开始进入第四个时代,我还没上车呢! 完整学习LLM(四):Token是什么 【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (1)---基础 LitCTF2026web部分wp CAD子系统,是自研还是外包? 什么是教程地狱?5个信号说明你已经陷入(附3步摆脱方法) polygon出题教程 Manim物理模拟:别自己写欧拉了! AI 学习笔记:Agent 的应用演示 - 凌杰 分享一个CAN报文编辑器软件 洛谷P13016 [GESP202506 六级] 最大因数 MiniCPM-V 4.6 部署实战:基于 GPUStack 与 SGLang 的端侧多模态模型部署 用 FRP 打通云服务器与本地 Ubuntu,让 Codex 远程调试本地硬件 软考 - 架构设计师 知识点总结 给 FastApiAdmin 加个“会议纪要”模块,我把后端二次开发的坑踩了个遍 聊一聊 MES系统如何实现多种标签打印并支持不同打印机 2026第四届LitCTF网络安全挑战赛Pwn的wp 断尺问题:戴德金分割现实悖论 给句子做个“语义审计”:从词向量到句子向量的方法论
[翻译] 为什么我要用 C# 构建数据库引擎
六六的博客 · 2026-05-28 · via 博客园_首页

原文: Why I'm Building a Database Engine in C#

作者: Loïc Baumann

译者: 六六

译注:本文翻译使用AI+人工校对的方式完成

Typhon 是一个用 .NET 编写的嵌入式、持久化、支持 ACID 的数据库引擎,原生支持游戏服务器与实时仿真所熟悉的实体-组件-系统(ECS)范式。

它通过 MVCC 快照隔离提供完整事务安全,并以缓存行感知的存储、零拷贝访问和可配置持久化能力为基础,目标是达到亚微秒级延迟。

这将是一个系列文章:A Database That Thinks Like a Game Engine

  1. Why I'm Building a Database Engine in C#(本文)
  2. What Game Engines Know About Data That Databases Forgot
  3. Microsecond Latency in a Managed Language
  4. Deadlock-Free by Construction
  5. Three Durability Modes, One WAL
  6. Epoch-Based Page Cache (即将发布)

当我告诉别人我正在用 C# 构建一个 ACID 数据库引擎时,第一反应总是如出一辙:“那 GC(垃圾回收)停顿怎么办?”

这是一个合情合理的问题。几乎没有人会在 .NET 中构建高性能数据库引擎。人们普遍认为,这类软件必须使用 C、C++ 或 Rust 编写——托管语言基本上被排除在“微秒级延迟俱乐部”之外。

但在构建了 30 年实时 3D 引擎和系统软件之后,我还是选择了 C#。这个项目名为 Typhon:一个嵌入式 ACID 数据库引擎,目标是实现 1–2 微秒的事务提交。

选择它的原因可能会改变你对 C# 能力的看法。


反对 C# 的理由(让我们直面它吧)

在我阐述理由之前,让我诚实地列出所有反对选择 C# 的理由。这些是现实存在的担忧,而非“稻草人”谬论。

  • GC 是非确定性的:它可以在任何时候暂停你的所有线程。对于一个承诺微秒级延迟的数据库引擎来说,一次 10 毫秒的 Gen2(第 2 代)回收是灾难性的——这超出了你延迟预算的 10,000 倍。
  • 你无法控制内存布局:托管堆决定了对象的存放位置。GC 可以在压缩过程中移动它们。你无法保证 B+ 树节点位于缓存行边界上,也无法保证你的页面缓存缓冲区不会在事务中途被重新定位。
  • JIT(即时编译)预热是真实存在的:对任何方法的第一次调用都要支付编译成本。在数据库引擎中,启动后的第一个事务不应该比稳定状态慢 100 倍。
  • 虚函数分发和边界检查会增加开销:每次数组访问都有隐藏的边界检查。每次接口调用都要经过虚函数表(vtable)。在处理数百万实体的热点循环中,这些纳秒级的开销会不断累积。

这些都是合理的问题。我不会假装它们不存在。但这就是大多数人忽略的一点:现代 C# 对每一个问题都有对应的解决方案。


大多数人不知道的 C# 另一面

很多开发者熟悉的 C# 是类、垃圾回收和 LINQ。但这只是语言的一半。.NET Runtime 团队在过去十年里逐步建立了另一面能力,而这部分看起来和许多人想象中的 C# 很不一样。

unsafe 提供接近 C 级别的控制:原始指针、指针运算、栈上缓冲区的 stackalloc、固定大小数组等。JIT 可以生成与 C 中相同类型的底层指令。

GCHandle.Alloc(Pinned) 可以让 GC 在关键位置变得不再相关。开发者可以固定字节数组,让 GC 不移动它们。Typhon 的整个页缓存都是固定内存;GC 不触碰、不扫描、不移动它。对引擎核心而言,它就是一个固定地址上的原始字节区。

ref struct 可以消除热路径上的堆分配ref struct 不能逃逸到堆,只能存在于栈上,并在作用域结束时消失。Typhon 的实体访问器 EntityRef 是一个 96 字节的 ref struct,目标是零分配、零 GC 压力。

受约束泛型可以提供真正的单态化(Monomorphization)。当代码写成 where T : unmanaged 时,JIT 能为每个类型参数生成单独的原生代码路径。sizeof(T) 会成为常量,无用分支会被消除。这接近 Rust 泛型带来的优化效果:不是运行时分派,而是编译期专门化。

硬件原语(Hardware intrinsics)是一等公民System.Runtime.Intrinsics 提供 Vector256Sse42.Crc32BitOperations.TrailingZeroCount 等能力。它们对应 C/C++ 中可用的 SIMD 指令,并且带有运行时特性检测,可以在不支持相关指令的平台上回退。

[StructLayout(Explicit)] 则可以精确控制内存布局:字段偏移、填充、大小等,你可以控制每一个字节。缓存行对齐、防止伪共享(False-sharing)、位打包(Bit-packing)——这些功能一应俱全。

这不是“C# 试图成为 C”,而是 C# 在其顶级的托管生态系统之上,提供了一个真正的系统级编程层。

Typhon 实际是什么样

硬件加速的 WAL 校验和

写入预写日志(WAL)的每个页面都需要 CRC32C 校验和。这是它在 C# 中的样子——直接按名字调用 CPU 指令:

private static uint ComputePartial(uint crc, ReadOnlySpan<byte> data)
{
    if (Sse42.X64.IsSupported)   return ComputeSse42X64(crc, data);
    if (Sse42.IsSupported)       return ComputeSse42X32(crc, data);
    if (ArmCrc32.Arm64.IsSupported) return ComputeArm64(crc, data);
    return ComputeSoftware(crc, data);
}

private static uint ComputeSse42X64(uint crc, ReadOnlySpan<byte> data)
{
    ulong crc64 = crc;
    ref byte ptr = ref MemoryMarshal.GetReference(data);
    int offset = 0;
    int aligned = data.Length & ~7;

    while (offset < aligned)
    {
        // 编译为单条 x86 crc32 指令
        crc64 = Sse42.X64.Crc32(crc64, Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref ptr, offset)));
        offset += 8;
    }

    uint crc32 = (uint)crc64;
    while (offset < data.Length)
    {
        crc32 = Sse42.Crc32(crc32, Unsafe.Add(ref ptr, offset));
        offset++;
    }
    return crc32;
}

Sse42.X64.Crc32() 会编译成单条 x86 crc32 指令。运行时检测 CPU 能力,JIT 消除无用分支,最后执行的是 C 程序员会写出的同类机器代码,同时还保留了不支持 SSE4.2 时的自动回退。

结果:每 8 KB 页面约 1.3 微秒

SIMD 数据块访问器

Typhon 的页缓存热路径使用一个 16 槽缓存,通过三层路径查找数据。

// === 超快路径:MRU(最近最常使用)检查 ===
var mru = _mruSlot;
if (_pageIndices[mru] == pageIndex)
{
    var headerOffset = pageIndex == 0 ? _rootHeaderOffset : _otherHeaderOffset;
    return (byte*)_baseAddresses[mru] + headerOffset + offset * _stride;
}

// === 快路径:通过 SIMD 搜索所有 16 个缓存插槽 ===
fixed (int* indices = _pageIndices)
{
    var target = Vector256.Create(pageIndex);

    var v0 = Vector256.Load(indices);
    var mask0 = Vector256.Equals(v0, target).ExtractMostSignificantBits();
    if (mask0 != 0)
    {
        var slot = BitOperations.TrailingZeroCount(mask0);
        return GetFromSlot(slot, pageIndex, offset, dirty);
    }

    var v1 = Vector256.Load(indices + 8);
    var mask1 = Vector256.Equals(v1, target).ExtractMostSignificantBits();
    if (mask1 != 0)
    {
        var slot = 8 + BitOperations.TrailingZeroCount(mask1);
        return GetFromSlot(slot, pageIndex, offset, dirty);
    }
}

_pageIndices 是一个 fixed int[16],共 64 字节,刚好一个缓存行,并且以适合 SIMD 的方式排列。一次 Vector256.Equals 可以比较 8 个页索引。MRU 快路径覆盖“重复访问同一页”这个常见场景,对分支预测友好,成本接近于零。

零拷贝实体读取

EntityRef 是一个只存在于栈上的 ref struct,大小为 96 字节,其中包含一个内联固定数组,用来缓存组件位置。

public unsafe ref struct EntityRef
{
    internal readonly EntityId _id;
    // ... 其他元数据
    private fixed int _locations[16];  // 内联组件数据块 ID

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ref readonly T Read<T>(Comp<T> comp) where T : unmanaged
    {
        byte slot = _archetype.GetSlot(comp._componentTypeId);
        int chunkId = _locations[slot];
        var table = _engineState.SlotToComponentTable[slot];
        return ref _tx.ReadEcsComponentData<T>(table, chunkId);
    }
}

这个 Read<T> 调用经历了:方法调用 → 插槽查找 → 数据块 ID → 页面缓存 → 指针算术 → 指向固定内存页面的 ref readonly T。零拷贝,零分配,零 GC 介入。由于泛型约束是 where T : unmanaged,JIT 知道确切布局,最终会编译成指针运算。

JIT 特化的哈希函数

Typhon 的哈希函数也利用了 JIT。由于受约束泛型下的 sizeof(TKey) 是编译期常量,不需要的分支会被消除。

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static uint ComputeHash<TKey>(TKey key) where TKey : unmanaged
{
    if (sizeof(TKey) == 4) return FastHash32(Unsafe.As<TKey, uint>(ref key));
    if (sizeof(TKey) == 8) return XxHash32_8Bytes(Unsafe.As<TKey, long>(ref key));
    return XxHash32_Bytes((byte*)Unsafe.AsPointer(ref key), sizeof(TKey));
}

例如调用 ComputeHash<int>(42) 时,JIT 只会生成 4 字节路径,其余分支会被完全移除。这是真正的单态化(Monomorphization),而非运行时分发(Runtime Dispatch)。

生产力论据

一个数据库引擎不仅仅只有热路径。在核心引擎周围包裹着庞大的基础设施:配置管理、结构化日志、遥测、依赖注入、测试、基准测试。

在 C 或 Rust 中,你需要自己构建其中的大部分,或者缝合质量参差不齐的库。

到了 .NET 里,这些能力已经是生产级且免费的:ILoggerOpenTelemetry 用于可观测性,BenchmarkDotNet 用于严谨的微基准测试,NUnit 用于测试,IConfiguration 用于配置。它们文档充分、互相兼容,并由 Microsoft 或经受过检验的开源社区维护。

对一个独立开发者来说,这是实际的竞争优势。我可以把时间花在并发原语和页缓存淘汰上,而不是重新发明日志框架。

核心在于内存布局,而非语言

这是我多年实时 3D 引擎开发经验教给我的洞察:数据库引擎的瓶颈在于内存访问模式,而非指令吞吐量。

在 Ryzen 7950X 上,一次 DRAM 缓存未命中大约需要 61 到 73 纳秒。这相当于 CPU 空等约 250 个周期。一次命中 L1 的 CAS 操作约 1.4 纳秒,两者比例约为 50:1。

如果你的数据结构导致缓存未命中,无论你的语言有多少“零成本抽象”都救不了你。反之,如果你的数据布局对缓存友好——连续、对齐、访问模式可预测——那么语言几乎无关紧要。带有 unsafe 的 C# 在热点路径上生成的机器码与 C 语言相同。JIT 就是这么强大。

真正重要的是:

  • 缓存行感知: Typhon 的 B+ 树节点是 128 字节——两个缓存行。Zen4 上的步进预取器会自动覆盖第二行。仅此一项就比 64 字节节点减少了 53% 的插入延迟和 30% 的查找延迟。
  • 数据导向设计(DoD): 采用 SOA(结构数组)而非 AOS(数组结构)。SIMD 友好的布局。仅使用可直接复制(Blittable)的类型。
  • 最小化间接引用: 每次指针追踪都是一次潜在的缓存未命中。SIMD 数据块访问器的 MRU 命中完全避免了追踪。

因此,写什么语言远不如设计什么样的内存布局重要。

数据表现

所有测量均在 Ryzen 9 7950X、.NET 10.0、BenchmarkDotNet、Release 配置下进行。

操作 延迟 吞吐
CRUD lifecycle MVCC(spawn、read、update、destroy、commit) 1.2 微秒 830K ops/sec
90 读 / 10 更新负载(每个事务 100 次操作,MVCC) 22 微秒 约 4.5M entity-ops/sec
B+Tree 查找(命中) 267 纳秒 3.7M ops/sec
B+Tree 顺序扫描(每个 key) 2.1 纳秒 479M keys/sec
无竞争锁获取 7.8 纳秒 128M ops/sec
页缓存命中 5.3 纳秒 -

作为参考,Zen4 上一次无竞争 CAS 约 1.4 纳秒;一次 DRAM 往返约 61 到 73 纳秒。Typhon 的锁获取是 7.8 纳秒,大约相当于 5 次 CAS;考虑到它还处理共享/独占仲裁和等待者跟踪,这个结果已经很紧凑。267 纳秒的 B+Tree 查找意味着大约 6 到 7 次内存访问,符合穿过 L2/L3 缓存进行树遍历的预期。

这些仍然是早期 Alpha 版本的数据,还有优化空间。但它们验证了核心论点:C# 不是瓶颈

权衡

任何选择都有成本。以下是我对考虑走同样道路的人的建议。

内存安全要由开发者自己负责。在 unsafe 代码块里,你可以破坏内存、解引用错误指针、写爆缓冲区,编译器不会拯救你。Span<T> 是稍慢但完全安全的替代方案。

GC 目前还不是问题,但它可能成为问题。通过固定页缓存,并在热路径使用 ref struct,Gen2 回收既少又便宜。但这不保证任何场景都如此。如果某个工作负载在事务之间大量分配托管对象,暂停仍然可能出现。解决办法是纪律:不要在热路径上分配。语言允许你分配,但不会强迫你分配。

“但 Rust 会给你编译时安全。”,没错,借用检查器可以捕获 unsafe C# 捕获不了的所有权和生命周期错误。但 C# 也有 Rust 没有的工具:Roslyn analyzers。我编写了一套自定义分析器(TYPHON001–007),将特定领域的安全规则强制转化为编译器错误:

  • [NoCopy] 特效和分析器:像 ChunkAccessor 这样的性能关键结构不能按值传递。如果忘记使用 ref,编译器会报错。它给关键类型提供了类似 Rust move 语义的保证,但范围只覆盖真正需要的类型。
  • 所有权跟踪:如果创建了 ChunkAccessorTransaction 却没有释放,那就是编译错误,而不是运行时泄漏。analyzer 会通过赋值、返回值、ref / out 参数追踪所有权转移,也可以用 [return: TransfersOwnership] 表达返回值所有权转移。
  • 释放完整性:如果类型持有关键 disposable 字段,而 Dispose() 漏掉了它,或者存在提前返回导致字段没有释放,也会成为编译错误。
// 这在 Typhon 中是一个编译错误 —— TYPHON001
void Process(ChunkAccessor accessor) { ... }  // ✗ 错误:必须通过 ref 传递
void Process(ref ChunkAccessor accessor) { ... }  // ✓ OK

也就是说,C# 不会免费得到 Rust 的安全性。但可以构建精确适合领域的一组编译期规则。与 Rust 的借用检查器不同,这些规则在诊断中带有领域上下文:“会导致页面缓存死锁”比“值已在此处移动”更具可操作性。

此外,Rust 在周边基础设施(日志、DI、配置、测试)方面的生态系统不如 .NET 成熟,作为独立开发者,我的开发速度至关重要。我选择了能让我发布更快的语言。

JIT 预热是真问题,但可以管理。冷启动后的前几笔事务会更慢。对嵌入式引擎来说,这是可以接受的,因为宿主应用通常有自己的预热阶段。如果是服务端数据库,则应考虑分层编译或 AOT。

下一步

在下一篇文章中,我将解释为什么 ACID 数据库引擎会从游戏引擎中借鉴存储架构——特别是实体-组件-系统(ECS)模式。游戏引擎和数据库在解决同一个基本问题:管理具有极端性能约束的结构化数据。它们只是演化出了完全不同的解决方案。