
倒排索引把「文档包含哪些词」反转为「一个词出现在哪些文档」,使全文检索不必扫描全部正文。但真正的搜索引擎不只有
term → docID[]:词典负责定位倒排表,posting 携带词频与位置,BM25 把命中文档排出次序,WAND / Block-Max WAND 再利用分数上界跳过不可能进入 Top-K 的候选。本文沿一条查询的数据流,把这些结构串起来。
向量索引里的量化,本质是用更少的比特近似保留「距离 / 内积」结构,从而把内存与带宽从 FP32 量级压下来。PQ、RaBitQ、TurboQuant 只是这条线上常被点名的几个点:前面还有精度截断与标量/二值,旁边还有 OPQ / AQ / RQ / LVQ / ScaNN 等码本与损失设计,后面则是随机旋转 + 无偏估计这一支。量化与 IVF / HNSW 等索引结构正交——结构负责缩候选,量化负责让每个候选上的距离更便宜(结构见
向量索引算法全景 )。
向量索引要解决的不是「怎么算距离」,而是少算多少次距离仍能保住近邻。工程上常见的方案可以按「候选从哪来」分成几族:哈希桶、空间树、聚类倒排、近邻图,以及把图/划分搬到磁盘或加速器上的系统。IVF、HNSW、DiskANN 只是各族里最常被点名的代表,不是清单的尽头。量化(PQ / RaBitQ 等)叠在打分层,另文讨论。
谓词是值为真/假的条件表达式(如
country = 'CN')。谓词下推是一种保持语义等价的改写:把过滤条件尽可能移向数据源头(靠近Scan/ 下推进 Join 下方),使中间结果更早变小。它属于规则改写(RBO)族,通常不依赖表级统计,却能为后面的代价选型省下大量搜索空间。
物理计划把逻辑算子落实成可执行的算法节点:
Scan变成全表或某条索引路径,Join变成 Nested Loop / Hash / Sort-Merge 等。本篇把两件最贵的决策拆开讲——单表怎么读进来,以及 Join 时两股数据流如何对齐——并用逐步示意说明 Nested Loop Join(NLJ)到底在循环什么。
传统查询优化器做的事可以压成一句:在 SQL 执行之前,把「语义等价的若干物理执行方式」缩成一棵计划树,再交给执行器按树跑完。它解决的是声明式 SQL 与命令式执行之间的鸿沟——用户只写「要什么」,优化器负责「怎么算更划算」。所谓「传统」,是相对后来的自适应 / Runtime Filter / 学习型优化而言:计划在编译期一次性定死,执行期默认不再改道。
物理候选枚举出来之后,传统优化器靠两样东西收束:代价模型(给每条候选打一个标量分)和搜索 / 剪枝(在指数级空间里留下估计最优)。分打得准不准,取决于统计与基数估计;搜得完搜不完,取决于表数量与启发式。本篇把「燃料 → 打分 → 搜索 → EXPLAIN 所见」串成一条链。
逻辑计划是一棵用关系代数算子(
Scan / Filter / Project / Join / Aggregate…)写成的树:它固定这条查询的语义——算什么,尚未绑定物理——怎么算(哪个索引、哪种 Join 算法)。它既不是 SQL 原文,也不是最终执行方式;而是解析完成之后、优化器开干之前的那层骨架。

分区表和分布式表都叫「把一张表拆成多份」,但拆的层级完全不同:
- 分区表是单机存储优化——同一张逻辑表,数据按规则分散到多个物理文件,对应用完全透明。
- 分布式表是多机分片策略——同一张逻辑表,数据按规则分散到多个数据库实例,应用必须通过中间层访问。
两者的核心差异在四个维度:数据分布范围(单机 vs 多机)、对应用的透明性(透明 vs 需中间件)、扩展上限(几十分区 vs 数千实例)、运维代价(标准 DBA 操作 vs 分片策略+数据迁移)。前者解决单机 I/O 热点,后者解决单机容量和吞吐上限。边界一句话:是否跨机器。

讨论「STL 容器线程安全」时,若先对齐一条分层链,就不容易把不同层次的问题搅在一起:标准只承诺数据竞争层的底线(多读安全、有写必同步)→ 但加了锁也只消除了竞争,生命周期层的「引用 / 指针 / 迭代器失效」依旧存在 → 而失效与否,最终落在内存模型层:容器是连续存储还是节点式,决定了一次
erase会不会让别人手里的地址作废。三层从上到下是「能不能并发访问」→「访问的地址还在不在」→「为什么在 / 不在」,对齐在哪一层再争,才不会鸡同鸭讲。
「进程 / 线程 / 协程」常被当成三个并列的名词背,但它们其实是同一条演化链上、被不同矛盾逼出来的三层调度单元:多道并存逼出进程(资源定界)→ 进程内要多执行流又省 IPC,逼出线程(同地址空间的调度实体)→ 线程切换进内核、栈又重,逼出协程(用户态自己调度的轻量执行流)。这篇不停在抽象动机,而是落到现实标准回答「它们到底是什么实体」:在 Linux 上,进程和线程都是
task_struct,区别只在clone()时共享了哪些资源;协程则根本不进内核,是运行时在用户态切换的栈 / 状态机。
在成员函数里只有裸指针
this,却需要把当前对象交给异步回调、信号槽或另一个shared_ptr接口时,直接std::shared_ptr<T>(this)会制造二次所有权或未定义行为。标准库与 Boost 提供了一组「从自身取智能指针」的辅助基类。本文对比它们的用途、获取方式与选型,并说明它们与 CRTP(Curiously Recurring Template Pattern) 的关系。
多线程编程中,数据竞争是最隐蔽的 bug 来源。C++11 的
std::atomic提供了原子变量,但真正的复杂性藏在memory_order参数里。本文从 CPU 缓存和编译器优化的视角,解释为什么原子操作需要内存序,以及 acquire/release/relaxed 等序的区别。
InnoDB 是 MySQL 最成熟的存储引擎,覆盖了绝大多数 OLTP 场景。MyRocks 基于 RocksDB(LSM-tree),在 InnoDB 已经足够好用的前提下仍被 Meta、Percona 等公司引入生产,原因只有一个:它在特定负载下能用更少的磁盘 I/O 完成同样的工作。本文从 InnoDB 的结构性开销出发,说明 LSM-tree 如何针对性地缓解这些开销,以及 MyRocks 为此付出的代价。
handler是 MySQL Server 与存储引擎之间的唯一接口层。执行器通过handler的虚函数调用读写数据,存储引擎只需实现这套接口即可接入 Server,而无需感知 SQL 解析与优化的细节。本文梳理handler接口的六个分族,重点分析事务族为何在handler(per-table)与handlerton(per-connection)上混合挂载,以及这种设计选择背后的事务语义约束。