PgSQL · 案例分享 · 递归收敛优化

简介: 背景有一个这样的场景,一张小表A,里面存储了一些ID,大约几百个。(比如说巡逻车辆ID,环卫车辆的ID,公交车,微公交的ID)。另外有一张日志表B,每条记录中的ID是来自前面那张小表的,但不是每个ID都出现在这张日志表中,比如说一天可能只有几十个ID会出现在这个日志表的当天的数据中。(比如车辆的行车轨迹数据,每秒上报轨迹,数据量就非常庞大)。那么我怎么快速的找出今天没有出现的ID

背景

有一个这样的场景,一张小表A,里面存储了一些ID,大约几百个。

(比如说巡逻车辆ID,环卫车辆的ID,公交车,微公交的ID)。

另外有一张日志表B,每条记录中的ID是来自前面那张小表的,但不是每个ID都出现在这张日志表中,比如说一天可能只有几十个ID会出现在这个日志表的当天的数据中。

(比如车辆的行车轨迹数据,每秒上报轨迹,数据量就非常庞大)。

那么我怎么快速的找出今天没有出现的ID呢。

(哪些巡逻车辆没有出现在这个片区,是不是偷懒了?哪些环卫车辆没有出行,哪些公交或微公交没有出行)?

select id from A where id not in (select id from B where time between ? and ?);

这个QUERY会很慢,有什么优化方法呢。

当然,你还可以让车辆签到的方式来解决这个问题,但是总有未签到的,或者没有这种设计的时候,那么怎么解决呢?

优化方法

其实方法也很精妙,和我之前做的两个CASE很相似。

在B表中,其实ID的值是很稀疏的,只是由于是流水,所以总量大。

优化的手段就是对B的取值区间,做递归的收敛查询,然后再做NOT IN就很快了。

例子

建表

create table a(id int primary key, info text);

create table b(id int primary key, aid int, crt_time timestamp);
create index b_aid on b(aid);

插入测试数据

-- a表插入1000条
insert into a select generate_series(1,1000), md5(random()::text);

-- b表插入500万条,只包含aid的500个id。
insert into b select generate_series(1,5000000), generate_series(1,500), clock_timestamp();

优化前的性能

\timing

explain (analyze,verbose,timing,costs,buffers) select * from a where id not in (select aid from b); 


                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on public.a  (cost=0.00..67030021.50 rows=500 width=37) (actual time=2932.080..618776.881 rows=500 loops=1)
   Output: a.id, a.info
   Filter: (NOT (SubPlan 1))
   Rows Removed by Filter: 500
   Buffers: shared hit=27037, temp read=4264454 written=8545
   SubPlan 1
     ->  Materialize  (cost=0.00..121560.00 rows=5000000 width=4) (actual time=0.002..298.049 rows=2500125 loops=1000)
           Output: b.aid
           Buffers: shared hit=27028, temp read=4264454 written=8545
           ->  Seq Scan on public.b  (cost=0.00..77028.00 rows=5000000 width=4) (actual time=0.009..888.427 rows=5000000 loops=1)
                 Output: b.aid
                 Buffers: shared hit=27028
 Planning time: 0.969 ms
 Execution time: 618794.299 ms
(14 rows)

另外你有一种选择是使用outer join, b表同样需要全扫一遍,有很大的改进,不过还可以更好,继续往后看。

postgres=# explain (analyze,verbose,timing,costs,buffers) select a.id from a left join b on (a.id=b.aid) where b.* is null;
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 Hash Right Join  (cost=31.50..145809.50 rows=25000 width=4) (actual time=2376.777..2376.862 rows=500 loops=1)
   Output: a.id
   Hash Cond: (b.aid = a.id)
   Filter: (b.* IS NULL)
   Rows Removed by Filter: 5000000
   Buffers: shared hit=27037
   ->  Seq Scan on public.b  (cost=0.00..77028.00 rows=5000000 width=44) (actual time=0.012..1087.997 rows=5000000 loops=1)
         Output: b.aid, b.*
         Buffers: shared hit=27028
   ->  Hash  (cost=19.00..19.00 rows=1000 width=4) (actual time=0.355..0.355 rows=1000 loops=1)
         Output: a.id
         Buckets: 1024  Batches: 1  Memory Usage: 44kB
         Buffers: shared hit=9
         ->  Seq Scan on public.a  (cost=0.00..19.00 rows=1000 width=4) (actual time=0.010..0.183 rows=1000 loops=1)
               Output: a.id
               Buffers: shared hit=9
 Planning time: 0.302 ms
 Execution time: 2376.934 ms
