我在PostgreSQL v13.10中运行了以下SQL:

WITH stuckable_statuses AS (
  SELECT status_id FROM status_descriptions
  WHERE (tags @> ARRAY['stuckable']::varchar[])
)

SELECT jobs.* FROM jobs
WHERE jobs.status = ANY(select status_id from stuckable_statuses)

当用ID数组EX替换ANY(select status_id from stuckable_statuses)时,运行速度非常慢.(1,2,3)真的跑得很快.

下面是该查询的解释分析:

Gather  (cost=1005.64..5579003.45 rows=1563473 width=2518) (actual time=45.495..40138.515 rows=303 loops=1)
  Workers Planned: 2
  Workers Launched: 2
  ->  Hash Semi Join  (cost=5.64..5421656.15 rows=651447 width=2518) (actual time=44.533..40126.793 rows=101 loops=3)
        Hash Cond: (jobs.status = status_descriptions.status_id)
        ->  Parallel Seq Scan on jobs  (cost=0.00..5378777.15 rows=13571815 width=2518) (actual time=0.892..38662.091 rows=10537079 loops=3)
        ->  Hash  (cost=5.56..5.56 rows=6 width=4) (actual time=0.377..0.378 rows=11 loops=3)
              Buckets: 1024  Batches: 1  Memory Usage: 9kB
              ->  Seq Scan on status_descriptions  (cost=0.00..5.56 rows=6 width=4) (actual time=0.310..0.370 rows=11 loops=3)
                    Filter: (tags @> '{stuckable}'::character varying[])
                    Rows Removed by Filter: 146
Planning Time: 0.711 ms
Execution Time: 40138.654 ms

以下是表格定义(摘自Rails‘schema.rb):


  create_table "jobs", id: :serial, force: :cascade do |t|
    t.string "filename"
    t.string "sandbox"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "status", default: 0, null: false
    t.integer "provider_id"
    t.integer "lang_id"
    t.integer "profile_id"
    t.datetime "extra_date"
    t.datetime "main_date"
    t.datetime "performer_id"
    t.index ["provider_id", "status", "extra_date"], name: "jobs_on_media_provider_id__status__extra_date"
    t.index ["provider_id", "status", "main_date"], name: "jobs_on_media_provider_id_and_status_and_due_date"
    t.index ["profile_id", "status", "extra_date"], name: "index_jobs_on_profile_id__status__extra_date"
    t.index ["profile_id", "status", "main_date"], name: "index_transcription_jobs_on_profile_id_and_status_and_due_date"
    t.index ["status", "sandbox", "lang_id", "extra_date"], name: "index_jobs_on_status__sandbox__lang_id__extra_date"
    t.index ["status", "sandbox", "lang_id", "main_date"], name: "index_jobs_on_status_and_sandbox_and_lang_id_and_due_date"
    t.index ["performer_id", "status", "extra_date"], name: "index_jobs_on_performer_id__status__extra_date"
    t.index ["performer_id", "status", "main_date"], name: "index_jobs_on_performer_id_and_status_and_due_date"
  end

  create_table "status_descriptions", id: :serial, force: :cascade do |t|
    t.integer "status_id"
    t.string "title"
    t.string "tags", array: true
    t.index ["status_id"], name: "index_status_descriptions_on_status_id"
  end

与使用ARRAY的相同SQL相比,我可以看到它没有使用INDEX BY JOBS.STATUS,这可能是因为JOBS表非常大(大约15kk行),而STATUS_DESCRIPTIONSION大约有200行.

如果可能的话,你能帮我优化一下这个SQL吗?

谢谢!

最新情况:

以下是具有硬编码数组的查询:

SELECT jobs.* FROM transcription_jobs
WHERE jobs.status IN (2, 3, 4, 291, 290, 46, 142, 260, 6, 7, 270)

下面是它的解释分析:

Index Scan using index_jobs_on_status__sandbox__lang_id__current_stage_due_date on jobs  (cost=0.56..98661.05 rows=26541 width=2518) (actual time=0.032..63.266 rows=483 loops=1)
  Index Cond: (status = ANY ('{2,3,4,291,290,46,142,260,6,7,270}'::integer[]))
Planning Time: 0.356 ms
Execution Time: 63.337 ms

推荐答案

主要问题是,它认为将找到1563473行,但实际上找到了303行.如果实际上找到了1563473行,那么序列扫描上的散列连接可能真的会比任何驱动的索引扫描更快.

不幸的是,对于您当前的数据模型和现有的PostgreSQL版本,您可能无法对此做任何事情.看起来,在职务表中,可堆叠的状态比不可堆叠的状态要少得多,但规划者无法知道这一点.

要强制执行更快的计划,可以在运行此查询之前临时关闭Enable_hashJoin或Enable_seqcan.这绝对是一个丑陋的解决方案,但它应该是一个可靠的解决方案.如果关闭并行查询(设置max_parally_Worker_per_gather=0),might就足以将计划切换到速度更快计划.如果您无论如何都不能从并行查询中获得太多好处,那么这将是一个不那么难看的解决方案,但也不太可靠.或者,你可以试着加plan hints.

最健壮的解决方案可能就是将其作为两个查询运行,在一个查询中获取状态id的数组/列表,然后将该数组/列表填充到第二个查询中.这样,规划者可以实际看到要使用的值,并可以相应地进行计划.(我注意到,您的硬编码计划仍然被严重误估,但估计错误的程度远不及其他计划,也不足以推动计划的 Select .这可能是一个单独的问题,与您当前面临的问题无关.)

对于您的数据模型,我的直觉是,给定状态的粘性不会经常改变,如果永远不会改变的话.如果您可以将该值直接记录为JOBS表中的新列,并在状态本身发生变化时对其进行更改,而不是需要对单独的表进行间接操作,那么几乎可以立即解决这个问题.

Sql相关问答推荐

获取每个帖子的匹配关键字列表

如何在幂函数中正确使用Power()和Exp()

使用占位符向SQL INSERT查询添加 case

Oracle SQL根据列中的条件 Select 最大记录数

有没有办法在Postgres中存储带有时区的时间戳,而不将其转换为UTC

将结果从一列转换为两行或更多

在SELECT中将日期格式转换为双周时段

优化Postgres搜索未知长度的子串

从JSON值数组创建扁平数组Athena

SQL按组 Select 最小值,当值不存在时为Null

Postgresql - WHERE 中的 MAX 标准 - 初学者问题

显示所有组并计算给定组中的项目(包括 0 个结果)

统计重复记录的总数

将最近的结束日期与开始日期相匹配

如何根据创建日期查找两个表中最接近的记录?

如何解释 SQL Server 中的 Foxpro 语法?

REGEXP_SUBSTR使用方法

自动生成计算频率的列

使用日期和间隔作为键加入 Athena 上的表?

在sql server中创建唯一标识符列