压缩、向量聚合与 ProxySQL 读写分离

共 24 题
📑 题目列表 24 题
#
★★★

1. 位图编码(Bitpacking)对低基数列的压缩

位图编码(Bitpacking)如何对低基数列进行压缩?

  • Bitpacking 的原理(按最小位数打包)
  • 低基数列的压缩效果
  • 与字典编码配合

Bitpacking(位打包)把整数按"实际所需的最小位数"紧凑存储,而非固定 32/64 位。原理:先确定列中值的最大位宽(如值 0~255 需要 8 位,0~7 需要 3 位),然后把每个值按该位宽连续打包,去掉高位零,减少存储位数。对低基数列(取值少、值域小),所需位数少,压缩效果好——例如某列只有 0~7 八个值,用 3 位打包比 32 位存节省 8 倍以上。Bitpacking 常与字典编码配合:先把列值映射为字典 ID(ID 是 0..n 的小整数),再对 ID 做 Bitpacking,进一步压缩。Bitpacking 的优点是解码效率高(可按 SIMD 批量解包)、无损,适合"整数 + 低位宽"的列。缺点是若列值域跨度大(有少量大值),最大位宽高,压缩效果差,此时可配合"前移溢出"(如 Frame-of-Reference,把值减去最小值)或分块处理。

Bitpacking 的核心是"按最大值确定位宽,去掉无用高位"。低基数映射为小整数 ID 后位宽小,压缩率高;配合字典或参照系(减去最小值)可进一步压缩。

#
★★★

2. 向量化 Hash 聚合的批量探测

向量化 Hash 聚合的批量探测如何实现?

  • 分组聚合的哈希表
  • 批量探测/插入
  • SIMD 与优化

向量化 Hash 聚合的批量探测:对分组列(group by key)的整批数据,批量计算哈希、批量探测哈希表、批量累加聚合值。流程:1)对一批 key 批量计算哈希(SIMD 哈希);2)对每个 key 探测哈希表,找到对应组桶;3)批量把聚合值累加到对应桶(SIMD 累加 sum、位图加 count、min/max 归约);4)对于新 key(未出现),插入新组。批量处理消除逐行调用,提升缓存局部性(同一批 key 的桶往往连续)。优化:用 radix 哈希(按哈希高位分区)让桶组缓存友好;用 SIMD 对多个 key 并行探测与累加;对 key 相同的行做"预聚合"减少桶访问。批量探测的核心是"一次处理一批 key 的哈希与桶访问",再利用 SIMD 加速累加,显著提升分组聚合吞吐。

批量探测的本质是"把逐 key 的哈希探测变成批量的哈希 + 桶访问 + SIMD 累加"。配合 radix 分区缓存优化,是向量化分组聚合的关键。

#
★★★

3. 向量化 Hash Join 的构建/探测批处理

向量化 Hash Join 的构建/探测批处理如何实现?

  • 构建阶段(build)批量建哈希表
  • 探测阶段(probe)批量匹配
  • SIMD 与缓存优化

向量化 Hash Join 分构建与探测两阶段,均按批处理。构建阶段(build):对内表(一般选较小表)按 join key 批量计算哈希、批量插入哈希表,把 key 与需要的列存入哈希表槽位。探测阶段(probe):对外表按批取 join key,批量计算哈希、批量在哈希表中探测(匹配),命中则输出连接结果。批处理的核心:1)对内表建表时的批量插入与对 probe 批的批量探测,避免逐行调用;2)用 SIMD 加速哈希计算与槽位比较(一次比较多个 key);3)radix 分区:按哈希值高位把数据分区,使每个分区/桶能放入 CPU 缓存,探测时缓存命中率高,减少随机内存访问;4)对探测结果批量生成匹配对(用位图/ID 记录)。构建/探测批处理把"哈希 + 比较 + 输出"批量向量化,是海量数据 join 高性能的关键。

构建/探测批处理的核心是"批量哈希 + 批量探测 + SIMD 比较 + radix 缓存友好"。它让 join 从逐行随机访问变为批量缓存友好的处理。

#
★★★

4. 向量化排序(基数排序)的应用

向量化排序中基数排序(radix sort)如何应用?

  • 基数排序的原理
  • 向量化实现
  • 与比较排序的权衡

