两表真正co-located需验证元数据而非仅看字段名:Citus查colocationid是否相同,TiDB核对分片键列名/类型/SHARD_ROW_ID_BITS/PRE_SPLIT_REGIONS,Doris检查EXPLAIN中LocalHashJoin是否出现。

确认两表是否真正 co-located(Citus/TiDB/Doris 均适用)
跨分片 JOIN 慢,90% 的原因是优化器误判了数据局部性——看起来字段一样,实际物理分布不一致。不能只看建表语句里都写了 user_id,必须查元数据验证。
- 在 Citus 中执行:
SELECT logicalrelid, partkey, colocationid FROM pg_dist_partition WHERE logicalrelid IN ('orders', 'users');两行colocationid必须完全相同,否则不是 co-located - TiDB 需检查
SHOW CREATE TABLE orders和SHOW CREATE TABLE users,确认SHARD_ROW_ID_BITS、分片键列名、类型(INT和BIGINT不兼容)、以及是否都启用了PRE_SPLIT_REGIONS - Doris 要核对
SHOW PROC '/frontends'和SHOW PROC '/backends',再结合EXPLAIN输出里的LocalHashJoin是否出现;没出现就说明没走本地 JOIN
避免用非分片键字段做等值 JOIN(TiDB/CockroachDB/Greenplum 通用)
哪怕右表有索引、JOIN 字段选择性极高,只要它不是分片键,分布式数据库大概率会广播或 shuffle 整张表。这不是 bug,是架构约束。
- 错误写法:
SELECT * FROM orders o JOIN customers c ON o.email = c.email—— 即使email是唯一索引,customers表仍可能被全量广播到每个节点 - 正确做法:把高频 JOIN 字段设为分片键,或加冗余字段(如在
orders表里冗余customer_id,并按该字段分片) - 某些引擎支持 hint 强制下推,但仅当逻辑可推导时生效:
/*+ SHARD_JOIN() */(CockroachDB)、/*+ TIDB_BROADCAST(c) */(TiDB),别指望它绕过分布约束
LEFT JOIN 右表为空或无索引时的隐形陷阱
很多人以为右表空就快,其实分布式环境下 LEFT JOIN 仍需确认“每条左表记录有没有匹配”,这触发的是全分片扫描,不是跳过。
- 右表若没全局唯一索引(不是普通二级索引),优化器无法定位到具体分片,只能拉取全部分片数据做 JOIN
- 比
LEFT JOIN更稳的写法是:NOT EXISTS (SELECT 1 FROM customers c WHERE c.id = o.customer_id),TiDB v7.5+ 和 Doris 2.1+ 能更好下推谓词 - 小表广播不等于安全:
/*+ BROADCAST(customers) */仅在单副本
物化固定 JOIN 路径比硬扛实时分布更可靠
如果某类 JOIN 模式稳定(比如订单 + 用户基础信息 + 地区维度),实时下推永远不如预计算。这不是妥协,是工程权衡。
- 在 TiDB(v7.5+)中建物化视图:
CREATE MATERIALIZED VIEW mv_order_user AS SELECT o.*, u.name, u.city FROM orders o JOIN users u ON o.user_id = u.id;—— 注意 TiDB 目前不支持自动刷新,得配定时任务跑REFRESH MATERIALIZED VIEW - Doris 支持异步物化视图:
CREATE MATERIALIZED VIEW mv_order_agg AS SELECT user_id, count(*) c FROM orders GROUP BY user_id REFRESH ASYNC; - Citus 没原生物化视图,但可用
CREATE TABLE ... AS SELECT+ 定时INSERT INTO ... SELECT ... ON CONFLICT模拟,配合citus_add_local_table_to_metadata()标记为本地表避免误广播
最容易被忽略的一点:统计信息过期比索引缺失更致命。你改了 JOIN 条件、加了新分区、批量导入了数据,但没跑 ANALYZE TABLE,优化器就还在按旧分布做决策——这时候 EXPLAIN 看起来一切正常,实际执行却跨了 12 个节点。每次上线新查询前,先查 SHOW STATS_META 或 pg_stats,别跳过这一步。

















