PostgreSQL 忽略索引,运行 seq 扫描

And*_*sov 5 postgresql index datatypes postgresql-performance

我的表包含列的索引total_balance

\d balances_snapshots
                                          Table "public.balances_snapshots"
    Column     |            Type             | Collation | Nullable |                    Default
---------------+-----------------------------+-----------+----------+------------------------------------------------
 user_id       | integer                     |           |          |
 asset_id      | text                        |           |          |
 timestamp     | timestamp without time zone |           |          | now()
 total_balance | numeric                     |           | not null |
 id            | integer                     |           | not null | nextval('balances_snapshots_id_seq'::regclass)
Indexes:
    "balances_snapshots_pkey" PRIMARY KEY, btree (id)
    "balances_snapshots_asset_id_idx" btree (asset_id)
    "balances_snapshots_timestamp_idx" btree ("timestamp")
    "balances_snapshots_user_id_idx" btree (user_id)
    "balances_total_balance_idx" btree (total_balance)
Foreign-key constraints:
    "balances_snapshots_asset_id_fkey" FOREIGN KEY (asset_id) REFERENCES assets(id) ON UPDATE CASCADE ON DELETE CASCADE
    "balances_snapshots_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
Run Code Online (Sandbox Code Playgroud)

简单的查询适用于 seq 扫描

explain analyze SELECT EXISTS (
  SELECT
    1
  FROM
    balances_snapshots
  WHERE
    total_balance = double precision 'NaN'
  LIMIT 1
) as exists;
                                                                QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------
 Result  (cost=4.75..4.76 rows=1 width=1) (actual time=237365.680..237365.681 rows=1 loops=1)
   InitPlan 1 (returns $0)
     ->  Seq Scan on balances_snapshots  (cost=0.00..9257326.32 rows=1948181 width=0) (actual time=237365.675..237365.676 rows=0 loops=1)
           Filter: ((total_balance)::double precision = 'NaN'::double precision)
           Rows Removed by Filter: 389636289
 Planning Time: 23.985 ms
 Execution Time: 237365.719 ms
(7 rows)
Run Code Online (Sandbox Code Playgroud)

如何让 PostgreSQL 使用索引?或者换句话说,是否有更有效的方法来扫描存在 NaN 值的表?

Sta*_*sev 10

问题是 PG 必须将原始数字转换为双精度:

Filter: ((total_balance)::double precision = ...
Run Code Online (Sandbox Code Playgroud)

因此,构建的索引包含数字,但您需要双精度 - 因此无法使用索引。您需要避免转换列中的值:

  WHERE
    total_balance = 'NaN'::numeric
Run Code Online (Sandbox Code Playgroud)

PS:在数据库中看到 NaN 很奇怪。为什么不存储空值?

  • 数据库服务于遗留的 JavaScript 应用程序,预计会在逻辑失败时插入 NaN。数据库不应因此类尝试而失败,以免使此遗留应用程序崩溃。 (2认同)

Erw*_*ter 9

精准解释

Postgres 有一个 forfloat8 = float8和 for运算符numeric = numeric。但不适合numeric = float8。必须强制转换一个操作数。

double precision,又名float8是数字类型中的“首选”数据类型。请参阅pg_type.typispreferred

运算符类型解析最终在第 3.d段中决定。

遍历所有候选者并保留接受首选类型的候选者

float8 = float8获胜。您的索引是基于运算符类构建的,numeric并且不适用。砰。

解决方案

double precision将查询中的强制转换替换为更合理的强制转换,匹配Stanislav 已经建议的numeric类型。total_balance

或者只是使用无类型文字'NaN'而不进行显式强制转换。total_balance这会自动解析为作业中的类型。看:

优化方案

是否有更有效的方法来扫描存在 NaN 值的表?

如果这是您查询的重点,那么部分索引对于您的情况会更有效:

CREATE INDEX balances_total_balance_nan_idx ON balances_snapshots ((true))
WHERE total_balance = 'NaN';
Run Code Online (Sandbox Code Playgroud)

实际的索引表达式并不重要。我用了一个常数。看:

你的EXPLAIN输出报告接近 4 亿行 ( 389636289),但实际上没有一行具有total_balance = 'NaN'( rows=0)。建议的部分索引的最小大小为 8 kB,而不是完整索引的约 10 GB(?),几乎没有任何写入成本,并且使查询几乎可以即时运行。

(您是否还需要所有其他现有索引?)

中的表达式为WHERE必须与查询中使用的表达式匹配。如果查询中奇怪的类型不匹配有充分的理由(?),请执行以下操作:

...
WHERE total_balance = float8 'NaN'
Run Code Online (Sandbox Code Playgroud)

旁白:浪费存储空间

说到这里,像这样对表列重新排序将节省相当多 MB 的存储空间和 RAM:

                                          Table "public.balances_snapshots"
    Column     |            Type             | Collation | Nullable |                    Default
---------------+-----------------------------+-----------+----------+------------------------------------------------
 id            | integer                     |           | not null | nextval('balances_snapshots_id_seq'::regclass)
 user_id       | integer                     |           |          |
 total_balance | numeric                     |           | not null |
 asset_id      | text                        |           |          |
 timestamp     | timestamp without time zone |           |          | now()
Run Code Online (Sandbox Code Playgroud)

看: