当前位置: 首页 > article >正文

PostgreSQL Join 执行策略(Nested Loop、Hash Join、Merge Join)与 NOT EXISTS 优化

以集成数据压缩 SQL 优化为例用大白话讲清楚 Nested Loop、Hash Join、Merge Join 三种执行策略。一、背景一条慢 SQL 引发的思考在对上游下发数据做压缩时有这样一条 UPDATE SQL-- ❌ 原始写法UPDATEmagellan_nk_order_inboundSETversion_numberCONCAT(version_number,_zip)WHEREversion_number20260521172049432ANDorde_idIN(SELECTa.orde_idFROMmagellan_nk_order_inbound aLEFTJOINmagellan_nk_order_inbound bONb.version_number20260522000707011ANDa.orde_idb.orde_idWHEREa.version_number20260521172049432ANDb.orde_idISNULL);这张表有几千万行数据跑起来非常慢。问题根源就在于 PostgreSQL 对IN (子查询)选择了Nested Loop执行策略。二、先搞清楚 N 和 M 是什么后面讲复杂度时会用到 N 和 M先把它们定义清楚变量含义举例NJoin 左表外表过滤后的行数旧批次数据500 万行MJoin 右表内表过滤后的行数新批次数据500 万行三、三种 Join 执行策略3.1 Nested Loop嵌套循环原理就是两层 for 循环外表每扫一行都去完整扫描一遍内表for 外表每一行 a: ← 循环 N 次 for 内表每一行 b: ← 循环 M 次 if a.id b.id: 匹配时间复杂度O(N × M)举例N 500万M 500万500万 × 500万 25,000,000,000,000 次25万亿次比较即使内表有索引内层变成二分查找也是O(N × logM)500万 × log(500万) ≈ 500万 × 23 ≈ 1.15亿次比无索引好得多但数据量越大越吃力。适用场景外表结果集非常小几十行、几百行内表在 Join Key 上有索引一句话总结数据量小时很快数据量大时是灾难。3.2 Hash Join哈希连接原理分两个阶段可以理解为先建字典再查字典第一阶段 —— Build建字典把小表的 Join Key 全部装进一个 HashMap哈希表HashMap { id_001: true, id_002: true, id_003: true, ... }这一步扫描内表一次共M 次操作。第二阶段 —— Probe查字典遍历外表每一行拿 Join Key 去 HashMap 里查HashMap 查询是 O(1)for 外表每一行 a: if a.id 在 HashMap 里 ← O(1) 查询 匹配这一步扫描外表一次共N 次操作。时间复杂度O(N M)举例N 500万M 500万500万 500万 1000万次操作 ✅与 Nested Loop 的25万亿次相比快了约 250万倍。适用场景两张表数据量都较大Join Key 上没有合适索引内存足够装下小表的 HashMap一句话总结大表 Join 的首选策略用内存换时间。3.3 Merge Join归并连接原理和归并排序思路一样要求两张表都按 Join Key事先排好序然后像拉链一样合并表A已排序: 1, 3, 5, 7, 9 表B已排序: 2, 3, 6, 7, 8 双指针同时移动遇到相同值就匹配 A1, B2 → A前进 A3, B3 → 匹配双方前进 A5, B6 → A前进 ...时间复杂度O(N M) 不含排序开销 如果需要临时排序O(N·logN M·logM)适用场景两张表在 Join Key 上都有排序索引B-Tree 索引本身就是有序的大范围扫描场景一句话总结有序数据的最优解依赖索引排序。四、三种策略横向对比对比项Nested LoopHash JoinMerge Join复杂度O(N × M)O(N M)O(N M)500万500万25万亿次 1000万次 ✅1000万次 ✅内存需求低中需装小表低是否需要索引内表最好有索引不需要需要排序索引最适合外表极小大表无索引 Join大表有排序索引最不适合两张大表 Join内存不够时无索引时五、为什么 NOT EXISTS 更容易触发 Hash Anti Join5.1 什么是 Anti Join反连接普通 Join 是找匹配的行Anti Join 是找没有匹配的行对应 SQL 里的NOT IN (子查询)NOT EXISTS (子查询)LEFT JOIN ... WHERE b.id IS NULL这三种写法逻辑等价但 PostgreSQL 规划器对它们的理解深度不同。5.2 IN (子查询) 的问题WHEREidIN(SELECTidFROM...)PostgreSQL 在处理IN (子查询)时早期版本会把它理解为对外表每一行去子查询里查一遍这天然就是 Nested Loop 的思维模式。虽然新版 PostgreSQL 会尝试把它转成 Hash Join但受统计信息准确性影响一旦估算行数偏差大就可能选错策略。5.3 NOT EXISTS 为什么更好ANDNOTEXISTS(SELECT1FROM表BWHEREb.ida.idANDb.version#{newerBatch})NOT EXISTS的语义对规划器来说更直接“把表B的数据建成 Hash 表外表每一行去 Hash 表里查没查到的就留下”这正好就是Hash Anti Join的执行模式规划器几乎不会选错。5.4 三种等价写法的规划器友好度-- 写法1LEFT JOIN IS NULL最不友好FROMaLEFTJOINbONa.idb.idWHEREb.idISNULL-- 规划器需要推断IS NULL 意味着没匹配统计信息偏差时容易走 Nested Loop-- 写法2NOT IN较不友好WHEREa.idNOTIN(SELECTb.idFROMb)-- 还有 NULL 值陷阱如果子查询结果包含 NULL整个 NOT IN 结果为空-- 规划器处理 NULL 语义时额外保守-- 写法3NOT EXISTS最友好✅WHERENOTEXISTS(SELECT1FROMbWHEREb.ida.id)-- 语义最明确规划器直接映射为 Hash Anti Join-- 无 NULL 陷阱子查询返回 NULL 行不影响结果六、本次优化对比优化前-- countOverlapLEFT JOIN IS NOT NULLSELECTCOUNT(a.orde_id)FROMmagellan_nk_order_inbound aLEFTJOINmagellan_nk_order_inbound bONb.version_number#{newerBatch}ANDa.orde_idb.orde_idWHEREa.version_number#{olderBatch}ANDb.orde_idISNOTNULL-- ← 语义绕规划器理解成本高-- updateZipBatchNumberIN (子查询 LEFT JOIN IS NULL)WHEREversion_number#{olderBatch}ANDorde_idIN(SELECTa.orde_idFROM...LEFTJOIN...WHEREb.idISNULL-- ← 双重绕弯)优化后-- countOverlap改为 INNER JOIN语义直接SELECTCOUNT(a.orde_id)FROMmagellan_nk_order_inbound aJOINmagellan_nk_order_inbound bONb.version_number#{newerBatch}ANDa.orde_idb.orde_idWHEREa.version_number#{olderBatch}-- 规划器直接选 Hash Join ✅-- updateZipBatchNumber改为 NOT EXISTSWHEREversion_number#{olderBatch}ANDNOTEXISTS(SELECT1FROMmagellan_nk_order_inbound bWHEREb.version_number#{newerBatch}ANDb.orde_idmagellan_nk_order_inbound.orde_id)-- 规划器直接选 Hash Anti Join ✅七、额外加速创建合适的索引即使规划器选了 Hash Join扫描version_number时如果没有索引还是要全表扫描。推荐为压缩相关的表创建联合索引-- version_number 用于过滤批次orde_id 用于 JoinCREATEINDEXidx_st_ver_ridONmagellan_nk_order_inbound(version_number,orde_id);有了这个索引扫描version_number #{olderBatch}直接走索引跳过无关数据Hash Join 的 Build 阶段也能走索引快速定位新批次数据八、总结知识点结论Nested Loop 慢在哪两层循环复杂度 O(N×M)大表灾难Hash Join 为什么快建字典再查字典复杂度 O(NM)NOT EXISTS vs INNOT EXISTS 语义明确规划器直接选 Hash Anti JoinNOT EXISTS vs LEFT JOIN IS NULLNOT EXISTS 更简洁无 NULL 陷阱规划器更友好索引的作用减少参与 Join 的行数从源头降低 N 和 M核心记忆大表 Join 看 Hash Join反连接用 NOT EXISTS索引让 N 和 M 变小。

相关文章:

PostgreSQL Join 执行策略(Nested Loop、Hash Join、Merge Join)与 NOT EXISTS 优化

以集成数据压缩 SQL 优化为例,用大白话讲清楚 Nested Loop、Hash Join、Merge Join 三种执行策略。一、背景:一条慢 SQL 引发的思考 在对上游下发数据做压缩时,有这样一条 UPDATE SQL: -- ❌ 原始写法 UPDATE magellan_nk_order_i…...

Godot 2D随机地图三大静默故障:黑屏、穿墙、寻路失败的根源与修复

1. 为什么刚上手Godot做2D随机地图就总卡在“生成出来是黑的”“角色穿墙”“房间连不通”这三件事上?如果你是刚从Unity或GameMaker转来Godot,或者第一次用GDScript写程序逻辑的新手,大概率已经在2D随机地图生成这个环节反复摔过跟头——不是…...

基于Arduino Uno与MQ-2传感器的智能气体检测报警系统DIY全攻略

1. 项目概述与核心思路最近在捣鼓家里的智能安防,琢磨着能不能自己做一个成本可控、反应灵敏的气体检测报警装置。市面上成品烟雾报警器虽然成熟,但要么功能单一,要么价格不菲,而且很难根据自己的需求进行定制化调整,比…...

泰拉瑞亚地图编辑器:从像素画布到创意世界的蜕变之旅

泰拉瑞亚地图编辑器:从像素画布到创意世界的蜕变之旅 【免费下载链接】Terraria-Map-Editor TEdit - Terraria Map Editor - TEdit is a stand alone, open source map editor for Terraria. It lets you edit maps just like (almost) paint! It also lets you cha…...

机器学习赋能矩方法:破解稀薄气体强非平衡流动模拟难题

1. 项目概述:当矩方法遇见机器学习在计算流体力学领域,模拟稀薄气体动力学和强非平衡流动,一直是个让工程师和科学家们头疼的“硬骨头”。想象一下,你正在设计一架高超音速飞行器,当它以数倍音速在大气层边缘飞行时&am…...

Godot 4.3随机地图性能优化:避开TileMap与RNG陷阱

1. 为什么刚写完第一版随机地图就崩溃?——从“能跑”到“能用”的真实断层你兴冲冲地照着教程敲完几十行GDScript,RandomNumberGenerator初始化了,for x in range(width)循环也套好了,甚至还在_draw()里用draw_rect()把每个格子都…...

告别复杂模型:用Python+OpenCV+dlib实现简易驾驶员疲劳监测(附完整代码)

轻量级驾驶员疲劳监测系统:PythonOpenCVdlib实战指南 在长途驾驶或夜间行车时,疲劳是导致交通事故的重要因素之一。传统基于嵌入式设备的疲劳监测系统往往需要专用硬件,增加了开发成本和部署难度。本文将介绍如何利用Python生态中的OpenCV和d…...

NPU跑LLM实战指南:KV Cache动态性如何突破硬件限制

NPU跑LLM实战指南:KV Cache动态性如何突破硬件限制 副标题: 从预分配+Attention Mask到三层软件栈,完整解析NPU推理架构 痛点:为什么NPU跑LLM这么难? LLM的生成机制和NPU的硬件特性存在根本冲突: LLM特性 NPU特性 冲突点 逐token生成 固定shape执行 KV Cache动态增长 动…...

如何用Untrunc拯救损坏视频?2025年终极MP4修复工具完全指南

如何用Untrunc拯救损坏视频?2025年终极MP4修复工具完全指南 【免费下载链接】untrunc Restore a damaged (truncated) mp4, m4v, mov, 3gp video. Provided you have a similar not broken video. 项目地址: https://gitcode.com/gh_mirrors/unt/untrunc 当你…...

基于ISDN信令的来电语音播报系统:从原理到树莓派实现

1. 项目概述:一个基于ISDN的来电语音播报系统如果你家里或办公室里还有一台老式的ISDN路由器,别急着把它当电子垃圾处理掉。我最近就利用手头一台闲置的ISDN路由器,折腾出了一个挺有意思的小玩意儿:一个能自动识别来电号码&#x…...

纯硬件实现I2C协议:从逻辑门到传感器通信的深度实践

1. 项目概述:用纯硬件“解剖”I2C总线很多朋友在玩传感器,尤其是温湿度传感器时,都绕不开I2C这个通信协议。市面上绝大多数的教程和方案,都会告诉你:找个单片机(比如Arduino、STM32)&#xff0c…...

Python Android打包终极指南:5个实战技巧解决移动开发痛点

Python Android打包终极指南:5个实战技巧解决移动开发痛点 【免费下载链接】python-for-android Turn your Python application into an Android APK 项目地址: https://gitcode.com/gh_mirrors/py/python-for-android Python-for-Android(简称p4…...

为什么你明明很努力,领导却总看不到?问题出在这

许多测试同行在深夜加班排查Bug时,在凌晨赶写自动化脚本时,在对着海量数据做性能分析时,内心都会浮现一个共同的困惑:我明明已经这么拼了,为什么在领导眼里,我依然是个“找茬的”,而不是“创造价…...

ROS机器人仿真架构解析:基于wpr_simulation的移动操作机器人技术实现

ROS机器人仿真架构解析:基于wpr_simulation的移动操作机器人技术实现 【免费下载链接】wpr_simulation 项目地址: https://gitcode.com/gh_mirrors/wp/wpr_simulation 在机器人操作系统(ROS)开发领域,硬件依赖和测试成本一直是制约算法迭代效率的…...

ImageGlass:一个支持90+图像格式的轻量级Windows图片查看器

ImageGlass:一个支持90图像格式的轻量级Windows图片查看器 【免费下载链接】ImageGlass 🏞 A lightweight, versatile image viewer 项目地址: https://gitcode.com/gh_mirrors/im/ImageGlass 还在为Windows自带的图片查看器功能单一而烦恼吗&…...

JavaScript对象创建:告别繁琐,四种灵活写法一学就会

在JavaScript里,创建对象的这般方法常把刚开始学习的新手弄得困惑不已,好像无论走哪条道都行得通,可又不清楚该挑哪一条才好。我编写JavaScript都有十几年功夫了,对象创建这事差不多每天都会碰到可谓基础技能。它不像变量声明那般…...

终极崩坏星穹铁道自动化指南:3分钟掌握解放双手的智能游戏伴侣

终极崩坏星穹铁道自动化指南:3分钟掌握解放双手的智能游戏伴侣 【免费下载链接】StarRailAssistant 崩坏:星穹铁道自动化 | 崩坏:星穹铁道自动锄大地 | 崩坏:星穹铁道锄大地 | 自动锄大地 | 基于模拟按键 项目地址: https://git…...

AI 应用原型开发阶段利用 Taotoken 快速进行多模型效果对比

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 AI 应用原型开发阶段利用 Taotoken 快速进行多模型效果对比 在构建一个 AI 应用的原型时,开发者常常面临一个核心问题&…...

怎么理解Filter不是在afterCompetition里面remove掉ThreadLocal里面的东西,而是说在finally块里面remove

文章目录1. 核心原因:Filter 的“套娃(洋葱圈)”执行模型2. 为什么不能(也无法)在这里用 afterCompletion?维度一:Filter 拿不到 afterCompletion维度二:生命周期顺序的致命冲突总结…...

实测对比,使用Taotoken聚合接口后Agent任务延迟与稳定性观感

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 实测记录:使用 Taotoken 聚合接口后 Agent 任务延迟与稳定性观感 效果展示类,记录将原有基于单一 API 的 A…...

USB数据隔离器DIY:物理切断数据线,防范充电攻击

1. 移动设备充电安全:一个被忽视的“物理后门”你可能每天都在做这件事:手机或平板电脑电量告急,随手拿起一根数据线,插在办公室的公共电脑、机场的充电站,甚至是朋友提供的充电宝上。这看起来再平常不过了&#xff0c…...

如何让旧款Mac运行最新系统:OpenCore Legacy Patcher完整指南

如何让旧款Mac运行最新系统:OpenCore Legacy Patcher完整指南 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 想让你的老旧Mac设备重新焕发活力&a…...

抖音批量下载助手:一键构建你的专属视频素材库

抖音批量下载助手:一键构建你的专属视频素材库 【免费下载链接】douyinhelper 抖音批量下载助手 项目地址: https://gitcode.com/gh_mirrors/do/douyinhelper 还在为手动保存抖音视频而烦恼吗?想要批量获取心仪创作者的精彩内容却无从下手&#x…...

使用Taotoken CLI工具一键配置多开发环境下的统一模型接入点

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 使用Taotoken CLI工具一键配置多开发环境下的统一模型接入点 在团队协作或管理多个AI应用项目时,一个常见的痛点是每个…...

3大突破性功能:用HiveWE革新你的魔兽争霸III地图创作体验

3大突破性功能:用HiveWE革新你的魔兽争霸III地图创作体验 【免费下载链接】HiveWE A Warcraft III world editor. 项目地址: https://gitcode.com/gh_mirrors/hi/HiveWE 还在为传统魔兽争霸III编辑器缓慢的加载速度和复杂的操作界面而烦恼吗?Hive…...

如何高效实现Windows自动化鼠标点击:AutoClicker完整实战指南

如何高效实现Windows自动化鼠标点击:AutoClicker完整实战指南 【免费下载链接】AutoClicker AutoClicker is a useful simple tool for automating mouse clicks. 项目地址: https://gitcode.com/gh_mirrors/au/AutoClicker AutoClicker是一款专业的Windows桌…...

机器学习力场攻克Peierls相变动力学:从对称性描述符到畴生长标度律

1. 项目概述:当机器学习遇见Peierls相变在凝聚态物理和材料科学的前沿,我们常常被一个核心问题所困扰:如何精确地模拟那些由电子和晶格(原子)强烈耦合所驱动的复杂动力学过程?这类系统,比如电荷…...

WarcraftHelper:让经典魔兽争霸3完美适配现代电脑的终极解决方案

WarcraftHelper:让经典魔兽争霸3完美适配现代电脑的终极解决方案 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争霸3在现代操…...

数字合成器d-FORMANT:从模拟经典到数字复刻的工程实践

1. 项目概述:从模拟经典到数字复刻如果你对合成器稍有了解,或者对电子音乐制作背后的硬件感兴趣,那么“FORMANT”这个名字你一定不陌生。它最初是上世纪70年代由《Elektor》杂志发布的一款模拟单音合成器,以其清晰的模块化设计和出…...

大模型测试新范式:Claude端到端验证的5层断言体系(语义一致性/上下文连贯性/安全边界/成本阈值/时序鲁棒性)

更多请点击: https://codechina.net 第一章:大模型测试新范式:Claude端到端验证的5层断言体系(语义一致性/上下文连贯性/安全边界/成本阈值/时序鲁棒性) 传统LLM测试常聚焦于准确率或BLEU等静态指标,而Cla…...