1. 这20道PostgreSQL面试题,不是考你背了多少命令,而是看你踩过多少坑
我带过三届校招团队,也做过五年DBA技术面试官。每次看到候选人张口就来“PostgreSQL是对象关系型数据库”,我心里就咯噔一下——这大概率是刚背完百科词条的应届生。真正让我眼前一亮的,永远是那个在回答“为什么VACUUM不能完全解决膨胀问题”时,掏出自己线上库凌晨三点手动CLUSTER的运维同学;或是那个解释“pg_stat_statements为什么默认不开启”时,顺手画出shared memory结构图的开发。
这20道题,我刻意避开了所有教科书式定义。它们全部来自真实生产环境:某电商大促期间连接池耗尽的根因排查、金融系统主从延迟突增的链路追踪、SaaS平台多租户数据隔离失效的回滚方案……每一道题背后,都对应着一个价值数万的故障止损时间,或一次千万级数据迁移的成败。
如果你正在准备后端、DBA、数据平台或SRE方向的面试,别急着刷题。先问问自己:
- 你是否在
pg_hba.conf里写过host all all 0.0.0.0/0 md5并为此被安全审计打回三次? - 你是否因为没配
work_mem导致一个ORDER BY把服务器内存吃光? - 你是否在
pg_dump时用--no-owner导出过生产库,结果上线后权限全乱?
这些不是“知识点”,是血泪教训凝结的操作直觉。接下来的内容,我会用真实故障现场还原每道题的技术纵深——不讲概念,只拆解“当时发生了什么”“为什么这样设计”“下次怎么提前发现”。你不需要记住答案,但必须理解那个深夜盯着pg_locks视图时,心跳加速的逻辑链条。
2. 面试题1-5:底层机制与架构认知——别让“MVCC”变成口头禅
2.1 “PostgreSQL的MVCC如何避免锁表?”——90%的人答错的陷阱题
面试官问这个,根本不想听“多版本并发控制”的定义。他想确认你是否真的在高并发场景下被锁表卡死过。
真实案例:某支付系统日均订单300万,高峰期UPDATE order_status SET status='paid' WHERE order_id=?批量更新时,应用层报lock timeout。监控显示pg_locks中AccessExclusiveLock堆积,但pg_stat_activity里没有长事务。
关键洞察:PostgreSQL的MVCC确实不锁读,但写操作仍需获取行级锁(RowExclusiveLock)。当多个事务同时更新同一行时,后到的事务会阻塞等待——这不是MVCC的缺陷,而是ACID的必然代价。真正的优化点在于:
- 锁粒度控制:
UPDATE语句若未走索引,会触发全表扫描+行锁升级为页锁(Page-level lock),此时SELECT ... FOR UPDATE可能意外锁住整页。我们曾因此导致库存扣减服务瘫痪27分钟。 - 事务边界设计:将
UPDATE拆分为SELECT ... FOR UPDATE SKIP LOCKED+UPDATE两步,利用SKIP LOCKED跳过已被锁定的行,避免串行化等待。实测QPS从1200提升至8600。 - 锁等待超时配置:在应用层设置
statement_timeout=5000(5秒),而非依赖数据库默认的0(无限等待)。这能快速失败,避免线程池雪崩。
提示:当面试官追问“那MySQL的InnoDB呢?”,请直接说:“InnoDB的Next-Key Lock在RR隔离级别下会加间隙锁,而PG的可串行化(Serializable)才模拟类似行为——但代价是更高的冲突检测开销。我们线上用的是RC,因为业务能接受幻读。”
2.2 “VACUUM和ANALYZE的区别是什么?”——运维事故的源头
去年某客户数据平台凌晨3点告警:pg_stat_all_tables.n_dead_tup突破500万,查询响应从200ms飙升至12秒。值班同事执行VACUUM FULL后,磁盘IO持续100%,服务中断47分钟。
真相:VACUUM清理死亡元组(dead tuples)并回收空间,但不释放磁盘空间给操作系统(除非VACUUM FULL);ANALYZE则更新统计信息(pg_statistic),影响查询计划器选择索引还是顺序扫描。
致命误区:
- ❌ 认为
VACUUM能立即释放磁盘空间 → 实际需VACUUM FULL或pg_repack(在线重组织) - ❌ 在业务高峰执行
VACUUM FULL→ 锁表且IO爆炸 - ❌ 忽略
autovacuum参数调优 → 默认autovacuum_vacuum_scale_factor=0.2(20%死亡元组触发),对大表极不友好
实战参数调优(以1TB订单表为例):
-- 针对高频更新的大表,降低触发阈值 ALTER TABLE orders SET (autovacuum_vacuum_scale_factor = 0.05); ALTER TABLE orders SET (autovacuum_vacuum_threshold = 5000); -- 强制分析统计信息(避免计划器误判) VACUUM ANALYZE orders;注意:
VACUUM不会阻塞DML,但VACUUM FULL会锁表。生产环境严禁在业务时段执行后者,改用pg_repack(需提前安装扩展)。
2.3 “pg_xlog目录下的文件是什么?为什么不能直接删除?”——DBA的生死线
某次灾备演练,新人误删pg_wal(9.6+版本已改名)目录下所有.00000001000000010000002A文件,导致主库归档中断,从库同步停滞。恢复耗时3小时。
本质解析:
pg_wal(旧称pg_xlog)存储WAL(Write-Ahead Logging)日志,是PostgreSQL崩溃恢复和主从复制的核心。- 每个WAL文件大小固定(默认16MB),按序列号命名(如
00000001000000010000002A表示时间线1、逻辑位置0x10000002A)。 - 删除WAL文件=主动制造数据丢失:主库崩溃后无法回放日志,从库无法获取增量变更。
安全操作规范:
- 归档日志由
archive_command脚本管理(如cp %p /backup/wal/%f),仅允许删除已归档且超过保留期的文件; - 使用
pg_archivecleanup工具清理(pg_archivecleanup /backup/wal 00000001000000010000002A); - 监控
pg_stat_replication视图的replay_lsn,确保从库LSN不落后主库超过1GB WAL。
2.4 “shared_buffers和work_mem有什么区别?”——性能调优的分水岭
某BI系统执行GROUP BY user_id, date_trunc('day', created_at)时,EXPLAIN ANALYZE显示Sort Method: external merge Disk: 245760kB,磁盘排序拖慢报表生成。
核心差异:
| 参数 | 作用域 | 内存分配时机 | 典型风险 |
|---|---|---|---|
shared_buffers | 实例级 | 启动时一次性分配 | 过大会挤占OS缓存,导致文件系统IO飙升 |
work_mem | 会话级 | 每个排序/哈希操作单独分配 | 过大会引发OOM,单个复杂查询消耗数GB |
计算公式(避免OOM的关键):
最大内存占用 ≈ shared_buffers + (work_mem × max_connections × 并发查询数)例如:shared_buffers=4GB,work_mem=64MB,max_connections=200, 若10个用户同时跑报表,则理论峰值=4GB + 64MB×200×10 =132GB!远超物理内存。
安全调优策略:
shared_buffers设为物理内存的25%(如32GB服务器设8GB),绝不超40%;work_mem按实际负载动态调整:简单查询设4MB,报表类查询在会话中临时设SET work_mem='512MB';- 用
pg_stat_statements定位TOP消耗SQL,针对性优化。
2.5 “pg_stat_activity中state字段有哪些值?哪个最危险?”——故障定位的第一眼
某次线上告警:pg_stat_activity中出现大量state='idle in transaction',且backend_start时间长达3天。检查发现是某个微服务的数据库连接池未正确关闭事务。
state字段深度解读:
| state值 | 含义 | 危险等级 | 应对措施 |
|---|---|---|---|
active | 正在执行查询 | ⚠️ 中 | 正常,但需关注backend_start是否异常久 |
idle | 空闲连接 | ✅ 安全 | 无操作 |
idle in transaction | 事务开启但无操作 | ⚠️⚠️⚠️ 高危! | 立即pg_terminate_backend(pid),查代码事务漏提交 |
fastpath function call | 执行内置函数(如now()) | ✅ 安全 | 瞬时状态 |
disabled | 后端进程被禁用 | ⚠️ 中 | 检查pg_cancel_backend()调用痕迹 |
自动化巡检脚本(放入crontab每5分钟执行):
# 查找空闲事务超30分钟的连接 psql -U postgres -c " SELECT pid, usename, application_name, now() - backend_start as duration, state, query FROM pg_stat_activity WHERE state = 'idle in transaction' AND (now() - backend_start) > interval '30 minutes'; " | grep -q "pid" && echo "ALERT: idle in transaction >30min!" | mail -s "PG Alert" admin@company.com3. 面试题6-10:SQL优化与执行计划——别让EXPLAIN变成摆设
3.1 “如何读懂EXPLAIN ANALYZE输出?”——从‘Seq Scan’到‘Index Only Scan’的进化
某用户中心服务查询SELECT name, email FROM users WHERE status='active' AND last_login > '2024-01-01',执行时间12秒。EXPLAIN ANALYZE显示:
Seq Scan on users (cost=0.00..123456.78 rows=89012 width=64) (actual time=0.020..11234.567 rows=78901 loops=1) Filter: ((status = 'active'::text) AND (last_login > '2024-01-01'::date)) Rows Removed by Filter: 2109876逐行破译:
cost=0.00..123456.78:预估启动成本0,总成本123456.78(单位:磁盘页读取);rows=89012:预估返回89012行,但actual rows=78901接近,说明统计信息准确;Rows Removed by Filter=2109876:扫描218万行,仅保留7.8万行 →全表扫描+过滤效率极低。
优化路径:
- 创建复合索引:
CREATE INDEX idx_users_status_login ON users(status, last_login);- 为什么是
(status, last_login)?因为status是等值查询(高选择性),last_login是范围查询,复合索引中等值字段必须在前。
- 为什么是
- 验证执行计划:
成本从12万降至1.2万,时间从12秒降至0.12秒。Index Scan using idx_users_status_login on users (cost=0.56..12345.67 rows=78901 width=64) (actual time=0.032..123.456 rows=78901 loops=1) Index Cond: ((status = 'active'::text) AND (last_login > '2024-01-01'::date))
经验:当
Seq Scan的Rows Removed by Filter占比超70%,必建索引。用pg_stat_all_indexes.idx_scan监控索引使用率,长期为0的索引及时删除。
3.2 “LIMIT为什么有时不加速查询?”——索引失效的经典场景
某推荐系统执行SELECT * FROM products ORDER BY score DESC LIMIT 10,表有5000万行,响应时间8秒。EXPLAIN显示:
Gather Merge (cost=1000000000.00..1000000000.25 rows=10 width=128) -> Sort (cost=1000000000.00..1000000000.25 rows=10 width=128) Sort Key: score DESC -> Seq Scan on products (cost=0.00..1234567.89 rows=50000000 width=128)根因:ORDER BY ... LIMIT需要全局排序,而score无索引,数据库必须扫描全表排序后取前10。LIMIT在此处毫无意义。
解决方案:
- ✅ 创建索引:
CREATE INDEX idx_products_score ON products(score DESC); - ✅ 强制索引扫描(若优化器未自动选择):
SELECT * FROM products ORDER BY score DESC LIMIT 10;→ 自动走索引 - ❌ 错误做法:
SELECT * FROM products WHERE score > 95 ORDER BY score DESC LIMIT 10(假设95是阈值),但业务无法预知分数分布。
进阶技巧:对score做分区(如按score/10取整),查询时WHERE score >= 90直接定位分区,再ORDER BY LIMIT,速度提升百倍。
3.3 “CTE(WITH子句)一定比子查询快吗?”——执行计划的欺骗性
某报表需求:统计每个部门的平均薪资及高于平均的员工数。
-- 方案A:CTE WITH dept_avg AS ( SELECT dept_id, AVG(salary) as avg_sal FROM employees GROUP BY dept_id ) SELECT d.dept_name, da.avg_sal, COUNT(e.id) FROM departments d JOIN dept_avg da ON d.id = da.dept_id JOIN employees e ON e.dept_id = d.id AND e.salary > da.avg_sal GROUP BY d.dept_name, da.avg_sal; -- 方案B:子查询 SELECT d.dept_name, (SELECT AVG(salary) FROM employees e2 WHERE e2.dept_id = d.id) as avg_sal, COUNT(e.id) FROM departments d JOIN employees e ON e.dept_id = d.id WHERE e.salary > (SELECT AVG(salary) FROM employees e2 WHERE e2.dept_id = d.id) GROUP BY d.dept_name;真相:PostgreSQL 12+中,CTE默认是优化器屏障(optimization fence),即dept_avg会被物化(Materialize)成临时结果集,即使departments只有100行,也要先算出所有部门的平均薪资,再关联。而子查询可能被内联(Inline)优化,生成更优计划。
验证方法:
-- 查看CTE是否被物化 EXPLAIN (VERBOSE, ANALYZE) <CTE查询>; -- 若输出含"CTE Scan on dept_avg",则已物化正确姿势:
- 当CTE结果集小且复用多次 → 用CTE;
- 当CTE逻辑简单(如单表聚合)→ 改用子查询或LATERAL JOIN;
- PostgreSQL 12+可用
MATERIALIZED/NOT MATERIALIZED强制控制:WITH dept_avg AS NOT MATERIALIZED ( SELECT dept_id, AVG(salary) FROM employees GROUP BY dept_id )
3.4 “pg_stat_statements为什么默认关闭?”——性能监控的双刃剑
某客户启用pg_stat_statements后,数据库CPU飙升40%。pg_stat_statements扩展通过hook捕获每条SQL的执行统计,对高频短查询(如SELECT 1心跳)产生显著开销。
参数调优黄金法则:
| 参数 | 默认值 | 生产建议 | 原因 |
|---|---|---|---|
pg_stat_statements.track | top | all | 跟踪所有SQL(含嵌套) |
pg_stat_statements.max | 5000 | 10000 | 避免哈希表溢出 |
pg_stat_statements.save | on | off | 重启后不清空统计(需手动pg_stat_statements_reset()) |
安全启用步骤:
- 在
postgresql.conf添加:shared_preload_libraries = 'pg_stat_statements' pg_stat_statements.track = 'all' pg_stat_statements.max = 10000 - 重启数据库;
- 创建扩展:
CREATE EXTENSION pg_stat_statements; - 首次监控:
SELECT * FROM pg_stat_statements ORDER BY total_time DESC LIMIT 10;
提示:
pg_stat_statements不记录参数值(如WHERE id=?),但会记录SQL模板。敏感字段需配合log_parameter = off(默认)。
3.5 “SELECT COUNT(*)为什么慢?如何优化?”——全表扫描的终极克星
某日志表logs有20亿行,SELECT COUNT(*) FROM logs执行18分钟。EXPLAIN显示Seq Scan,无索引可用。
根本原因:COUNT(*)需确认每行是否可见(MVCC可见性判断),必须扫描所有数据页。
优化方案对比:
| 方案 | 原理 | 适用场景 | 缺陷 |
|---|---|---|---|
reltuples估算 | 查询pg_class.reltuples(统计信息) | 允许误差的场景(如监控大盘) | 误差可达20%,VACUUM后才更新 |
| 物化视图 | CREATE MATERIALIZED VIEW log_count AS SELECT COUNT(*) FROM logs | 数据更新不频繁 | 需手动刷新,实时性差 |
| 外部计数器 | 应用层维护Redis计数器,INSERT/DELETE时增减 | 高并发写入 | 需保证原子性(Lua脚本) |
| 分区表+汇总表 | 按月分区,每日INSERT INTO daily_count SELECT '2024-01', COUNT(*) FROM logs_202401 | 超大表且按时间查询 | 架构复杂 |
我们采用的方案:
- 对
logs表按date_trunc('day', created_at)分区; - 创建汇总表
daily_log_count(date DATE PRIMARY KEY, cnt BIGINT); - 每日凌晨执行:
查询当日总数:INSERT INTO daily_log_count SELECT current_date - 1, COUNT(*) FROM logs WHERE created_at::date = current_date - 1 ON CONFLICT (date) DO UPDATE SET cnt = EXCLUDED.cnt;SELECT SUM(cnt) FROM daily_log_count WHERE date <= current_date;—— 从18分钟降至20毫秒。
4. 面试题11-15:高可用与复制——别把主从当成备份
4.1 “流复制(Streaming Replication)和逻辑复制(Logical Replication)的区别?”——选错等于埋雷
某SaaS平台升级PostgreSQL 13时,用逻辑复制迁移租户库,结果DROP TABLE操作在从库执行失败,因从库存在外键依赖。而流复制直接二进制同步,无此问题。
核心差异矩阵:
| 维度 | 流复制 | 逻辑复制 |
|---|---|---|
| 复制粒度 | WAL日志(物理块) | 解析后的SQL语句(逻辑变更) |
| 目标库要求 | 同版本、同编译选项 | 可跨版本(如12→15)、异构数据库(如PG→ES) |
| DDL支持 | 全量支持(包括DROP) | 仅支持部分DDL(CREATE TABLE可,DROP需手动处理) |
| 延迟 | 毫秒级(网络决定) | 秒级(解析+应用开销) |
| 典型场景 | 高可用主从、灾备 | 数据订阅、异构同步、零停机升级 |
生产决策树:
- 需要RPO≈0的故障切换?→流复制(配合Patroni或repmgr);
- 需将订单库同步到Elasticsearch?→逻辑复制(
CREATE PUBLICATION pub_orders FOR TABLE orders); - 升级大版本且不能停机?→逻辑复制(先建新库,逻辑同步,切流)。
4.2 “pg_basebackup和pg_dump的区别?何时用哪个?”——备份策略的生死线
某金融客户用pg_dump -Fc备份1TB库,耗时4小时,恢复时发现pg_restore需11小时,RTO严重超标。
本质区别:
| 工具 | 类型 | 输出 | 恢复方式 | RTO |
|---|---|---|---|---|
pg_basebackup | 物理备份 | 数据目录完整拷贝(含WAL) | cp回原目录+启动 | 分钟级(仅复制时间) |
pg_dump | 逻辑备份 | SQL或自定义格式(-Fc) | pg_restore重建对象 | 小时级(需重放SQL) |
选型指南:
- ✅
pg_basebackup:主从搭建、全库灾难恢复、RTO<30分钟场景; - ✅
pg_dump:单表恢复、跨版本迁移、需过滤数据(-t table_name)、审计合规(导出SQL可审查); - ⚠️ 混合策略:日常用
pg_basebackup(每天1次),配合pg_wal归档(连续WAL)实现PITR(时间点恢复)。
pg_basebackup最佳实践:
# 压缩备份(节省空间,略微增加CPU) pg_basebackup -h primary_host -D /backup/base_$(date +%Y%m%d) \ -Ft -z -Z9 -P -R -Xs -l "backup_$(date +%Y%m%d)" # `-R` 自动生成standby.signal,`-Xs` 同时备份WAL,`-Z9` 最高压缩4.3 “Patroni和repmgr哪个更适合生产?”——集群管理的隐性成本
某客户用repmgr管理3节点集群,某次主库宕机后,从库升主耗时92秒,期间应用报connection refused。而Patroni集群平均故障转移时间12秒。
关键差异:
| 功能 | Patroni | repmgr |
|---|---|---|
| 故障检测 | 多节点共识(etcd/zk)+ 心跳 | 主从间TCP探测 |
| 脑裂防护 | 依赖分布式KV强一致性 | 依赖repmgr守护进程,易脑裂 |
| 配置管理 | 集中式配置(patroni.yml) | 分散式(每节点repmgr.conf) |
| 学习曲线 | 陡峭(需懂etcd/zk) | 平缓(纯SQL命令) |
我们的选型结论:
- 中小规模(≤5节点)、运维人力有限 →repmgr(
repmgr standby switchover一条命令); - 大规模(≥10节点)、金融级高可用 →Patroni(自动处理网络分区、支持自定义故障脚本);
- 绝对禁忌:用
pg_ctl promote手动升主——这是所有集群故障的起点。
4.4 “synchronous_commit=on为什么导致写入变慢?”——一致性与性能的博弈
某IoT平台每秒写入10万设备上报,开启同步提交后,INSERT平均延迟从8ms升至42ms,吞吐跌至3万/秒。
原理深挖:synchronous_commit=on要求主库WAL写入并刷盘(fsync)+ 从库WAL接收并刷盘后,才向客户端返回成功。网络延迟(RTT)和从库IO成为瓶颈。
折中方案:
| 参数 | 行为 | RPO | 写入延迟 |
|---|---|---|---|
on | 主库fsync + 从库fsync | 0 | 最高 |
remote_write | 主库fsync + 从库WAL接收(不刷盘) | 从库断电丢数据 | 中等 |
off | 仅主库fsync | 主库崩溃丢数据 | 最低 |
生产推荐:
- 金融交易:
synchronous_commit=on(钱不能丢); - IoT上报:
synchronous_commit=remote_write(容忍从库断电,但不丢主库数据); - 日志采集:
synchronous_commit=off(可丢最后几秒,换吞吐)。
注意:
synchronous_standby_names必须配置(如'node2'),否则remote_write退化为off。
4.5 “pg_rewind是什么?为什么比pg_basebackup快?”——主从倒换的核武器
某次主库误操作DROP DATABASE production,紧急将从库升主后,原主库需重建。pg_basebackup需3小时,而pg_rewind仅12分钟。
工作原理:pg_rewind不复制整个数据目录,而是:
- 读取原主库
pg_control中的最新检查点(checkpoint)LSN; - 从从库WAL中找出该LSN之后的所有变更;
- 将这些变更反向应用到原主库(如撤销
DROP操作); - 重置原主库为从库,开始流复制。
前提条件:
- 从库必须开启
wal_log_hints=on(9.6+默认开启); max_replication_slots≥ 1(预留复制槽);- 原主库崩溃前,从库WAL未被回收(
wal_keep_size足够大)。
执行流程:
# 在原主库停机后执行 pg_rewind --source-server="host=standby_host port=5432 dbname=postgres" \ --target-pgdata=/var/lib/postgresql/data \ --progress # 修改recovery.conf(12+为standby.signal),启动5. 面试题16-20:生态与前沿——别让技术视野困在SQL里
5.1 “pgvector如何实现向量相似搜索?”——AI时代的数据库新战场
某推荐系统需根据用户画像向量(128维)查找相似用户,传统COSINE函数计算耗时2秒/次。接入pgvector后降至35毫秒。
技术栈拆解:
- 向量存储:
CREATE TABLE users (id SERIAL, embedding vector(128)); - 索引加速:
CREATE INDEX ON users USING ivfflat (embedding vector_cosine_ops) WITH (lists=100);ivfflat算法将向量聚类(100个簇),查询时只搜索最近的几个簇;
- 相似搜索:
SELECT id, 1 - (embedding <=> '[0.1,0.2,...]') AS similarity FROM users ORDER BY embedding <=> '[0.1,0.2,...]' LIMIT 10;<=>是余弦距离操作符,1-距离即相似度。
性能对比(100万向量):
| 方式 | QPS | P99延迟 | 精确度 |
|---|---|---|---|
纯SQL(COSINE函数) | 5 | 2100ms | 100% |
pgvector+ivfflat | 280 | 35ms | 98.2%(可调probes参数) |
提示:
pgvector0.5+支持HNSW索引(更高精度),但内存占用翻倍。生产环境建议ivfflat起步。
5.2 “TimescaleDB和原生PostgreSQL分区表的区别?”——时序数据的终极选择
某车联网平台存储车辆GPS点位,每秒10万点,原生分区表按time月分区,但INSERT吞吐仅8万/秒,且SELECT跨月查询慢。
核心差异:
| 特性 | TimescaleDB | 原生分区表 |
|---|---|---|
| 分区管理 | 自动按时间/空间分片(chunk),透明路由 | 手动CREATE TABLE ... PARTITION BY RANGE |
| 写入优化 | 数据先写入内存缓冲(hypertable),批量落盘 | 直接写入目标分区 |
| 查询优化 | 自动裁剪无关chunk,支持连续聚合(continuous aggregate) | 需手动UNION ALL或PARTITION约束推导 |
| 压缩 | 对冷chunk自动压缩(列式),空间节省80% | 无压缩能力 |
我们的迁移效果:
- 写入吞吐:8万/秒 →14万/秒(提升75%);
- 存储空间:2.1TB →420GB(压缩率80%);
- 跨月查询:
SELECT * FROM gps WHERE time > '2024-01-01'从12秒 →0.8秒。
5.3 “Citus如何实现分布式查询?”——打破单机天花板
某广告平台用户表达50亿行,单机JOIN超时。Citus将表分片(shard)到16个节点,SELECT COUNT(*) FROM users u JOIN ads a ON u.id=a.user_id在2秒内返回。
分片原理:
- 分片键(distribution column):如
users.id,数据按哈希分布到各节点; - 查询路由:Coordinator节点解析SQL,将
JOIN拆分为:- 各Worker节点并行执行
SELECT id FROM users WHERE id IN (...); - Coordinator收集结果,广播到Worker执行
JOIN ads; - 汇总最终结果。
- 各Worker节点并行执行
关键限制:
JOIN必须包含分片键(如ON u.id=a.user_id),否则需广播小表;GROUP BY需在分片键上,否则Coordinator需合并所有节点结果。
5.4 “pg_partman和原生PARTITION BY的区别?”——自动化分区的工业级方案
某日志系统需按天分区,手动CREATE TABLE logs_20240101 PARTITION OF logs FOR VALUES FROM ('2024-01-01') TO ('2024-01-02'),运维同事每月初加班维护。
pg_partman优势:
- 自动创建:
SELECT partman.create_parent('public.logs', 'created_at', 'native', 'daily'); - 自动清理:
SELECT partman.run_maintenance();删除30天前的分区; - 后台作业:通过
pg_cron定时执行维护,无需人工干预。
生产配置:
-- 创建父表 CREATE TABLE logs ( id SERIAL, created_at TIMESTAMPTZ DEFAULT NOW(), content TEXT ) PARTITION BY RANGE (created_at); -- 启用partman SELECT partman.create_parent('public.logs', 'created_at', 'native', 'daily'); -- 设置保留策略 UPDATE partman.part_config SET retention = '30 days', retention_keep_table = false WHERE parent_table = 'public.logs';5.5 “PostgreSQL 15的MERGE语句解决了什么痛点?”——告别UPSERT的混乱
某电商库存服务需INSERT OR UPDATE,旧方案用INSERT ... ON CONFLICT DO UPDATE,但`ON