【问题标题】:Postgres query is very slow when using a parameter instead of an hardcoded string使用参数而不是硬编码字符串时,Postgres 查询非常慢
【发布时间】:2012-06-05 05:29:32
【问题描述】:

我遇到了这个 Postgres 问题,如果我使用参数而不是在查询字符串上硬编码它的值,则相同的查询需要很长时间才能执行。列名是“media_type”,它是一个 VARCHAR(20)。我使用 Symfony2 和 Doctrine2 ORM 从 PHP 运行这些查询,并且有问题的表有大约 1.000.000 条记录。

我的查询有问题吗?会不会是 Postgres 配置问题?

1 - media_type 的硬编码值

duration: 5.365 ms  parse pdo_stmt_00000001: SELECT id,site_id FROM item where media_type = 'Collection' AND enabled = 'true' AND site_id = $1 AND user_id = $2 ORDER BY id DESC LIMIT $3 OFFSET $4
duration: 0.142 ms  bind pdo_stmt_00000001: SELECT id,site_id FROM item where media_type = 'Collection' AND enabled = 'true' AND site_id = $1 AND user_id = $2 ORDER BY id DESC LIMIT $3 OFFSET $4
parameters: $1 = '1', $2 = '1', $3 = '100', $4 = '0'
duration: 8.667 ms  execute pdo_stmt_00000001: SELECT id,site_id FROM item where media_type = 'Collection' AND enabled = 'true' AND site_id = $1 AND user_id = $2 ORDER BY id DESC LIMIT $3 OFFSET $4
parameters: $1 = '1', $2 = '1', $3 = '100', $4 = '0'

执行计划:

duration: 8.640 ms  plan:
    Query Text: SELECT id,site_id FROM item where media_type = 'Collection' AND enabled = 'true' AND site_id = $1 AND user_id = $2 ORDER BY id DESC LIMIT $3 OFFSET $4
    Limit  (cost=8.38..8.38 rows=1 width=12) (actual time=8.516..8.595 rows=24 loops=1)
      Buffers: shared hit=10 read=15
        ->  Sort  (cost=8.38..8.38 rows=1 width=12) (actual time=8.505..8.530 rows=24 loops=1)
            Sort Key: id
            Sort Method: quicksort  Memory: 26kB
            Buffers: shared hit=10 read=15
            ->  Index Scan using item_media_type_index on item  (cost=0.00..8.37 rows=1 width=12) (actual time=7.955..8.397 rows=24 loops=1)
                    Index Cond: ((media_type)::text = 'Collection'::text)
                    Filter: (enabled AND (site_id = $1) AND (user_id = $2))
                    Buffers: shared hit=8 read=15

2 - 使用 media_type 的参数(SLOWER)

duration: 5.557 ms  parse pdo_stmt_00000001: SELECT id,site_id FROM item where media_type = $1 AND enabled = 'true' AND site_id = $2 AND user_id = $3 ORDER BY id DESC LIMIT $4 OFFSET $5
duration: 1.322 ms  bind pdo_stmt_00000001: SELECT id,site_id FROM item where media_type = $1 AND enabled = 'true' AND site_id = $2 AND user_id = $3 ORDER BY id DESC LIMIT $4 OFFSET $5
parameters: $1 = 'Collection', $2 = '1', $3 = '1', $4 = '100', $5 = '0'
duration: 71564.998 ms  execute pdo_stmt_00000001: SELECT id,site_id FROM item where media_type = $1 AND enabled = 'true' AND site_id = $2 AND user_id = $3 ORDER BY id DESC LIMIT $4 OFFSET $5
parameters: $1 = 'Collection', $2 = '1', $3 = '1', $4 = '100', $5 = '0'

执行计划:

