sysdate() 导致 Postgres 忽略索引并进行昂贵的顺序扫描

Posted

技术标签:

【中文标题】sysdate() 导致 Postgres 忽略索引并进行昂贵的顺序扫描【英文标题】:sysdate() causes Postgres to do ignore the Index and do a costly Sequential Scan 【发布时间】:2020-01-15 15:42:22 【问题描述】:

有人遇到过这种情况吗? Postgres 企业数据库高级服务器 11.5.12

sysdate()(Oracle 专有)导致对 4,782 行的 Seq 扫描:

EXPLAIN SELECT p.id, p.practice
 FROM PatientStatistics ps
 INNER JOIN Patients p
   ON p.id=ps.patient
 WHERE ps.nextfutureapptdateservertime <= sysdate()
 ORDER BY p.id ASC;

Hash Join  (cost=799.81..1761.53 rows=4782 width=8)
   Hash Cond: (p.id = ps.patient)
   ->  Index Only Scan using patients_index3 on patients p  (cost=0.29..921.44 rows=15442 width=8)
   ->  Hash  (cost=644.11..644.11 rows=4782 width=4)
         ->  Seq Scan on patientstatistics ps  (cost=0.00..644.11 rows=4782 width=4)
               Filter: (nextfutureapptdateservertime <= sysdate)

更改为 now()current_timestamp(SQL 标准)可解决此问题。 Postgres 正确使用索引:

EXPLAIN SELECT p.id, p.practice
FROM PatientStatistics ps
INNER JOIN Patients p
   ON p.id=ps.patient
WHERE ps.nextfutureapptdateservertime <= now()
ORDER BY p.id ASC;

Nested Loop  (cost=0.57..51.41 rows=17 width=8)
   ->  Index Only Scan using "patientstatisti_idx$$_0c9a0048" on patientstatistics ps  (cost=0.29..8.53 rows=17 width=4)
         Index Cond: (nextfutureapptdateservertime <= now())
   ->  Index Scan using patients_pk on patients p  (cost=0.29..2.52 rows=1 width=8)
         Index Cond: (id = ps.patient)

有趣的是要注意这些函数的输出不同:

SELECT now();
SELECT current_timestamp;

15-JAN-20 09:36:41.932741 -05:00
15-JAN-20 09:36:41.932930 -05:00

SELECT sysdate();

15-JAN-20 09:37:17

也许 Postgres 的日期索引使用具有小数部分的日期时间进行散列。规划器看到它通过了一个没有小数的日期,并且它知道索引的键不会准确排列,因此它退回到扫描以确保查询提供 100% 准确的结果。

在 30 分钟的谷歌搜索后,我在网上找不到任何关于此的内容。

【问题讨论】:

sysdate() 的稳定性如何? current_timestamp 和 now() (它们是相同的)是稳定的,因为它们不会在事务中更改。 sysdate() 一样吗? 当您使用 Enterprise Advanced Server 时,您应该与他们签订支持合同 - 我会将其作为错误提交。 【参考方案1】:

我不知道 EDB 的专有 fork,所以以下内容基于猜测。

now() 或(等效地)current_timestamp 是一个 STABLE 函数,因此如果在语句执行过程中(实际上是在事务中)多次评估它,它会返回相同的值。

怀疑是sysdate,就像PostgreSQL的clock_timestamp()一样,是VOLATILE(返回实际时间)。

那么函数每次与行比较时可以有不同的值,这使得无法使用索引扫描。

如果我的怀疑不正确,我会称之为 EDB 错误。

【讨论】:

即使标记为 volatile,我的 DIY 函数也会导致索引扫描。 @wildplasser 是内联的 SQL 函数吗? with clock_timestamp() 我确实得到了不希望的行为,正如预期的那样。 没错。你的 DIY 函数不是 VOLATILE. 否,但波动性确实影响now() [可能是因为它是内联sql]【参考方案2】:

我不知道他们是如何实现的,但是这个解决方法在这里可以正常工作:


CREATE OR REPLACE FUNCTION mysysdate(OUT timestamptz)
AS
$func$
select now();
$func$
language sql stable;

select mysysdate() ;

EXPLAIN select *
FROM public.feature_timeslice
WHERE valid_time_begin < mysysdate() - '10 year + 14 days'::interval;

select version() ;
\df+ mysysdate

输出:


CREATE FUNCTION
           mysysdate           
-------------------------------
 2020-01-15 17:15:13.896497+01
(1 row)

                                              QUERY PLAN                                               
-------------------------------------------------------------------------------------------------------
 Index Scan using feature_timeslice_alt2 on feature_timeslice  (cost=0.42..4474.84 rows=9206 width=28)
   Index Cond: (valid_time_begin < (now() - '10 years 14 days'::interval))
(2 rows)

                                                version                                                
-------------------------------------------------------------------------------------------------------
 PostgreSQL 11.3 on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 4.8.4-2ubuntu1~14.04.4) 4.8.4, 64-bit
(1 row)

                                                                                       List of functions
 Schema |   Name    |     Result data type     |     Argument data types      | Type | Volatility | Parallel |  Owner   | Security | Access privileges | Language |  Source code  | Description 
--------+-----------+--------------------------+------------------------------+------+------------+----------+----------+----------+-------------------+----------+---------------+-------------
 tmp    | mysysdate | timestamp with time zone | OUT timestamp with time zone | func | stable     | unsafe   | postgres | invoker  |                   | sql      |              +| 
        |           |                          |                              |      |            |          |          |          |                   |          | select now();+| 
        |           |                          |                              |      |            |          |          |          |                   |          |               | 
(1 row)

注意:粒度不影响查询计划,

select date_trunc('sec', now());

也会导致索引扫描。

【讨论】:

【参考方案3】:

是的。这一定是波动性的事情。 PG关于此事的文档。 https://www.postgresql.org/docs/8.2/xfunc-volatility.html

他们将“timeofday()”显示为 Volatile 的示例。

now() - STABLE - 此查询开始的时间。在同一个查询中调用它 6 次,它返回相同的时间。 timeofday() 和 sysdate() - VOLATILE - time function() 被调用时的时间;不是查询。这就像炮轰操作系统的date 工具。在同一个查询中调用 6 次,您将得到 6 次不同的结果。

【讨论】:

以上是关于sysdate() 导致 Postgres 忽略索引并进行昂贵的顺序扫描的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Mysql 时间戳转换为 sysdate(6) 格式(以毫秒为单位)?

自定义窗口函数忽略 Postgres 中的空值

Postgres:删除属于特定角色的所有表(或强制删除角色,忽略依赖对象)

强制Oracle的sysdate为多个语句返回不同的值

如何在 9.414 版本中使用 executemany 忽略 postgres 中的重复项?

Postgres 忽略表约束,即使约束不匹配也会扫描所有继承的表