基数排序(radix sort)按整数 key 的每一位(或每几位)分桶(计数排序),从低位到高位逐趟排序,复杂度 O(n·k)。相比比较排序(如快排 O(nlogn)),基数排序"无比较",且可向量化:1)对每趟,统计各桶计数(SIMD 计数)、计算前缀和、把元素按桶写入(分散);2)多趟处理多个位,利用 SIMD 对整批元素的位提取与计数。基数排序适用于"整型 key 的排序"(如 ORDER BY 整数列、按分桶 key 排序),且可利用 SIMD 与缓存(桶数量可控)实现极快排序。向量化执行中,基数排序用于:排序算子的整型快速排序、聚合/join 的 key 排序、以及分桶/topN。权衡:基数排序对整数快、确定性强,但对字符串/浮点需转换 key;数据量大时桶计数与分散的开销需注意。现代分析引擎(如 DuckDB、ClickHouse)对整型排序常用基数排序配合 SIMD 提升吞吐。

基数排序是"无比较 + 可按位分桶",天然适合 SIMD 与向量化。它把排序从"比较论"变成"分桶论",对整型 key 排序极快,是向量化排序的重要工具。

#
★★

5. 向量化引擎的算子融合(operator fusion)

向量化引擎的算子融合(operator fusion)是什么?有何好处?

  • 算子融合的概念
  • 减少中间数据物化
  • 提升缓存与执行效率

算子融合(operator fusion)指把多个相邻算子(如 filter、project、某些计算)合并为一个执行算子,在一次数据遍历中完成多个操作,避免把中间结果物化到内存/缓存。例如把 scan → filter → project 融合为一个算子,逐批处理时先过滤再投影,输出结果直接进入下一算子,减少中间列向量的创建与内存往返。好处:1)减少中间数据物化(少建中间向量、少写内存),降低内存与内存带宽开销;2)提升缓存局部性(数据在流水线内流动,不必写回再读);3)减少算子间调度与函数调用开销;4)利于寄存器与 SIMD 利用(融合后的循环内连续运算)。向量化执行中,融合通过"数据驱动"(pull 或 push 模型)把算子流水线化,让数据在融合组内连续处理。独立性强的算子(如纯计算、过滤)适合融合,而 join、agg 等有状态/需分组的算子较难融合。算子融合是列式/向量化引擎(如 Velox、DuckDB)减少中间开销的关键技术。

算子融合的本质是"把多个算子合并为一次遍历,减少中间物化"。它降低内存往返与调度开销,提升缓存与 SIMD 利用,是流水线执行的核心。

#
★★

6. 向量化执行中的分支预测优化

向量化执行中如何做分支预测优化?

  • 分支预测的原理与代价
  • 消除分支(用掩码/位运算)
  • 向量化中的分支处理

现代 CPU 依赖分支预测器提高流水线效率,但分支预测失败(mispredict)会清空流水线,代价高昂。向量化执行中,数据处理常含大量条件判断(如过滤、比较、null 检查),若逐行分支,会频繁触发分支预测失败。优化手段:1)将分支转为"数据驱动"运算——用掩码(mask)与位运算(SIMD 比较生成掩码,用掩码做选择/过滤)代替逐行 if,避免分支预测;2)用 SIMD 的"比较→掩码→select"模式,对整批数据统一处理,即使个别元素不满足也统一计算,再按掩码选结果,消除分支;3)重组代码使分支更可预测(把常见路径提前);4)对某些条件用"谓词执行"(predicate execution)而非分支。关键是"用数据流替代控制流":把 if 分支变成掩码运算,降低分支预测失败率,提升吞吐。这是向量化引擎利用 SIMD 时不依赖分支预测的关键。

分支预测优化的核心是"用掩码/位运算替代分支"(数据驱动而非控制驱动)。它让 SIMD 批量处理不依赖分支预测,减少流水线清空,提升效率。

#
★★

7. ProxySQL 的读写分离与主从延迟感知

ProxySQL 如何实现读写分离与主从延迟感知?

  • 读写分离的规则路由
  • 主从延迟感知
  • 延迟配置与策略

