MPP 系列连载到第七篇的时候,群里最多的提问已经从“MPP 是什么”变成了“查询又慢了怎么办”“编译怎么老报错”“这个工具到底怎么用”。这篇就把这半年里最零散也最值钱的经验打包整理一下,涉及性能、注意事项、工具、编译和 FAQ 五块。每块单拎出来都能写一篇长文,但实际工作中它们是揉在一起的——一个查询慢了,你既要会看执行计划,也要知道是不是编译安装时参数没配好,还要能翻日志、上 GDB 定位是哪个 segment 进程出了问题。
如果你已经对 MPP 有基础了解,正在把它用于实际业务,或者正准备从 Oracle/MySQL 体系迁过来,又或者正在折腾 MPP 的源码编译,这篇应该能帮你少走不少弯路。下面按五个主题展开,全是实操里长出来的经验,不是教科书。
1. 性能:MPP 的“快”是分布出来的,也是分布毁掉的
外界对 MPP 的第一印象都是“快”,但只有真正用过的人才知道,MPP 的快是有前提的。它的性能第一性原理不是 CPU 核数多,而是“数据分布”。数据按照分布键 hash 到各个 segment 节点上,理想情况下每台机器拿到的数据量和计算量是均匀的。一旦这个前提被破坏,再多的节点也是白搭。
1.1 分布键选错,后面全白搭
我见过太多用户把 MPP 当单机数据库用,建表时随便指定一个分布键,甚至干脆用默认随机分布,结果跑起来比单机还慢。分布键的选择直接决定了 join 时要不要重分布(redistribute motion),也决定了每个 segment 上的数据是否均匀。
选分布键的原则很简单:
- 优先选高基数字段,比如订单号、用户 ID,这种字段 hash 后散得开。
- 优先选高频 join 的关联字段,两个大表 join 时,如果分布键一致,数据不需要在网络间搬移,直接在本地 segment 完成 join。
- 避免选低基数字段,比如性别、状态、省份这种取值很少的列,很容易把数据压到少数几个 segment 上。
- 如果表很小(比如维度表),直接用复制表(replicated),每个 segment 都放一份完整数据,避免广播。
建表之后再换分布键非常痛苦,需要重写整张表,所以在建表阶段就要想清楚。如果怀疑已有的表已经倾斜,可以用这条 SQL 快速检查:
SELECT gp_segment_id, count(*) AS cnt FROM your_table GROUP BY gp_segment_id ORDER BY gp_segment_id;正常情况下,每个 segment 的 count 应该差不多。如果发现某个 segment 的数据量是平均值的几倍甚至十几倍,那就是典型的倾斜。我还习惯再算一个比值:
SELECT max(cnt)::numeric / nullif(min(cnt), 0) AS skew_ratio FROM ( SELECT gp_segment_id, count(*) AS cnt FROM your_table GROUP BY gp_segment_id ) t;skew_ratio 超过 1.2 就值得警惕了,超过 1.5 基本意味着这个表的设计有问题。倾斜问题在分布式数据库里是绕不开的,识别得越早,代价越小。
1.2 执行计划里藏着的真相
在单机数据库上调优,重点看索引有没有被用上、join 顺序是否合理;在 MPP 里调优,重点变成了执行计划里的 Motion 节点。所谓 Motion,就是数据在 segment 之间流动的方式,大概分两类:Redistribute Motion(数据按新 key 重新分布)和 Broadcast Motion(把小表广播给所有 segment)。
用 explain 看执行计划时,我通常关注三个东西:
- 有没有不必要的 redistribute motion。两个表 join,如果分布键相同,不应该有 redistribute;如果出现了,说明 join 条件里的字段和建表分布键不一致。
- 广播的是不是小表。Broadcast 一个 100 行的维度表没问题,但如果是广播一个几千万行的大表,执行计划基本废了。
- 有没有“单点聚集”节点。比如 order by 最终要在 coordinator 上做归并排序,数据量特别大时这个单点会成为瓶颈。
实际调过几个慢查询之后你会发现,大量算子对硬件性能的挑战往往不是 CPU,而是网络。每一个 redistribute 动作都是一次全网 shuffle,数据量一大,万兆网卡都能被打满。所以 MPP 优化的核心原则就一句话:能不 shuffle 就不 shuffle。这也是为什么我会反复强调分布键——它在建表那一刻就决定了未来 SQL 的命运。
Oracle/MySQL 里常见的“加索引”“改 hint”“调整 join 顺序”这些手段,在 MPP 里能发挥的空间小很多。这是因为 MPP 的优化器(比如 GPORCA)会自动选择执行计划,SQL 写完了,计划基本已经定了,人为干预的余地不多。反而“怎么建表”“怎么设计分布键”“怎么裁剪分区”这些 DDL 阶段的工作,影响远比单机数据库大。
1.3 写入性能:这是 OLAP,别用 OLTP 姿势
很多从 MySQL 迁移过来的业务方,第一步就把应用原封不动搬过来,还是那条链路:应用一条条 INSERT,每笔订单一行。结果 MPP 集群跑起来比 MySQL 还慢,一度让我很头疼。
原因在于 MPP 的架构——每个 INSERT 都要经过 coordinator 分发到对应 segment,segment 还要写 WAL 日志,一条条插入的事务开销是单机数据库的上百倍。MPP 天生是为批量分析设计的,不是为点写设计的。
正确的写入姿势是:
- 用 COPY 命令批量装载,或者用 gpfdist 配合外部表并行导入,几十 GB 的数据可以在几分钟内灌进去。
- 应用层面把数据攒批,比如攒够 10 万行或者 100MB 再提交一次,效果立竿见影。
- 尽量别做高频 UPDATE/DELETE。MPP 的更新逻辑是先标记旧版本再插入新版本,频繁更新会快速制造表膨胀,性能随之恶化。
- 索引不是不能建,但每建一个索引,批量写时就要多维护一棵 B+ 树。分析型的查询往往全表扫描,索引的存在感远低于 OLTP。
记住:MPP 的分析性能是拿写入灵活性换的。能用批量装载解决的问题,就不要在应用层一条条插。
1.4 资源管理与并发:小查询被大查询饿死的真相
MPP 集群最容易出现的故障不是宕机,而是“看起来没死,但所有查询都卡住”。最常见的原因就是没有做资源隔离,一个大查询把集群所有 segment 的 CPU、内存、IO 全部占满,其他小查询全部排队。
老版本的资源队列(Resource Queue)功能比较粗,只能限制并发数;新版本推荐用资源组(Resource Group),可以细粒度地控制每个组的 CPU 份额、内存上限和并发度。我的实践经验是把业务按重要性分成几个资源组:
- 核心报表组:CPU 份额最高,并发适中,保证重点任务不被挤。
- 即席查询组:并发限制得严一些,防止有人跑大查询把集群打满。
- 后台批处理组:放在低峰期执行,内存上限给足。
资源配比不是一次调好的,要靠监控数据反复修正。刚开始我习惯把 CPU 份额配成比例制,后来发现更重要的是内存上限,因为 OOM 杀进程比 CPU 排队可怕得多。执行以下语句可以看资源组的使用状态:
SELECT * FROM gp_toolkit.gp_resgroup_status;从这能看出每个组的并发使用量、排队语句数、CPU 使用率。如果你发现一个组里大量语句在排队,说明这个组的并发配额太小;如果 CPU 打满、内存见底,那就是内存配额的问题。这套体系和单机数据库的差别非常大,需要时间来适应。
下表是我总结的 MPP 与单机数据库在调优关注点上的差异:
| 调优关注点 | Oracle / MySQL | MPP 数据库 |
|---|---|---|
| 核心瓶颈 | CPU、IO、锁、索引结构 | 数据分布、网络 shuffle、Motion |
| 执行计划 | 索引使用、join 顺序、hint | 分布键、广播/重分布代价 |
| 写入模型 | 小事务、高并发、行锁 | 批量装载、COPY、外部表 |
| 扩容方式 | 垂直扩容/只读备库 | 水平扩容、数据重分布 |
| 调优时机 | SQL 执行阶段 | 建表阶段就决定了大半 |
2. 注意事项:这些坑文档里不会明说
MPP 用起来最大的风险不是某个功能不会,而是你以为它和单机数据库差不多,按单机思维去设计、运维,最后在某个深夜收到告警。以下几个坑是我在真实环境里反复踩过的。
2.1 连接数不是你想的那个数
单机 PostgreSQL 设置 max_connections = 500,那就是 500 个连接。MPP 不一样,一个客户端连接打到 coordinator 上,coordinator 要为这个会话在每个 segment 上都启动一个后端进程。也就是说,如果集群有 48 个 segment,500 个并发连接就意味着集群里同时存在 500 × 48 = 24000 个 segment 后端进程。
刚开始我完全没意识到这个问题,直到有一次应用侧用长连接池把连接数打到上限,集群内存直接告警,coordinator 日志里全是“sorry, too many clients already”。从那以后我学乖了:
- 所有应用必须走连接池(pgbouncer 或 odyssey),并严格控制池里的连接数,推荐值是单节点 CPU 核数的 2 到 3 倍,而不是应用并发数。
- max_connections 参数调大之前,先算算集群总进程数会不会把内存撑爆。
- 定期清理 idle 状态的会话。写个定时任务,把空闲超过 30 分钟的连接断掉,能省出大量内存。
这个坑是所有从单机数据库迁到 MPP 的人必踩的,只能说提前知道就能少一次深夜值班。
2.2 表膨胀和事务回卷:PG 系的“宿命”
基于 PostgreSQL 的 MPP 数据库,无论商业版还是开源版,都继承了 MVCC 机制。这意味着 UPDATE、DELETE 不会真正物理删除旧数据,而是标记旧版本。如果不定期清理,表会持续膨胀,查询扫描的数据块越来越多,IO 性能明显下降,磁盘占用也会悄悄往上爬。
我遇到过一次“io性能明显下降了”的排查,磁盘 IO 等待时间高得离谱,查了半天发现不是磁盘故障,而是一张每天被频繁更新的维度表膨胀到了原始数据的 5 倍。从那以后,我把 VACUUM 和 ANALYZE 直接写进了例行维护脚本,每周固定跑一次。
可以用下面这条 SQL 找出膨胀最严重的表:
SELECT schemaname, relname, n_live_tup, n_dead_tup, n_dead_tup::float8 / greatest(n_live_tup, 1) AS dead_ratio FROM pg_stat_user_tables WHERE n_dead_tup > 10000 ORDER BY dead_ratio DESC;dead_ratio 超过 0.2 的表就该安排了。同时别忘了 ANALYZE,统计信息过期比膨胀更危险,它会直接让优化器选出错误的执行计划,性能可能下降一个数量级。
2.3 扩容缩容:不是加个节点就完事
MPP 的优势是水平扩展,但扩容不是“加一台机器”那么简单。扩容过程中要做数据重分布,也就是把已有的数据按新的分布策略重新打散到所有节点。这个过程的耗时取决于数据总量,几十 TB 的集群扩容十几个小时是常态,期间集群 IO 和网络压力很大,业务查询会明显变慢。
缩容更麻烦,需要把被缩节点的数据先迁移出去,再下线节点,流程复杂且风险高。所以我的建议是:扩容前做好容量预估,尽量让集群一步到位;如果一定要扩,选业务低峰期执行,并提前通知业务方接受性能波动。
2.4 功能兼容性:别把 MPP 当 Oracle/MySQL 用
MPP 确实兼容标准 SQL,支持窗口函数、复杂 join、分析函数,性能也很强。但它在某些地方是有明确短板的:
- 点查能力弱。按主键查一行数据,也要经过 coordinator 路由到对应 segment,比单机数据库的主键索引慢不少。
- 支持的唯一约束、外键约束通常很鸡肋,因为全局约束检查代价极高,很多 MPP 实现干脆不推荐使用。
- 触发器、存储过程的生态远不如单机数据库丰富。
- 高并发小事务不是它的菜。
如果你的业务特征是大量小事务、点查、强约束,那应该优先考虑分布式 OLTP 数据库,而不是 MPP。MPP 适合的是数据量在 TB 级以上、以复杂分析查询为主的场景。产品选型阶段想清楚定位,比后期调优省心一万倍。
3. 工具链:从 explain 到 GDB 的组合拳
MPP 的日常运维离不开一套趁手的工具。许多人以为数据库运维就是打开一个图形化客户端,看看表、跑跑 SQL 就行,实际真正遇到性能问题、进程崩溃的时候,图形界面什么忙都帮不上。我平时的排障工具链由四层组成,从 SQL 层到进程层逐级递进。
3.1 SQL 层诊断:psql 加系统视图
命令行永远是第一选择。psql 里的 \timing 打开之后,每个查询的耗时一目了然,是判断一个 SQL 是否异常的直观手段。
执行计划分析用 explain,但要注意 explain analyze 会真实执行查询,生产环境跑在大表上会影响业务,建议先 explain 看计划,确认有把握再加 analyze。
MPP 维护文档里非常实用的一组视图我几乎天天用:
- pg_stat_activity:看当前所有会话和正在执行的 SQL,定位长时间运行的查询。
- gp_toolkit.gp_size_of_disk_data:看每个节点的磁盘占用分布,快速发现数据倾斜。
- gp_toolkit.gp_resgroup_status:看资源组使用与排队情况。
- gp_toolkit.gp_workfile_usage:看查询是否用了大量临时文件,往往是内存不足或排序过大。
我最常用的排查语句长这样,能直接列出运行超过 5 分钟的 SQL:
SELECT pid, usename, state, now() - query_start AS running_time, query FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '5 minutes' AND query NOT ILIKE '%pg_stat_activity%' ORDER BY query_start;这套组合拳能解决 80% 的性能问题定位——剩下的 20% 需要往下一层走。
3.2 图形化工具:看拓扑可以,调优别依赖
很多团队喜欢给 DBA 配上图形化工具,类似 SQLServer 的客户端或者 Navicat 这种。MPP 生态里 peer 级的图形工具也不少,pgAdmin、DBeaver 都能连,商业发行版还自带 Web 控制台,例如 Greenplum Command Center。
我的看法是:图形工具适合看集群整体状态、segment 健康情况和历史监控曲线,真正做性能调优时,用命令行加 SQL 视图效率反而更高。因为图形工具看到的执行计划是“格式化”过的,缺少很多 Motion 细节,而 MPP 的调优点恰恰就在这些细节里。就跟你用 SQLServer 的图形工具看执行计划时会切换到“估计的执行计划”窗口一样,最终你还是要看底层算子,不是看那些花花绿绿的箭头。
3.3 进程层定位:GDB 是最后的底牌
SQL 层解决不了问题时,往往已经发生 segment 进程崩溃或卡死。这时候要看什么?第一是日志,第二就是 core dump。
具体做法是:先查 pg_log 下的 segment 日志,找到崩溃进程的 PID,然后看有没有生成 core 文件。有 core 文件的话,用 GDB 加载分析:
gdb `which postgres` /path/to/core.pid进入 GDB 之后,先执行 bt 看调用栈,再执行 thread apply all bt 看所有线程的堆栈。如果 core 文件显示某个函数在等待锁,那基本可以判断是死锁或资源等待。
如果进程还活着但手感不对,可以用 gdb -p 直接附加到进程上:
gdb -p 12345同样用 bt 看堆栈。需要提醒的是,生产环境附加进程要非常谨慎,不少公司禁止在业务高峰期执行这类操作,因为 gdb 附加会短暂挂起进程。正确的做法是在测试环境复现,或者把问题现场先留证,等低峰期再分析。
日志和 GDB 的组合是我排查疑难问题的标准流程:先看日志找到方向和进程号,再用 GDB 深入内部看锁和内存状态。这套方法帮我在不少案例里定位到了核心问题,也让我真正理解了 MPP 内部进程的工作方式。
3.4 运维脚本与生态工具:效率翻倍的关键
如果集群规模超过 10 台,手工 SSH 上去逐台检查就不现实了。我建议把监控和巡检脚本化、平台化:
- 基础监控用 node_exporter 加 Prometheus,数据库指标用 postgres_exporter,再通过 Grafana 展示。重点监控项包括:segment 数据偏差、磁盘空间、连接数、CPU、内存、网络吞吐。
- 例行巡检脚本用 shell 写一个循环,逐节点执行磁盘、会话、日志检查,输出摘要。
- 大数据生态集成方面,MPP 通常支持通过外部表访问 HDFS 数据,实现与 Hadoop 体系的联动,这就不多展开了。
说到“工具类”这个话题,我在团队里一直强调一句话:工具是拿来用的,不是拿来看的。引入一个图形控制台、一套监控系统,最终目的都是缩短排查时间。如果你的工具链不能让一个新手在 10 分钟内定位到问题方向,那这套工具就是摆设。
4. 源码编译:从 tar 包到可运行集群的完整记录
编译 MPP 源码这件事,很多使用者会觉得没必要——直接用官方安装包不好吗?我的观点是,自己完整编译一遍,收益远超想象。编译过程能让你理解 MPP 的目录结构、依赖关系、构建选项对最终行为的影响,排错时多了一层直觉。而且某些高级特性只有在编译时显式打开才生效,直接装二进制包往往享受不到。
这一节以基于 PostgreSQL 的 MPP 为例,我自己的环境是 Ubuntu 服务器,源码从 GitHub 拉取。
4.1 环境准备:依赖、内存、磁盘
编译 MPP 是个体力活,对机器有三点硬性要求:
- 内存至少 8GB,低于这个数在 make 阶段很容易触发 OOM,cc1 进程被内核杀掉,报错信息看着像编译器 bug,其实是内存不足。
- 磁盘剩余空间至少 20GB,源码树、中间产物和安装目录加起来挺占地方。
- 依赖包必须装齐。Ubuntu 上一般需要这组:
sudo apt-get install build-essential bison flex libreadline-dev zlib1g-dev libssl-dev libxml2-dev libkrb5-dev如果你没有 root 权限,只能在用户目录下编译,也可以,就是需要先把依赖装到自己的目录里。读一下 readline 或者 zlib 的源码包,按老套路装:
./configure --prefix=$HOME/.local make -j4 make install然后导出环境变量,让后续的 configure 能找到这些库:
export PATH=$HOME/.local/bin:$PATH export LD_LIBRARY_PATH=$HOME/.local/lib:$LD_LIBRARY_PATH export CPPFLAGS="-I$HOME/.local/include" export LDFLAGS="-L$HOME/.local/lib"这个问题在官方编译文档里通常只有一句话,实际上坑过不少人。如果你照着网上的教程在无 sudo 的机器上编,十有八九是这步没做对。
4.2 编译步骤与关键参数
以 Greenplum 系 MPP 为例,编译流程大致是:
git clone https://github.com/greenplum-db/gpdb.git cd gpdb ./configure --prefix=/usr/local/gpdb --with-python --with-perl --with-libxml make -j8 make install source /usr/local/gpdb/greenplum_path.shconfigure 这一步值得仔细看。--prefix 指定安装目录,--with-python 和 --with-perl 开启 PL/Python、PL/Perl 过程语言支持,--with-libxml 开启 XML 相关功能。需要什么功能,在编译前就要想清楚,编译完了再改就要全部重来。
make 的并行度控制也有讲究。我见过有人直接 make -j32,结果内存吃满,机器直接卡死。稳妥的做法是根据内存调整,8GB 内存用 -j4,16GB 用 -j8,32GB 以上再考虑 -j16。并行度不是越高越好,尤其在这个编译过程会触发大量 C++ 编译器的场景下,内存才是真正的瓶颈。
编译日志一定要保留。不要用 nohup 把输出丢弃,正确做法是:
make -j8 > build.log 2>&1 tail -f build.log编译失败后,先看 build.log 最后几百行,绝大多数问题在日志里都有明确线索,比到处搜报错快。
4.3 编译报错实录:三个典型问题
这些年编译 MPP 遇到的报错,归根结底就是三类,说不上多高级,但每一个都让我折腾了好几个小时。
第一类:互相冲突的版本或找不到头文件。configure 阶段报错最常见,类似 configure: error: readline library not found。这基本就是依赖没装或者路径不对。装好 libreadline-dev 之后重新 configure 即可,无 sudo 场景就检查 CPPFLAGS/LDFLAGS 有没有导出到位。
第二类:编译过程中 cc1 进程被 OOM killer 杀掉。报错通常是 gcc: internal compiler error: Killed,出现这个先别怀疑编译器坏了,去查 /var/log/syslog,十有八九是内存不够。解决方法是降低 -j 并行度,或者临时加 swap,同时关掉网页浏览器、IDE 这些吃内存的程序。
第三类:编译速度异常慢。我遇到过 Linux 服务器上的文件审计服务在后台监听整个源码目录,导致每次读写文件都要过一道实时扫描,make 的速度慢到令人发指。从 Windows 上玩 ESP32 编译的经验也能看到类似问题,安全软件的实时防护对小文件密集的编译过程伤害极大。解决方式是让编译目录加入扫描白名单,或者干脆把编译放到隔离环境执行。Windows 上类似 Defender 实时保护拖慢 IDE 的说法也很常见,本质是同一回事。
另外强烈建议在编译环境中安装 ccache。第一次全量编译不吃亏,关键是后续改一行源码重新编译时,ccache 能命中大量缓存,把增量编译从几分钟压缩到十几秒。这在调试自己的改动时幸福感极高。
4.4 编译完成后的部署验证
安装完成后,先执行 greenplum_path.sh 或设置好 PATH,然后验证版本:
SELECT version();如果是全新集群,还需要运行 gpinitsystem 之类初始化命令,把 segment 实例创建出来。这一步之后,建议做一轮冒烟测试:建库、建表、插入几行、跑一个带 join 的查询、最后 explain 看执行计划是否正常。千万别直接跨过验证步骤就上生产配置,我有一次就是编译完直接配集群,结果 segment 起不来,排查了半天是个依赖库版本不对。
如果集群规模大、需要批量部署,建议把编译产物打包,做成 rpm/deb 或者标准的 tar 包,内网分发到各机器。避免每台机器都现场编译一遍,既慢又容易出现环境差异。
5. FAQ:团队群里问得最多的十个问题
最后把日常答疑里最高频的问题集中列一下,每个问题都附上我的排查思路和结论。这些问题看着基础,但背后都是真实翻车现场。
5.1 性能类
Q:为什么 count(*) 这么慢?
因为 MPP 没有“元数据级 count”优化,count 必须真实扫描所有 segment 上的数据,再汇总到 coordinator。数据量越大越慢是正常的。优化思路是维护一张预聚合的计数表,或者在允许误差的场景用采样估算。
Q:某个查询以前快,今天突然变慢,怎么定位?
按顺序排查:先看统计信息是否过期,跑一次 ANALYZE,很多时候问题立刻消失;再看执行计划是否变化,重点是有没有出现新的 redistribute motion;然后看资源组排队情况,是不是被大查询堵住;最后检查表膨胀。经验顺序是先统计信息、后执行计划、再资源、最后表结构。
Q:IO 性能明显下降了,怎么排查?
先用 iostat 和 vmstat 看是所有节点都慢还是个别节点慢。如果是某个节点慢,检查磁盘是不是出了问题、数据是否倾斜、表是否膨胀。很多时候所谓“IO 性能下降”其实是数据分布不均导致某些节点过载,不是磁盘本身的问题。
Q:大表 join 大表怎么调优?
第一优先保证两表分布键一致,这样 join 完全在本地完成,不产生重分布。第二,通过过滤条件尽量下推减少参与 join 的数据量。第三,检查执行计划里有没有不必要的 motion。如果小表足够小,可以设置成复制表或者接受广播 join,但要确认广播代价可控。
5.2 编译部署类
Q:编译时内存不够怎么办?
先降低 -j 并行度,比加 swap 管用。如果还不行,再加 swap 或者找一台内存更大的编译机器。不要在编译时同时跑别的重型任务。
Q:可以直接下载编译好的安装包吗?
商业发行版一般有官方预编译包,开源项目有时也有 CI 构建产物。能用就用,省时间。但如果你要跑在特殊环境或者需要自定义编译参数,就得自己编。就像某些 GIS 库会有已经编译好的 Windows 版本下载,省心但受限于默认配置,两回事。
Q:编译出来的二进制拷到另一台机器上报错 GLIBC 版本不对?
这是因为编译机的 glibc 比运行机新,动态链接时在旧机器上找不到高版本符号。解决办法是在目标环境或与目标环境 glibc 版本一致的机器上编译,不要相信“编译一次到处运行”那套,那是 Java 才有的待遇。
Q:扩容之后数据分布不均,怎么办?
检查扩容的第二个阶段有没有完成。有些 MPP 的扩容分两阶段,第一阶段只把新节点接入集群,第二阶段才会按新分布键重分布数据,第二阶段没跑完之前,新节点数据量很少,旧节点依然超载。执行重分布任务并确认完成即可。
5.3 使用与运维类
Q:小表 join 大表为什么还是慢?
看执行计划里小表是被广播还是被重分布。如果小表默认分布键和 join 键不一致,它可能没有被广播,而是老老实实重分布了一整张表,这个代价可能比广播还大。直接把它建为复制表通常能解决。
Q:连接数被占满怎么办?
先看 pg_stat_activity 里是不是一堆 idle 连接,把空闲连接清掉。然后查应用侧连接池配置,确认池大小是否合理。最后才是调大 max_connections,每次调大都要重新评估集群内存,因为连接数放大效应在 MPP 里非常致命。
Q:VACUUM 执行时提示表被锁或者卡住了?
典型原因是某些长事务一直持有快照不释放,VACUUM 只能跳过那些被事务访问过的数据。先去 pg_stat_activity 找长时间运行的事务,跟业务方确认后 kill 掉,再重新 VACUUM。另外,VACUUM 要在低峰期跑,运行时也会占用 IO,别把它和业务高峰叠在一起。
写到最后,想起一个很深的教训:我第一次编译 MPP 时,面对报错的第一反应是到处搜,后来发现大部分问题就是依赖没装齐,或者并行度太高内存撑不住。安静下来把 configure 的输出读一遍,比搜索引擎高效得多。这个系列也一样,很多答案不在文档里,而在你自己的环境里。碰到问题别急着换工具、换产品,先从分布、执行计划、资源、日志这几层按顺序查一遍,大多数问题都会浮出水面。