我可以做些啥来优化适用于 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 查询?的主要内容,如果未能解决你的问题,请参考以下文章