为什么 PostgreSQL 在将数值与 bigint 列进行比较时执行 seq 扫描?

Mil*_*son 4 postgresql performance execution-plan postgresql-9.3 query-performance

给定一个vp具有列timestamp类型的表bigint和一个btree索引timestamp,为什么 Postgres 会忽略索引并在timestamp与浮点值比较时运行 seq 扫描,当索引扫描会产生相同的结果时?

整数比较:

SELECT * FROM vp WHERE vp.timestamp > 1470752584需要48 毫秒

 在 vp 上使用 vp_ts_idx 进行索引扫描(cost=0.57..257.87 rows=2381 width=57)(实际时间=0.014..38.669 rows=80323 loops=1)
   索引条件:(“时间戳”> 1470752584)
 总运行时间:48.322 毫秒

数值比较:

SELECT * FROM vp WHERE vp.timestamp > 1470752584.1需要103 秒,因为它忽略vp_ts_idx并执行整个表的 seq 扫描:

  vp 上的 Seq Scan (cost=0.00..7378353.16 rows=95403915 width=57) (实际时间=62625.420..103122.701 rows=98240 loops=1)
   过滤器:(("timestamp")::numeric > 1470752584.1)
   过滤器删除的行:285945491
 总运行时间:103134.333 毫秒

背景:相比于最近的车辆位置查询timestampEXTRACT(EPOCH FROM NOW()) - %s,其中%s为所需的秒数,但没有明确铸造到bigint。解决方法是使用CAST(EXTRACT(EPOCH FROM NOW()) - %s AS bigint).

当列类型为 时,为什么查询计划器不会自动执行此操作bigint?这是一个错误,还是我没有考虑这种行为有用的一些边缘情况?

dez*_*zso 6

关键是你不要比较相同的类型。将 abigint与 a进行比较时numeric,更简单的方法是“扩展”bigint小数位为 0(如 1 -> 1.0),而反之则意味着四舍五入/截断。(在这种特定情况下,很容易看出两者导致相同的结果,但如果值为负呢?)

那么,是什么在您的比较得到的,是numericnumeric比较,这是不是一个bigint指数可以食用。

值得一看的是,哪些类型转换是可能的,哪些是在这些情况下执行的。为此,这里有两行pg_cast

SELECT castsource::regtype, casttarget::regtype, castcontext 
  FROM pg_cast 
 WHERE castsource::regtype = 'bigint'::regtype
   AND casttarget::regtype = 'numeric'::regtype;

 castsource ? casttarget ? castcontext 
???????????????????????????????????????
 bigint     ? numeric    ? i

SELECT castsource::regtype, casttarget::regtype, castcontext
  FROM pg_cast
 WHERE castsource::regtype = 'numeric'::regtype
   AND casttarget::regtype = 'bigint'::regtype;

 castsource ? casttarget ? castcontext 
???????????????????????????????????????
 numeric    ? bigint     ? a
Run Code Online (Sandbox Code Playgroud)

根据链接的文档页面, castcontext

指示可以在哪些上下文中调用强制转换。e仅表示显式强制转换(使用CAST::语法)。a意味着隐式分配给目标列,以及显式。i意味着隐含在表达式中,以及其他情况。

因此,这意味着numeric->bigint方向仅在您将前者分配给后者时才“自行”发生(即您没有显式调用其中一个转换运算符)。在像您的比较这样的表达式中,情况并非如此,因此解析器只会考虑另一种方式(用i上面标记)。这意味着,除非您以其他方式强制numeric进行numeric比较,否则您可以进行比较。

笔记:

  • psql 是 PostgreSQL 的命令行客户端,它本身不做任何这些事情(我已经相应地编辑了标题)
  • 使用关键字作为列名(如timestamp)从来都不是一个好主意 - 你可能会在这里和那里遇到意想不到的解析错误,除非你足够小心,在任何地方都用双引号引起来
  • 使用 Unix 纪元作为时间戳可能很麻烦。PostgreSQL 中的“真实”时间戳具有丰富的功能 - 在大多数情况下,使用它要容易得多。