ProxySQL 通过"查询规则(query rules)+ 后端主机组(hostgroup)"实现读写分离:把写请求(INSERT/UPDATE/DELETE)路由到主库主机组,把读请求(SELECT)路由到从库主机组。实现:1)在 mysql_query_rules 中定义规则,按 SQL 类型(如正则匹配 ^SELECT)、用户、查询分类路由到指定 hostgroup;2)写库与读库分别配置 hostgroup,从库可多台并配置权重。主从延迟感知:ProxySQL 定期检查从库的复制延迟(通过 SHOW SLAVE STATUS/SHOW REPLICA STATUS 的 Seconds_Behind_Master),若延迟超过阈值(如 5 秒),则把该从库暂时从读路由中摘除(或标记为不可用),避免读到过期数据;延迟恢复后重新加入。可配置 mysql_replication_hostgroup 与延迟阈值,实现"读流量只在延迟可接受的从库上分发"。此外可配合"读一致性"策略(如对强一致读走主库、对可容忍延迟读走从库)。

ProxySQL 读写分离的核心是"规则路由 + 延迟感知摘除"。主从延迟感知让读路由只在延迟可接受的从库上分发,避免读到陈旧数据,是读写分离的关键保障。

-- 定义读写分离规则
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (1, 1, '^SELECT', 2, 1),   -- SELECT 路由到从库 hostgroup 2
       (2, 1, '^INSERT|^UPDATE|^DELETE', 1, 1); -- 写路由到主库 hostgroup 1
-- 配置主从 hostgroup 与延迟阈值
INSERT INTO mysql_replication_hostgroups (writer_hostgroup, reader_hostgroup, check_type, check_interval)
VALUES (1, 2, 'READ_ONLY', 5000);
#
★★

8. ProxySQL 在分库分表前的流量治理

ProxySQL 在分库分表前的流量治理有什么作用?

  • 代理层的流量治理能力
  • 在分库分表前的角色
  • 治理项(限流、路由、审计)

在分库分表(Sharding)方案落地前,ProxySQL 可作为流量治理层,承担:1)读写分离:把读流量分发到从库,减轻主库压力,为分库分表前"降读压力"做准备;2)连接管理与连接池:聚合客户端连接,复用后端连接,防止连接数过多压垮数据库;3)限流与熔断:对突发/异常流量限流,保护后端数据库;4)查询路由与规则:按 SQL/库/用户路由到不同后端,便于后端水平扩展(即使尚未分库分表,也可把不同库路由到不同实例);5)审计与监控:记录查询、慢查询、会话,便于发现热点与瓶颈;6)灰度与切换:在后端实例间切换(如迁移、扩缩容)时平滑引流。ProxySQL 在"分库分表前"的价值是"先做流量治理与隔离,为后续拆分做准备"——它提供了连接、路由、限流、监控的统一入口,让数据库架构演进更平滑。

ProxySQL 在分库分表前的角色是"统一流量治理入口"。它先落地连接管理、读写分离、限流、路由与监控,为后续分库分表打下基础,避免直接大拆导致的复杂度爆炸。

#
★★

9. 轻量压缩(Lightweight Compression)在内存中的解码

轻量压缩(Lightweight Compression)在内存中的解码如何实现?

  • 轻量压缩的概念(连内存数据也压缩)
  • 解码的高效性
  • 向量化解码

轻量压缩(Lightweight Compression)指对内存中的数据也进行压缩(如 SIMD 友好的位打包、字典、RLE),以节省内存带宽、减少内存占用,同时在需要时快速解码。它通常用于"数据仍驻留内存,但压缩存储、按需解码"的场景,如列存储中常驻内存的列、缓存、以及分析引擎的批量数据。实现要点:1)编码选择 SIMD 友好的格式(如批量位打包、固定宽度的字典),使解码可用 SIMD 批量展开;2)解码时按"块"(cacheline 对齐)批量解压缩,利用 SIMD 指令(如一次解 8/16 个整数)快速还原;3)解码结果直接落地为连续列向量,供后续向量化计算;4)对无压缩收益的数据跳过压缩。轻量压缩的核心是"解码开销要远小于 IO/带宽节省"——用 SIMD 解码把压缩/解压的 CPU 成本降到很低,使内存带宽成为受益者。它常与列存、向量化引擎结合,在"内存计算的输入"阶段做压缩与即时解码。

