【发布时间】:2021-09-30 11:43:13
【问题描述】:
我有一个 Postgresql 表:
CREATE TABLE IF NOT EXISTS acls1k (
pkey serial PRIMARY KEY,
user_name VARCHAR(50),
tenant_id VARCHAR(36),
CONSTRAINT user_name_unique1k UNIQUE (user_name)
);
user_name 列上有一个unique 索引。当我使用常量查询表时,index 用于查询:
explain analyze select * from acls1k where user_name = 'p1kuser1t1';
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Index Scan using user_name_unique1k on acls1k (cost=0.28..8.29 rows=1 width=53) (actual time=0.071..0.073 rows=1 loops=1)
Index Cond: ((user_name)::text = 'p1kuser1t1'::text)
Planning Time: 0.240 ms
Execution Time: 0.094 ms
(4 rows)
但是当我使用 current_user 变量时,会执行 顺序扫描 而不是索引扫描:
explain analyze select * from acls1k where user_name = current_user;
QUERY PLAN
--------------------------------------------------------------------------------------------------
Seq Scan on acls1k (cost=0.00..59.00 rows=1 width=53) (actual time=0.162..0.845 rows=1 loops=1)
Filter: ((user_name)::text = CURRENT_USER)
Rows Removed by Filter: 999
Planning Time: 0.097 ms
Execution Time: 0.861 ms
(5 rows)
尝试铸造:
explain analyze select * from acls1k where user_name = CAST(current_user as varchar(50));
explain analyze select * from acls1k where user_name = CAST(current_user as text);
explain analyze select * from acls1k where user_name = current_user::text;
但是,仍然使用顺序扫描,不确定为什么我会看到这种行为,有什么方法可以让这个查询使用索引扫描?
编辑:
谁能回答为什么从name 数据类型到varchar 的内联转换失败?
@jimjonesbr 给出了如何使用prepared statements 进行索引扫描的答案。我推测问题在于查询规划器没有正确处理数据类型转换。
我尝试使用函数而不是准备好的语句,但无法运行解释分析来检查索引扫描是否执行。我能够注意到的是,将current_user 作为varchar 参数传递而不是内联使用它可能表明使用索引扫描时运行时更快。
create function get_acl(auser varchar(63))
returns varchar(36)
language plpgsql
as
$$
begin
return (select tenant_id from acls1k where user_name = auser);
end;
$$;
create function get_acl_inline()
returns varchar(36)
language plpgsql
as
$$
begin
return (select tenant_id from acls1k where user_name = current_user);
end;
并使用explain analyze 运行产生这个:
explain analyze select from get_acl(current_user::text);
QUERY PLAN
------------------------------------------------------------------------------------------------------
Function Scan on get_acl (cost=0.26..0.27 rows=1 width=0) (actual time=0.435..0.436 rows=1 loops=1)
Planning Time: 0.027 ms
Execution Time: 0.456 ms
(3 rows)
explain analyze select from get_acl_inline();
QUERY PLAN
-------------------------------------------------------------------------------------------------------------
Function Scan on get_acl_inline (cost=0.25..0.26 rows=1 width=0) (actual time=1.833..1.834 rows=1 loops=1)
Planning Time: 0.024 ms
Execution Time: 1.850 ms
时间差可能表明索引扫描是在get_acls(current_user::text) 中进行的
【问题讨论】:
-
如果不强制转换为
text或varchar类型,则需要进行seq 扫描,因为current_user是name类型,具有不同的比较语义。使用演员表应该可以工作,如果表格是新填充的,请确保您已经分析过表格。 -
@DanielVérité 我仍在努力理解这一点。看来铸造是不够的:db-fiddle.com/f/8qX2tbtU3tNH1U6QsVTyaf/3我错过了什么?
-
@JimJones:您的示例非常不同,因为它插入同一用户的 100 万次,而 darc 对该列具有唯一约束。如果统计数据表明该常数非常频繁(在这种情况下索引会适得其反),则 postgres 可能不会使用索引来查找常数
-
@DanielVérité 我明白了,但我的意思是:为什么它使用准备好的语句来使用索引,尽管这是一个坏主意?我的意思是,这两个查询的数据是相同的。我有点困惑,很抱歉我打扰了:)
-
像这样的简单函数最好定义为
language sql stable- 然后您还可以看到完整的执行计划。
标签: sql postgresql query-optimization