我有一个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)
);
用户名称列上有一个独特的
索引。当我使用常量查询表时,该查询会使用索引:
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)
中执行了索引扫描。
current_user
强制转换为text
或varchar
类型,则预计会进行序列扫描,因为current_user
是name
类型,其比较语义不同。使用强制转换应该可以解决问题,请确保在表格刚刚填充时进行了分析。 - Daniel Véritélanguage sql stable
- 这样你也可以看到完整的执行计划。 - user330315