轻量压缩的本质是"用少量的 SIMD 解码成本换取内存带宽/容量的节省"。关键是解码必须 SIMD 化、按块批量,使解码开销可忽略,从而在内存场景用高压缩换高带宽。

#
★★

10. ProxySQL 的查询路由规则(mysql_query_rules)

ProxySQL 的查询路由规则(mysql_query_rules)如何工作?

  • 规则表的字段与匹配
  • 规则优先级与 apply
  • 路由到 hostgroup

ProxySQL 的查询路由由 mysql_query_rules 表驱动,每条规则包含:rule_id(优先级,数字越小越先匹配)、match_pattern(正则匹配 SQL 文本)、match_digest(匹配查询摘要)、match_user(匹配用户)、destination_hostgroup(路由目标主机组)、apply(是否应用此规则后终止后续规则)、以及 schemas(库名)等。工作流程:客户端查询到达后,ProxySQL 按 rule_id 从小到大依次匹配(先 match_user/match_digest/match_pattern),命中则执行动作(如路由到指定 hostgroup、连接、重写、缓存、镜像),若 apply=1 则停止后续规则匹配。规则可实现:读写分离(按 SQL 类型)、库/表路由、用户隔离、慢查询重路由、查询重写、限流(按用户/频率)。规则是动态可配置的(经管理接口 LOAD MYSQL QUERY RULES TO RUNTIME),实现热更新。设计要点:合理设置 rule_id 顺序与正则,避免规则冲突与性能开销(过多正则匹配影响吞吐)。

查询路由规则的核心是"按优先级匹配、命中后路由到目标 hostgroup"。它是 ProxySQL 实现读写分离、路由、治理的核心机制,支持动态热加载。

#
★★

11. ProxySQL 的连接多路复用(multiplexing)

ProxySQL 的连接多路复用(multiplexing)如何工作?

  • 连接复用的概念
  • 与后端连接共享
  • 事务/会话的约束

ProxySQL 的连接多路复用(multiplexing)指"多个客户端连接共享少量后端连接":客户端连接 ProxySQL,ProxySQL 把请求在多条后端连接间复用(同一后端连接可服务多个客户端的不同请求),从而大幅减少到达数据库的后端连接数。实现:客户端连接持久化到 ProxySQL,ProxySQL 维护一个后端连接池,把请求按需分配到可用后端连接;请求完成后,后端连接回到池中供其他客户端复用。收益:降低数据库的连接数(避免连接数耗尽)、减少连接建立开销、提升 cohort 复用。约束:多路复用受"会话/事务状态"限制——若请求处于事务中、有会话变量(如 SET @var)、临时表或锁,则不能与其他客户端复用同一后端连接(会冲突),ProxySQL 会为此类请求独占一条后端连接,待事务/会话结束再归还。因此多路复用适合"无状态、短查询、无事务状态"的流量,对需保持会话状态的请求需关闭复用。

多路复用的核心是"前端连接与后端连接解耦",用少量后端连接服务大量客户端。但事务/会话状态会阻止复用(需独占连接),这是多路复用的关键约束。

#
★★

12. ProxySQL 的查询重写(rewrite)能力

ProxySQL 的查询重写(rewrite)能力如何实现?

  • 查询重写的场景
  • replace_pattern 与指令
  • 重写与路由结合

ProxySQL 的查询重写通过 mysql_query_rules 中的 replace_pattern 实现:对匹配的 SQL,用正则替换(replace_pattern 作为替换串)改写 SQL 后再发送给后端。重写场景:1)库名/表名替换(如把 app_db 改写为 app_db_2024,用于迁移/分区);2)SQL 规范化(强制加 hint、改写 join);3)脱敏/转换(改写特定查询);4)把某些查询重定向(结合路由)。实现:match_pattern 匹配源 SQL,replace_pattern 是替换模板(支持捕获组 \1 等),重写后 SQL 继续按规则处理(可再路由到目标 hostgroup)。查询重写与路由结合:重写常在路由前/后执行,实现"改写 + 路由"组合。设计要点:重写正则要精确、避免误改(错误的 replace 会破坏查询),重写会改变 SQL 语义需谨慎,且重写对消化整段 SQL 的长度有要求。重写能力让 ProxySQL 能在不改应用的情况下调整后端查询。