(18 rows)

递归收敛优化后的性能

explain (analyze,verbose,timing,costs,buffers) 
select * from a where id not in 
(
with recursive skip as (  
  (  
    select min(aid) aid from b where aid is not null  
  )  
  union all  
  (  
    select (select min(aid) aid from b where b.aid > s.aid and b.aid is not null)   
      from skip s where s.aid is not null  
  )  -- 这里的where s.aid is not null 一定要加,否则就死循环了.  
)   
select aid from skip where aid is not null
);


                                                                                 QUERY PLAN                    
------------------------------------------------------------------------------------------------------
 Seq Scan on public.a  (cost=54.98..76.48 rows=500 width=37) (actual time=10.837..10.957 rows=500 loops=1)
   Output: a.id, a.info
   Filter: (NOT (hashed SubPlan 5))
   Rows Removed by Filter: 500
   Buffers: shared hit=2012
   SubPlan 5
     ->  CTE Scan on skip  (cost=52.71..54.73 rows=100 width=4) (actual time=0.042..10.386 rows=500 loops=1)
           Output: skip.aid
           Filter: (skip.aid IS NOT NULL)
           Rows Removed by Filter: 1
           Buffers: shared hit=2003
           CTE skip
             ->  Recursive Union  (cost=0.46..52.71 rows=101 width=4) (actual time=0.037..10.104 rows=501 loops=1)
                   Buffers: shared hit=2003
                   ->  Result  (cost=0.46..0.47 rows=1 width=4) (actual time=0.036..0.036 rows=1 loops=1)
                         Output: $1
                         Buffers: shared hit=4
                         InitPlan 3 (returns $1)
                           ->  Limit  (cost=0.43..0.46 rows=1 width=4) (actual time=0.031..0.032 rows=1 loops=1)
                                 Output: b_1.aid
                                 Buffers: shared hit=4
                                 ->  Index Only Scan using b_aid on public.b b_1  (cost=0.43..131903.43 rows=5000000 width=4) (actual time=0.030..0.030 rows=1 loops=1)
                                       Output: b_1.aid
                                       Index Cond: (b_1.aid IS NOT NULL)
                                       Heap Fetches: 1
                                       Buffers: shared hit=4
                   ->  WorkTable Scan on skip s  (cost=0.00..5.02 rows=10 width=4) (actual time=0.019..0.019 rows=1 loops=501)
                         Output: (SubPlan 2)
                         Filter: (s.aid IS NOT NULL)
                         Rows Removed by Filter: 0
                         Buffers: shared hit=1999
                         SubPlan 2
                           ->  Result  (cost=0.47..0.48 rows=1 width=4) (actual time=0.018..0.018 rows=1 loops=500)
                                 Output: $3
                                 Buffers: shared hit=1999
                                 InitPlan 1 (returns $3)
                                   ->  Limit  (cost=0.43..0.47 rows=1 width=4) (actual time=0.017..0.017 rows=1 loops=500)
                                         Output: b.aid
                                         Buffers: shared hit=1999
                                         ->  Index Only Scan using b_aid on public.b  (cost=0.43..66153.48 rows=1666667 width=4) (actual time=0.017..0.017 rows=1 loops=500)
                                               Output: b.aid
                                               Index Cond: ((b.aid > s.aid) AND (b.aid IS NOT NULL))
                                               Heap Fetches: 499
                                               Buffers: shared hit=1999
 Planning time: 0.323 ms
 Execution time: 11.082 ms
(46 rows)

采用收敛查询优化后,耗时从最初的 618794毫秒 降低到了 11毫秒 ,感觉一下子节约了好多青春。

