我可以做些啥来优化适用于 Postgres 和 MySQL 的 SQL 查询?

Posted

技术标签:

【中文标题】我可以做些啥来优化适用于 Postgres 和 MySQL 的 SQL 查询?【英文标题】:What can I do to optimize my SQL query that applies to both Postgres and MySQL?我可以做些什么来优化适用于 Postgres 和 MySQL 的 SQL 查询? 【发布时间】:2020-09-06 02:05:28 【问题描述】:

我的表 DDL 是:

CREATE TABLE accounts (
    id serial NOT NULL,
    active bool NULL DEFAULT true,
    created_at timestamp NULL,
    organization_id NULL,
    CONSTRAINT accounts_pkey PRIMARY KEY (id)
);
CREATE INDEX index_accounts_on_active ON accounts USING btree (active);
CREATE INDEX index_accounts_on_created_at ON accounts USING btree (created_at);
CREATE INDEX index_accounts_on_organization_id ON accounts USING btree (organization_id);

我的表有 20 万条记录,我的查询是:

select count(*) from account where active = false and organization_id in (3,2,20,30,99,69) and created_at >= '2016-09-03 15:29:54.541924';

解释分析返回此查询计划:

Finalize Aggregate  (cost=5791.37..5791.38 rows=1 width=8) (actual time=36.504..36.504 rows=1 loops=1)
  ->  Gather  (cost=5791.26..5791.37 rows=1 width=8) (actual time=36.411..38.788 rows=2 loops=1)
        Workers Planned: 1
        Workers Launched: 1
        ->  Partial Aggregate  (cost=4791.26..4791.27 rows=1 width=8) (actual time=31.313..31.313 rows=1 loops=2)
              ->  Parallel Seq Scan on accounts  (cost=0.00..4625.94 rows=66126 width=0) (actual time=0.073..26.518 rows=56057 loops=2)
                    Filter: ((NOT active) AND (created_at >= '2016-09-03 15:29:54.541924'::timestamp without time zone) AND (organization_id = ANY ('3,2,20,30,99,69'::integer[])))
                    Rows Removed by Filter: 43943
Planning Time: 0.293 ms
Execution Time: 38.863 ms

感谢您的任何建议。

【问题讨论】:

你有两个答案。哪一个提供最好的性能? 您有两个数据库。您实际使用的是哪一个? 我目前正在使用 Postgres,但我想要一个与这两个数据库兼容的解决方案。 【参考方案1】:

这里需要一个复合索引来覆盖整个WHERE 子句。以下应适用于任一数据库:

CREATE INDEX idx ON account (organization_id, created_at, active);

我首先放置了限制较多的列,然后是限制最少的列。也就是说,我假设很少有记录符合您对organization_id 的限制,而更多记录符合active

请注意,虽然您在上述三列上确实有索引,但它们位于不同的索引中。大多数时候(但并非总是如此),数据库会选择仅使用单个索引来满足执行计划。这意味着,例如,Postgres 可能会选择不使用您的任何索引,因为没有一个索引涵盖整个 WHERE 子句。

【讨论】:

没有。首先放置使用= 测试的列。这将更好地聚集信息。 @RickJames 我不了解 mysql,但 postgresql 使用领先的 organization_id 就好了,跳转到每个 in-list 值内的正确 created_at 偏移量。 (但由于选择性低,在这里这样做可能没什么意义)。【参考方案2】:

对于这个查询:

select count(*)
from account
where active = false and
      organization_id in (3,2,20,30,99,69) and
      created_at >= '2016-09-03 15:29:54.541924';

我会推荐一个包含三个键的索引:(active, organization_id, created_at)

也就是说,跨两个数据库编写查询的最有效方法可能是:

select sum(cnt)
from ((select count(*) as cnt
       from account
       where active = false and
             organization_id = 3 and
             created_at >= '2016-09-03 15:29:54.541924'
      ) union all
      (select count(*) as cnt
       from account
       where active = false and
             organization_id = 2 and
             created_at >= '2016-09-03 15:29:54.541924'
      ) union all
      . . .
     ) a;
   

这样可以充分利用(active, organization_id, created_at)(organization_id, active, created_at)上的索引。

【讨论】:

【参考方案3】:

对于 MySQL,我说

INDEX(active, organization_id, created_at),
INDEX(active, created_at, organization_id) 

优化器将查看统计数据来决定哪个可能更快。

每个都是“覆盖”。优化器将使用它选择的INDEX 的前两列进行过滤,然后使用第三列完成过滤。

active 必须是第一个,因为它是用= 测试的,而其他两个不是。

= 超过“基数”的参数:Higher cardinality column first in an index when involving a range?

【讨论】:

【参考方案4】:

鉴于您正在访问超过一半的行:

(actual) rows=56057
Rows Removed by Filter: 43943

与仅仅进行 seq 扫描相比,几乎没有理由认为任何索引都会非常有用。

【讨论】:

以上是关于我可以做些啥来优化适用于 Postgres 和 MySQL 的 SQL 查询?的主要内容,如果未能解决你的问题,请参考以下文章

可以做些啥来优化离开方法和清空局部变量堆栈所需的时间?

查询在数据库中花费了更多时间,尽管在连接条件中使用了索引列,那么我们可以在代码中做些啥来优化

我能做些啥来解决“1 次提交落后于主人”?

查询很慢,我可以做些啥来改进?

我可以做些啥来提高 Lua 程序的性能?

我可以做些啥来加快 S3 上传/更新?