查询重写的核心是"正则匹配 + 替换改写 SQL"。它与路由结合,实现不改应用即可调整查询(迁移、规范、路由),但需谨慎避免误改语义。

#
★★

13. ProxySQL 的管理接口与运行时配置

ProxySQL 的管理接口与运行时配置如何工作?

  • 管理接口(admin)与库
  • 运行时配置(RUNTIME/SAVE/LOAD)
  • 热更新与持久化

ProxySQL 有管理接口(admin interface,默认端口 6032),通过 SQL 语法管理配置。配置分三层:memory(内存中当前配置)、runtime(当前生效配置)、disk(持久化配置)。变更流程:1)修改内存表(如 INSERT INTO mysql_users ...);2)LOAD ... TO RUNTIME 把内存配置加载到 runtime 生效;3)SAVE ... TO DISK 持久化到磁盘(重启后保留)。管理接口可配置:mysql_users(用户与后端映射)、mysql_servers(后端主机)、mysql_query_rules(查询规则)、mysql_replication_hostgroups(复制组)、全局变量(admin-variables、mysql-variables)等。运行时配置实现热更新:无需重启 ProxySQL 即可调整路由、增删后端、改限流。管理接口还支持监控(stats 表)、连接管理(kill 连接)、查看状态。设计要点:管理接口与客户端接口分离(6032 vs 6033),管理接口需鉴权;配置变更要按"memory → runtime → disk"顺序规范性操作,避免丢失。

管理接口的核心是"SQL 式配置 + 三级存储(memory/runtime/disk)+ 热更新"。它让 ProxySQL 配置可动态、可热加载、可持久化,是运维灵活性的关键。

#
★★

14. 差分编码对时序/自增列的效果

差分编码(Delta Encoding)对时序/自增列有什么压缩效果?

  • 差分编码原理
  • 时序/自增列的特征
  • 压缩效果与变体

差分编码(Delta Encoding)存储"当前值与前一值的差值"而非原始值。时序/自增列(如自增 ID、时间戳、单调递增的计数)相邻值差很小(如差值 1 或很少变化),因此差分后数值很小,可用更少位数表示(配合 Bitpacking/Varint),压缩率极高。例如时间戳序列 1700000000, 1700000001, 1700000002... 存差值 1,1,1... 后几乎全为 1,可极紧凑。变体:1)Delta-of-Delta(二阶差分):对差值再差分(时间戳序列尤其有效,因为差值本身接近恒定);2)Frame-of-Reference(参照系):先减去块内最小值,再打包。差分编码对"有序、近似等距"的时序/自增列效果极佳;对"随机、无规律"的列,差值可能很大,压缩效果差,甚至可能增大存储。因此差分编码适合有相对稳定变化趋势的列,常与位打包、Varint 结合,并且解码 SIMD 友好(可批量累加还原)。

差分编码的核心是"把大值变成小差值"。时序/自增列相邻差小,差分后位宽骤降,压缩率极高;对随机列则不适,需结合数据特征选择。

#
★★

15. 字典编码的全局/局部字典选择

字典编码的全局字典与局部字典如何选择?

  • 全局字典 vs 局部字典
  • 构建与查询权衡
  • 适用场景

字典编码把列值映射为 ID。全局字典(global dictionary):在整个表/分区级维护一个统一字典,所有数据块共享;优点:ID 全局唯一,可跨块做等值判断/join 优化、可直接对字典值做谓词下推,空间统一;缺点:字典构建开销大、需维护、对频繁更新的列成本高,且高基数时字典可压缩性下降。局部字典(local/block dictionary):每个数据块(row group/page)独立维护字典;优点:构建简单、适配局部数据特征、避免全局字典维护成本、高基数下每块字典更紧凑;缺点:块间 ID 不统一,无法跨块做字典级优化,块间需比较原始值。选择:低基数、查询频繁做等值过滤、列稳定 → 用全局字典(利于谓词/join 优化);高基数、数据更新频繁、需局部自适应 → 用局部字典。Parquet 等格式常采用"先局部字典,若块内基数过高则回退 plain"的策略。二者也可结合(全局索引 + 局部字典)。