目录
相关文章
|
NoSQL 数据可视化 Redis
redis数据导出和导入最佳实践
redis的导出和导入,有可视化工具,也有命令行工具,这里,我们当然是要选可视化工具,命令行工具安装也麻烦,使用也麻烦,直接pass掉。 这里我是推荐使用yunedit-redis来做导出和导入,其一是因为它的导出和导入非常类型,可以导出整库、还可以导出单个db,还可以只导出几个key、甚至可以使用它的自定义导出功能根据正侧表达式导出符合规则的key的数据
|
7月前
|
机器学习/深度学习 人工智能 物联网
刷屏背后的 AI画图:80% 可以被替代,剩下 20% 才是核心价值
刷屏背后的 AI画图:80% 可以被替代,剩下 20% 才是核心价值
|
机器学习/深度学习 编解码 人工智能
InternVL3.5多模态大模型开源发布,1B-241B九种尺寸,支持跨平台GUI自动化与矢量图生成
近日,上海人工智能实验室(上海AI实验室)重磅开源发布了多模态大模型书生·万象 InternVL3.5,通过创新的级联式强化学习(Cascade RL)、动态视觉分辨率路由与解耦部署架构,实现推理能力、部署效率与通用能力的全面升级。
3235 7
|
10月前
|
人工智能 自然语言处理 数据可视化
不用懂代码,需求提完就落地?AI+低代码的底层逻辑,只有这3条路
AI+低代码平台成主流趋势,通过“精准识别需求、自动搭建逻辑、灵活适配优化”三大路径,助力企业高效落地数字化转型。以AI赋能非技术人员,打破沟通壁垒,实现业务系统快速构建与持续演进,真正提升组织协作效率与竞争力。
|
算法 安全 Python
【继电保护】小电流接地系统故障仿真-中性点不接地与经消弧线圈接地仿真模型(Simulink仿真实现)
【继电保护】小电流接地系统故障仿真-中性点不接地与经消弧线圈接地仿真模型(Simulink仿真实现)
681 2
|
分布式计算 Java 关系型数据库
二、Sqoop 详细安装部署教程
在大数据开发实战中,Sqoop 是数据库与 Hadoop 生态之间不可或缺的数据传输工具。这篇文章将以 Sqoop 1.4.7 为例,结合官方站点截图,详细讲解 Sqoop 的下载路径、安装步骤、环境配置,以及常见 JDBC 驱动的准备过程,帮你一步步搭建出能正常运行的 Sqoop 环境,并通过 list-databases 命令验证安装是否成功。如果你正打算学习 Sqoop,或者在搭建大数据平台过程中遇到安装配置问题,本文将是非常实用的参考指南。
1209 6
|
存储 人工智能 搜索推荐
拔俗AI助教系统:基于大模型与智能体架构的新一代教育技术引擎
AI助教融合大语言模型、教育知识图谱、多模态感知与智能体技术,重构“教、学、评、辅”全链路。通过微调LLM、精准诊断错因、多模态交互与自主任务规划,实现个性化教学。轻量化部署与隐私保护设计保障落地安全,未来将向情感感知与教育深度协同演进。(238字)
1448 0
|
IDE Java Maven
快速构建第一个Flink工程
本文简述通过maven和gradle快速构建的Flink工程。建议安装好Flink以后构建自己的Flink项目,安装与示例运行请查看:Flink快速入门--安装与示例运行. 在安装好Flink以后,只要快速构建Flink工程,并完成相关代码开发,就可以轻松入手Flink。
725 0
快速构建第一个Flink工程
|
人工智能 自然语言处理 算法
AIGC 在数字三维模型制作
生成式人工智能(AIGC)正革新数字三维模型制作,提升效率并拓宽创意边界。本文探讨其应用、影响及前景。
|
Windows
油猴脚本(篡改猴)获取某度网盘链接
本文档介绍如何安装及使用Tampermonkey(油猴)测试版插件来增强浏览器功能,并配合aria2c下载工具实现高效下载。首先需从官方或可靠来源下载油猴测试版并确保移除原有正式版以避免冲突。接着安装aria2c至系统目录使全局可用。利用特定油猴脚本如“网盘直链下载助手”,可以将网盘文件转换为直接下载链接,再通过桌面快捷方式打开PowerShell执行aria2c下载。文档还推荐了一些实用脚本,例如“懒人工具箱”,并提供了获取链接。通过这些步骤,用户能够显著提升日常浏览体验及资源下载效率。
油猴脚本(篡改猴)获取某度网盘链接