duration: 71564.922 ms  plan:
    Query Text: SELECT id,site_id FROM item where media_type = $1 AND enabled = 'true' AND site_id = $2 AND user_id = $3 ORDER BY id DESC LIMIT $4 OFFSET $5
    Limit  (cost=90663.16..181326.31 rows=17184 width=12) (actual time=3.667..71564.864 rows=24 loops=1)
      Buffers: shared hit=183786 read=96585
        ->  Index Scan Backward using item_pkey on item  (cost=0.00..906610.46 rows=171836 width=12) (actual time=3.655..71564.798 rows=24 loops=1)
               Filter: (enabled AND ((media_type)::text = $1) AND (site_id = $2) AND (user_id = $3))
               Buffers: shared hit=183786 read=96585

提前致谢。

【问题讨论】:

    标签: postgresql symfony doctrine-orm postgresql-9.1 sql-execution-plan


    【解决方案1】:

    在您的静态、更快的查询中,正在使用 item_media_type_index。 在您绑定的较慢查询中,未使用 item_media_type_index。

    “item”表中“media_type”列的选择性是什么?如果你做了:

    SELECT media_type, COUNT(*)
      FROM item
      GROUP BY media_type
      ORDER BY 2 desc
    

    media_types 是否均匀平衡,或者与其他媒体类型相比,'Collection' media_types 是否相对较少?如果“Collection”项目相对较少,那么我会冒险猜测:使用静态查询,解析器知道您正在查询“Collection”,并且可以确定“Collection”的计数较低并且索引可能值得使用。但是在绑定变量的情况下,解析器不知道您使用的是哪个 media_type。其他一些 media_type 值可能占表中记录的很大比例(例如 20%)。在这种情况下,进行扫描甚至比使用索引还要麻烦。解析器需要做出决定,并且恰好决定不使用索引(对于您的情况是错误的,但对于其他 media_type 可能是正确的)。这只是基于其他一些 rdbms 工作方式的猜测。

    在这种情况下,如果您知道选择性属性非常偏斜,则答案是硬编码,使用动态 sql 强制延迟解析,或者如果您认为正确,则强制使用索引。

    【讨论】:

      【解决方案2】:

      这是 PostgreSQL 中长期存在的一个问题,过去需要一些有趣的规划器调整才能解决。它已在 PostgreSQL 9.2(现在处于测试版)中得到修复,不过一如既往地感谢 Tom Lane。

      E.1.3.1.3。优化器

      提高计划者选择参数化计划的能力(Tom 车道)

      准备好的语句现在被解析、分析和重写,但不是 必然计划。当准备好的计划执行时 参数,规划器可能会为每个常数重新规划它,或者它 如果其成本接近于 常量特定计划。

      请参阅 9.2 beta release notesquick note I wrote about this on lwn.net。在mailing lists 上有很多关于处理准备好的/参数化的语句运行速度比正常语句慢的信息。

      【讨论】:

      • 谢谢! PostgreSQL 9.2 有预期的发布日期吗?我没有运气就寻找它。另外,PostgreSQL 测试版通常有多稳定?
      • @luis 我尽可能为我的开发工作运行 PostgreSQL 测试版,并且从未遇到过问题。但是,我没有在生产中使用它们。如果您仍处于开发阶段,那么使用测试版是轻而易举的事。对于生产 - 好吧,Pg 总体上非常稳定,虽然 存在 非常偶然的危险错误,但由于回归测试,它们通常处于奇怪的极端情况。我不能建议你在生产环境中运行测试版——它是测试版是有原因的,团队认为它还没有准备好——但如果我必须解决一个关键问题,我会这样做。如果可能,请解决并等待发布。
      • @luis PostgreSQL 9.2 今天发布了,仅供参考。
      【解决方案3】:

      当我绑定到一个 SMALLINT 字段并传入一个 Postgres 隐式从 INTEGER 转换为 SMALLINT 的值时,我遇到了一个非常类似的问题。我通过明确演员来修复它。由于 media_type 是 VARCHAR(20) 类型,Postgres 正在从 TEXT 类型进行隐式转换。试试这个:

      where media_type = $1::VARCHAR(20)

      【讨论】:

      • 这看起来值得一试——尽管它也在更有效的索引查找计划中进行转换。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多