全局/局部字典的核心权衡是"跨块优化能力 vs 构建维护成本"。全局字典利于谓词与 join 优化但维护贵,局部字典简单自适应但缺跨块优化,按基数与更新频率选择。

#
★★

16. 压缩与查询性能(解压代价)的权衡

压缩与查询性能(解压代价)之间存在什么权衡?

  • 压缩率 vs 解压速度
  • 解压成为瓶颈的风险
  • 分层与按需解压

压缩与查询性能的权衡核心是"压缩节省的 IO/带宽 vs 解压消耗的 CPU"。高压缩率(如 Zstd 最高级)减少存储与 IO,但解压慢、CPU 高,可能让解压成为瓶颈;低压缩率(如 LZ4 快速)解压快、CPU 低,但节省的 IO 少。对 IO 密集场景,压缩的收益(省 IO)大于解压成本,应选高压缩或较高压缩;对 CPU 密集/内存带宽敏感场景,解压成本可能反超收益,应选快速压缩。设计权衡:1)分层压缩算法(如 LZ4 用于热数据、Zstd 用于冷数据/归档);2)按需解压——只解压满足谓词的块、只解压所需列,减少解压量;3)SIMD 解压降低 CPU 成本;4)压缩级别可调(找平衡点)。实践中,用"压缩率与解压吞吐"的测量(如 Zstd 的 level 3 是常用平衡点)来选择。关键是"压缩是手段,不是目的"——最终目标是让"总 IO + 解压 CPU"最小化。

权衡的本质是"省 IO 还是省 CPU"。高压缩省 IO 但解压贵,低压缩反置;通过分层算法、按需解压、SIMD 解压与压缩级别调节,使总成本最小。

#
★★

17. 列式压缩在对象存储上的传输优化

列式压缩在对象存储上的传输优化如何实现?

  • 对象存储的上传/下载优化
  • 压缩与 Range 读取
  • 数据本地性与并行

列式数据(如 Parquet/ORC)在对象存储上传输的优化:1)压缩:列数据压缩后存储,减少存储体积与传输字节,对象存储带宽受限时尤其有效;2)Range 读取(字节范围读):对象存储支持按 Range 读取,列式文件的"读取所需列 + 跳块"只需下载对应列的字节范围,而非整个文件,大幅减少传输量;3)footer/元数据先读:先读文件 footer 获取统计与偏移,再按需读数据块;4)并行下载:对多个列块/row group 并行 Range 请求,提升吞吐;5)数据本地性与缓存:列数据在对象存储上不可变,可缓存聚合,避免重复下载;6)压缩与编码(如 Parquet 的 dictionary、Zstd)在格式层已优化,传输时天然受益。配合谓词下推,只下载满足条件的块,是对象存储上列式分析(如数据湖、Lakehouse)高效的关键。还需注意对象存储的请求开销(延迟),用 Range 与并行减少请求次数。

对象存储传输优化的核心是"只传所需 + 压缩 + 并行 Range"。列式格式的 footer 统计 + 按列的 Range 读取 + 压缩,让数据湖分析只下载必要字节,降低传输成本。

#
★★

18. 自适应编码(根据数据特征选择)

自适应编码(根据数据特征选择编码)如何实现?

  • 数据特征与编码匹配
  • 自动选择编码
  • 压缩率与解码性能平衡

自适应编码指"根据数据块的实际特征自动选择最优编码",而非固定一种编码。原理:1)对数据块采统计特征(基数、重复度、有序性、值域、稀疏度);2)根据特征候选多种编码(字典、RLE、Delta、Bitpacking、plain);3)计算或实测各编码的估计压缩率与解码成本,选择最优;4)把所选编码写入元数据。例如:低基数 → 字典;连续重复 → RLE;有序等差 → Delta;低位宽整数 → Bitpacking;无明显特征 → plain 或快速压缩。自适应编码的收益:对不同数据分布都能达到较高压缩率,且避免"编码不适配导致的压缩率差或解码慢"。实现上,Parquet/ORC 各 writer 会做启发式选择(如先试字典、字典基数过高则回退)。设计权衡:自适应需要采样与尝试的开销,编码选择也需考虑解码性能(不只压缩率)。目标是在"压缩率 + 解码性能"间取最优。

自适应编码的核心是"让编码匹配数据特征"。它通过采样统计 + 候选编码评估 + 自动选择,兼顾压缩率与解码性能,避免固定编码的适配差。

#
★★

19. 编码对 SIMD 解码友好的设计

编码如何设计才能对 SIMD 解码友好?

  • SIMD 解码的条件
  • 编码格式设计
  • 解码的批量性

编码对 SIMD 解码友好,需要满足几个条件:1)解码器是"无分支/少分支"的批量循环——SIMD 解码需要数据流独立,避免逐元素分支;2)固定宽度(fixed-width)或规则布局——如 Bitpacking 的固定位宽、字典 ID 的固定宽度,便于 SIMD 批量加载与解包;3)对齐与连续——编码数据在内存中连续、对齐(如 32/64 字节),SIMD 可一次加载多个元素;4)解码可并行——一批元素的解码互不依赖,可并行执行。设计示例:Bitpacking 用固定位宽 + 批量解包(SIMD 一次解多个整数);字典编码用固定宽度 ID 批量读取;Delta 编码用 SIMD 批量累加还原;RLE 用 SIMD 批量展开。相对地,Varint、(变长)、复杂字典查找等不利于 SIMD。因此 SIMD 友好的编码通常是"固定宽度、规则、可批量、无分支"的格式,牺牲一点压缩率换取解码吞吐。

SIMD 友好编码的本质是"固定宽度、规则、可批量、无分支"。编译的格式决定了解码能否用 SIMD 批量处理,是"压缩率 vs 解码速度"权衡的另一维度。

#
★★

20. 向量化过滤(选择)的位图结果集

向量化过滤(选择)的位图结果集如何实现?

  • 过滤生成位图(selection vector)
  • 位图与压缩/投影
  • 延迟物化

向量化过滤对一批列数据做条件判断,输出一个"位图结果集"(selection vector / 掩码位图),标记哪些行满足条件。实现:1)用 SIMD 比较整批列值,生成掩码(如 AVX2 比较产生位掩码);2)掩码标记每行是否通过(1/0);3)后续算子按掩码处理——只对满足的行做投影/计算,或先保留位图(延迟物化)到更晚阶段再取数据。优势:1)位图紧凑(1 位/行),可作中间结果在算子间传递,避免物化整列数据;2)过滤与投影分离,可"先算位图再按需取列",减少无关列的处理(延迟物化);3)位图可压缩(稀疏位图用 RLE)、可做位运算(与/或/非)组合多个过滤条件。向量化过滤的位图结果集是"选择(selection)"的核心:它把"哪些行保留"作为轻量位图传递,直到真正需要列数据时才取,兼顾正确性与性能。位图还可用于聚合计数、join 匹配等。

向量化过滤的本质是"用位图(掩码)表示选择结果,支持延迟物化"。位图紧凑、可组合、可延迟取据,让过滤在算子间高效流动。

#

21. 向量化表达式求值的批量执行

向量化表达式求值的批量执行如何实现?

  • 表达式按批计算
  • 与逐行求值对比
  • 表达式编译/向量化

向量化表达式求值把表达式(如 a + b * 2、函数调用)在一批数据上批量计算,而非逐行求值。实现:1)表达式被编译为对列向量的批量操作序列(如先对 b 乘 2,再与 a 相加),每步处理整列;2)中间结果用列向量表示,SIMD 批量运算;3)对 null 处理用三值逻辑位图;4)复杂表达式可编译为"批量执行函数"(去掉解释开销)。与逐行求值相比,批量执行消除了每行的解释/分派开销,利用 SIMD 与缓存局部性。优化:1)表达式编译(expression compilation)——把表达式树编译为可对齐的批量代码(类似 JIT),减少运行时解释;2)常量折叠(把常量表达式提前算);3)算子融合(把表达式与过滤/投影融合)。向量化表达式求值让"投影、过滤、函数计算"等在批上高效执行,是分析引擎计算性能的关键。数据类型、null、溢出等需在批量路径中正确处理。

向量化表达式求值的核心是"在列向量上批量计算表达式,消除逐行解释"。配合表达式编译与 SIMD,让投影/过滤/函数计算高效执行。

#

22. 向量化执行与 JIT 编译的组合

向量化执行与 JIT 编译如何组合?

  • JIT 编译的概念
  • 向量化与 JIT 的互补
  • 混合执行策略

JIT(Just-In-Time)编译指在运行时把查询(或表达式)编译为机器码直接执行,避免解释求值。向量化执行与 JIT 可组合:1)向量化引擎用"批量列向量 + SIMD"处理,JIT 可把"按批的算子/表达式"编译为高效机器码(含 SIMD 指令),生成针对特定查询的可执行函数——这比通用解释/向量化解释更进一步,消除解释与类型分派开销;2)JIT 可自动向量化(把循环编译为 SIMD 指令),或针对性生成无分支代码;3)对复杂表达式、多列、可变类型,JIT 生成特化代码(类型特化)提升性能。组合策略:向量化作为基础执行框架(批处理、列向量、算子),JIT 用于关键/热路径(表达式、过滤、聚合循环)的生成与优化;两者结合既保留向量化的架构优势,又通过编译获得接近手写机器码的性能。风险是 JIT 编译有启动开销(需权衡)与调试复杂度。ClickHouse、DuckDB、Velox 等常结合两者。

向量化与 JIT 的组合是"架构 + 加速":向量化提供批处理与列向量框架,JIT 把热路径编译为特化机器码(含 SIMD),消除解释与类型开销,兼顾吞吐与灵活性。

#

23. ProxySQL 的查询缓存与黑名单

ProxySQL 的查询缓存与黑名单如何实现?

  • 查询缓存机制
  • 黑名单/限流
  • 应用场景

ProxySQL 的查询缓存:通过 mysql_query_rulescache_ttl(缓存存活时间)为匹配查询开启结果缓存;命中缓存的查询直接返回缓存结果,不访问后端,减轻数据库压力。适用"高频、结果变化不大、可接受一定过期"的查询(如配置查询、统计查询)。缓存为按查询摘要(digest)的全局缓存,需设置合理 TTL 并注意缓存一致性(写后需失效)。黑名单:通过查询规则把异常/可疑查询"deny"(拒绝)或限流,如匹配到 SELECT * FROM huge_table 或恶意 SQL 时拒绝执行;或对超高频查询(digest 超阈值)做限流(如 max_connections 或频率限制),防止拖垮数据库。黑名单实现:在 mysql_query_rules 中设置 error_msg(拒绝并返回错误)或 mirrorlimit 等。查询缓存与黑名单共同实现"减负 + 防护":缓存降低重复查询压力,黑名单拦截异常/恶意查询。设计要点:缓存的 TTL 与一致性、黑名单规则的覆盖范围与误伤风险。

查询缓存解决"重复查询压库",黑名单解决"异常/恶意查询拖库"。两者都通过查询规则实现,缓存须注意 TTL 与一致性,黑名单须防误伤。

#

24. ProxySQL 的故障转移与健康检查

ProxySQL 的故障转移与健康检查如何实现?

  • 健康检查机制
  • 故障转移与摘除
  • 权重与连接管理

ProxySQL 通过健康检查(health check)监控后端数据库状态,并据此做故障转移。健康检查:ProxySQL 定期对每个后端(mysql_servers)做探测(如 SELECT 1mysql_ping),根据 check_interval(间隔)、check_timeoutcheck_fail(连续失败次数)判定后端是否健康;健康状态写入 mysql_serversstatus(ONLINE/OFFLINE/SHARDING)。故障转移:当主库健康检查失败,ProxySQL 标记其不可用,并从写路由中摘除;若配置了复制组(mysql_replication_hostgroups),可配合后端切换(如从库提升为主)后更新路由。对从库,健康检查失败则从读路由摘除,避免读到故障/timing 数据。故障转移还涉及:连接的管理(断开到故障后端的连接)、流量在健康后端间按权重重分配。设计要点:健康检查参数要合理(避免误判导致频繁摘除),故障转移后需恢复后重新加入(状态恢复),并配合监控告警。ProxySQL 的故障转移是"探测 + 摘除 + 重路由"的自动机制。

ProxySQL 故障转移的核心是"健康检查判定 + 摘除故障后端 + 流量重路由"。它让后端故障时读/写流量自动避开故障节点,配合恢复后重新加入。